first pass - kryz inspired

This commit is contained in:
2026-06-10 18:34:23 +00:00
parent 3063d01ec5
commit 771e1e166f
53 changed files with 10521 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
<section class="about">
<div class="container">
<div class="about-intro">
<img src="assets/svg/orange-flower.svg" alt="" class="about-flower-decor" aria-hidden="true">
<h2>About <span class="text-accent">KMountain Flower</span></h2>
<p class="section-intro">
For nearly four decades, KMountain Flower has been the voice of our
community — a nonprofit, listener-supported radio station dedicated to
music, local news, and the stories that bind mountain towns together.
</p>
</div>
<div class="about-grid">
<div class="about-card">
<div class="card-icon">🎵</div>
<h3>Our Music</h3>
<p>
From bluegrass and folk to jazz and classical, our playlists reflect
the rich cultural tapestry of the high country and its neighbors. We
feature local artists alongside the world's best voices.
</p>
</div>
<div class="about-card">
<div class="card-icon">🤝</div>
<h3>Our Community</h3>
<p>
Every show is staffed by volunteers who live and work in these
mountains. From high school DJs to retired teachers, our team is your
community — passionate about sharing what they love.
</p>
</div>
<div class="about-card">
<div class="card-icon">📡</div>
<h3>Our Reach</h3>
<p>
Over the air on 98.7 FM and streaming live worldwide. Whether you're
hiking the ridgeline or on the other side of the planet, our signal
— and our welcome — goes everywhere.
</p>
</div>
<div class="about-card">
<div class="card-icon">🌱</div>
<h3>Our Mission</h3>
<p>
To keep public radio free, independent, and deeply rooted in place.
No corporate advertising, no spin — just music, conversation, and
community served with care.
</p>
</div>
</div>
<div class="about-cta">
<div class="about-flower-divider" aria-hidden="true">
<img src="assets/svg/small-flower.svg" alt="">
</div>
<h3>Become a Member</h3>
<p class="section-intro">
As a 501(c)(3) nonprofit, every donation keeps our airwaves free and our
signal strong. Members receive exclusive perks and the satisfaction of
knowing they helped make this possible.
</p>
<a routerLink="/donate" class="btn">Join the Station</a>
</div>
</div>
</section>
+87
View File
@@ -0,0 +1,87 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.about {
padding: $spacing-3xl 0;
background: var(--color-cream);
}
.about-intro {
text-align: center;
margin-bottom: $spacing-2xl;
.about-flower-decor {
width: 80px;
height: 80px;
margin: 0 auto $spacing-md;
opacity: 0.6;
animation: float 5s ease-in-out infinite;
}
}
.about-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: $spacing-lg;
margin-bottom: $spacing-3xl;
}
.about-card {
@include card-style;
padding: $spacing-xl;
text-align: center;
background: var(--color-white);
.card-icon {
font-size: 2.5rem;
margin-bottom: $spacing-md;
}
h3 {
font-size: 1.3rem;
margin-bottom: $spacing-sm;
color: var(--color-primary);
}
p {
font-size: 0.95rem;
color: var(--color-dark);
line-height: 1.7;
}
}
.about-cta {
text-align: center;
padding: $spacing-2xl $spacing-lg;
background: $mixed-gradient;
border-radius: $radius-xl;
color: var(--color-white);
.about-flower-divider {
width: 40px;
height: 40px;
margin: 0 auto $spacing-md;
img {
width: 100%;
filter: brightness(1.2);
}
}
h3 {
color: var(--color-white);
font-size: 1.8rem;
margin-bottom: $spacing-sm;
}
.section-intro {
color: rgba(255, 255, 255, 0.85);
margin-bottom: $spacing-lg;
}
}
@media (max-width: #{$bp-md - 1px}) {
.about-grid {
grid-template-columns: 1fr;
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-about',
standalone: true,
imports: [],
templateUrl: './about.component.html',
styleUrl: './about.component.scss',
})
export class AboutComponent {}
+8
View File
@@ -0,0 +1,8 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes)],
};
+5
View File
@@ -0,0 +1,5 @@
<app-navbar></app-navbar>
<main>
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
+36
View File
@@ -0,0 +1,36 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', redirectTo: '/', pathMatch: 'full' },
{
path: '',
loadComponent: () =>
import('./hero/hero.component').then((m) => m.HeroComponent),
},
{
path: 'about',
loadComponent: () =>
import('./about/about.component').then((m) => m.AboutComponent),
},
{
path: 'schedule',
loadComponent: () =>
import('./schedule/schedule.component').then((m) => m.ScheduleComponent),
},
{
path: 'donate',
loadComponent: () =>
import('./donate/donate.component').then((m) => m.DonateComponent),
},
{
path: 'events',
loadComponent: () =>
import('./events/events.component').then((m) => m.EventsComponent),
},
{
path: 'contact',
loadComponent: () =>
import('./contact/contact.component').then((m) => m.ContactComponent),
},
{ path: '**', redirectTo: '' },
];
+13
View File
@@ -0,0 +1,13 @@
@use '../styles/variables' as *;
app-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 200;
}
main {
padding-top: 70px;
}
+13
View File
@@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './navbar/navbar.component';
import { FooterComponent } from './footer/footer.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, NavbarComponent, FooterComponent],
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App {}
+65
View File
@@ -0,0 +1,65 @@
<section class="contact">
<div class="container">
<div class="contact-header">
<img src="assets/svg/small-flower.svg" alt="" class="contact-flower" aria-hidden="true">
<h2>Get in <span class="text-accent">Touch</span></h2>
<p class="section-intro">
Have a song request, want to volunteer, or just want to say hello?
We'd love to hear from you.
</p>
</div>
<div class="contact-grid">
<!-- Contact form -->
<div class="contact-form-wrapper">
<form class="contact-form" (ngSubmit)="onSubmit()" #contactForm="ngForm">
<div class="form-group">
<label for="name">Your Name</label>
<input id="name" type="text" ngModel name="name" required placeholder="Jane Doe">
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input id="email" type="email" ngModel name="email" required placeholder="jane@example.com">
</div>
<div class="form-group">
<label for="subject">Subject</label>
<select id="subject" ngModel name="subject" required>
<option value="" disabled selected>Choose a topic</option>
<option value="general">General Inquiry</option>
<option value="volunteer">Volunteer</option>
<option value="donation">Donation Question</option>
<option value="advertising">Community Announcement</option>
<option value="song">Song Request</option>
<option value="technical">Technical Support</option>
</select>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" rows="5" ngModel name="message" required placeholder="Your message..."></textarea>
</div>
<button type="submit" class="btn btn-form">Send Message</button>
</form>
</div>
<!-- Station info -->
<div class="contact-info">
<div class="info-card">
<h4><span aria-hidden="true">📍</span> Studio Location</h4>
<p>42 Pine Street<br>Mountain View, CO 80424</p>
</div>
<div class="info-card">
<h4><span aria-hidden="true">📞</span> Phone</h4>
<p>(970) 555-0198<br>MonFri 8 AM 6 MT</p>
</div>
<div class="info-card">
<h4><span aria-hidden="true">✉️</span> Email</h4>
<p><a href="mailto:hello@kmountainflower.org">hello@kmountainflower.org</a></p>
</div>
<div class="info-card">
<h4><span aria-hidden="true">📡</span> Frequencies</h4>
<p>98.7 FM — Mountain Region<br>Stream: kmountainflower.org/live</p>
</div>
</div>
</div>
</div>
</section>
+135
View File
@@ -0,0 +1,135 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.contact {
padding: $spacing-3xl 0;
background: var(--color-cream);
}
.contact-header {
text-align: center;
margin-bottom: $spacing-2xl;
.contact-flower {
width: 50px;
height: 50px;
margin: 0 auto $spacing-md;
opacity: 0.5;
}
}
.contact-grid {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: $spacing-2xl;
align-items: start;
}
// Form
.contact-form-wrapper {
background: var(--color-white);
padding: $spacing-xl;
border-radius: $radius-lg;
box-shadow: $shadow-md;
}
.contact-form {
display: flex;
flex-direction: column;
gap: $spacing-md;
}
.form-group {
display: flex;
flex-direction: column;
gap: $spacing-xs;
label {
font-weight: 600;
font-size: 0.9rem;
color: var(--color-primary);
}
input,
select,
textarea {
padding: $spacing-sm;
border: 1px solid var(--color-light);
border-radius: $radius-sm;
font-family: var(--font-primary);
font-size: 0.95rem;
color: var(--color-dark);
background: var(--color-white);
transition: border-color $transition-fast, box-shadow $transition-fast;
&:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px rgba(232, 122, 46, 0.15);
}
&::placeholder {
color: var(--color-medium);
}
}
textarea {
resize: vertical;
min-height: 120px;
}
}
.btn-form {
@include button-style($accent-orange, $neutral-white, $accent-orange-dark);
align-self: flex-start;
font-size: 1rem;
padding: $spacing-sm $spacing-xl;
}
// Info
.contact-info {
display: flex;
flex-direction: column;
gap: $spacing-md;
}
.info-card {
background: var(--color-white);
padding: $spacing-lg;
border-radius: $radius-md;
box-shadow: $shadow-sm;
h4 {
font-size: 1rem;
color: var(--color-primary);
margin-bottom: $spacing-sm;
display: flex;
align-items: center;
gap: $spacing-xs;
span {
font-size: 1.2rem;
}
}
p {
font-size: 0.9rem;
color: var(--color-dark);
line-height: 1.6;
a {
color: var(--color-accent);
}
}
}
@media (max-width: #{$bp-md - 1px}) {
.contact-grid {
grid-template-columns: 1fr;
}
.btn-form {
width: 100%;
justify-content: center;
}
}
+15
View File
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-contact',
standalone: true,
imports: [FormsModule],
templateUrl: './contact.component.html',
styleUrl: './contact.component.scss',
})
export class ContactComponent {
onSubmit(): void {
alert('Thank you for your message! We will get back to you shortly.');
}
}
+73
View File
@@ -0,0 +1,73 @@
<section class="donate">
<!-- Hero banner -->
<div class="donate-banner">
<div class="donate-banner-overlay">
<div class="container donate-banner-content">
<img src="assets/svg/orange-flower.svg" alt="" class="donate-flower-hero" aria-hidden="true">
<h2>Keep the Music <span class="text-accent">Free</span></h2>
<p class="donate-subtitle">
KMountain Flower Radio is 100% listener-funded and independent.
Your gift keeps our signal strong and our airwaves free for everyone.
</p>
</div>
</div>
</div>
<div class="container">
<!-- Donation tiers -->
<div class="donate-tiers">
<h3>Choose Your Level of Support</h3>
<div class="tiers-grid">
<div class="tier-card" *ngFor="let tier of tiers">
<div class="tier-icon">{{ tier.icon }}</div>
<div class="tier-amount">${{ tier.amount }}</div>
<div class="tier-period">per month</div>
<h4>{{ tier.name }}</h4>
<ul class="tier-benefits">
<li *ngFor="let benefit of tier.benefits">{{ benefit }}</li>
</ul>
<a href="#" class="btn btn-tier">Select {{ tier.name }}</a>
</div>
</div>
</div>
<!-- One-time donation -->
<div class="donate-one-time">
<div class="donate-flower-divider" aria-hidden="true">
<img src="assets/svg/small-flower.svg" alt="">
</div>
<h3>Or Make a One-Time Gift</h3>
<p class="section-intro">
Every dollar matters — from $5 to $5,000. No amount is too small to make
a difference. All donations are tax-deductible (EIN: 84-XXXXXXX).
</p>
<a href="#" class="btn btn-lg">Give Any Amount</a>
</div>
<!-- Why donate -->
<div class="donate-why">
<div class="why-grid">
<div class="why-item">
<span class="why-icon">📻</span>
<strong>98.7 FM Signal</strong>
<p>Covering 3 mountain counties and the valleys between</p>
</div>
<div class="why-item">
<span class="why-icon">🎙️</span>
<strong>24/7 On-Air</strong>
<p>Over 15,000 hours of programming per year</p>
</div>
<div class="why-item">
<span class="why-icon">🌍</span>
<strong>Worldwide Stream</strong>
<p>Accessible from every corner of the globe, free forever</p>
</div>
<div class="why-item">
<span class="why-icon">🎓</span>
<strong>Youth Programs</strong>
<p>Free radio training for 200+ local students each year</p>
</div>
</div>
</div>
</div>
</section>
+197
View File
@@ -0,0 +1,197 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.donate {
background: var(--color-cream);
}
// Banner
.donate-banner {
position: relative;
margin-bottom: $spacing-3xl;
}
.donate-banner-overlay {
background: $mixed-gradient;
padding: $spacing-3xl $spacing-lg;
text-align: center;
}
.donate-banner-content {
position: relative;
z-index: 1;
}
.donate-flower-hero {
width: 80px;
height: 80px;
margin: 0 auto $spacing-md;
opacity: 0.7;
animation: float 4s ease-in-out infinite;
}
.donate-banner h2 {
color: var(--color-white);
font-size: 2.4rem;
margin-bottom: $spacing-md;
}
.donate-subtitle {
color: rgba(255, 255, 255, 0.85);
font-size: 1.1rem;
max-width: 550px;
margin: 0 auto;
line-height: 1.7;
}
// Tiers
.donate-tiers {
text-align: center;
margin-bottom: $spacing-3xl;
h3 {
font-size: 1.8rem;
margin-bottom: $spacing-xl;
}
}
.tiers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: $spacing-lg;
margin-bottom: $spacing-xl;
}
.tier-card {
@include card-style;
padding: $spacing-xl;
text-align: center;
background: var(--color-white);
border-top: 4px solid var(--color-accent);
.tier-icon {
font-size: 2rem;
margin-bottom: $spacing-sm;
}
.tier-amount {
font-size: 2.8rem;
font-weight: 700;
color: var(--color-accent);
line-height: 1;
}
.tier-period {
font-size: 0.85rem;
color: var(--color-medium);
margin-bottom: $spacing-md;
}
h4 {
font-size: 1.2rem;
color: var(--color-primary);
margin-bottom: $spacing-md;
}
.tier-benefits {
list-style: none;
text-align: left;
margin-bottom: $spacing-lg;
li {
padding: $spacing-xs 0;
font-size: 0.9rem;
color: var(--color-dark);
border-bottom: 1px solid var(--color-light);
&::before {
content: '';
color: $success-green;
font-weight: 700;
}
}
}
.btn-tier {
@include button-style($accent-orange, $neutral-white, $accent-orange-dark);
display: inline-block;
width: 100%;
text-align: center;
}
}
// One-time
.donate-one-time {
text-align: center;
padding: $spacing-2xl $spacing-lg;
background: var(--color-primary);
border-radius: $radius-xl;
margin-bottom: $spacing-3xl;
.donate-flower-divider {
width: 40px;
height: 40px;
margin: 0 auto $spacing-md;
img {
width: 100%;
filter: brightness(1.2);
}
}
h3 {
color: var(--color-white);
font-size: 1.6rem;
margin-bottom: $spacing-sm;
}
.section-intro {
color: rgba(255, 255, 255, 0.8);
margin-bottom: $spacing-lg;
}
.btn-lg {
@include button-style($accent-orange, $neutral-white, $accent-orange-dark);
font-size: 1.2rem;
padding: $spacing-md $spacing-2xl;
}
}
// Why
.donate-why {
padding-bottom: $spacing-xl;
}
.why-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: $spacing-lg;
.why-item {
text-align: center;
padding: $spacing-lg;
.why-icon {
display: block;
font-size: 2.5rem;
margin-bottom: $spacing-sm;
}
strong {
display: block;
color: var(--color-primary);
margin-bottom: $spacing-xs;
font-size: 1.05rem;
}
p {
font-size: 0.9rem;
color: var(--color-medium);
}
}
}
@media (max-width: #{$bp-md - 1px}) {
.donate-banner h2 { font-size: 1.8rem; }
.tiers-grid { grid-template-columns: 1fr; max-width: 320px; margin-left: auto; margin-right: auto; }
}
+64
View File
@@ -0,0 +1,64 @@
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
interface Tier {
name: string;
amount: number;
icon: string;
benefits: string[];
}
@Component({
selector: 'app-donate',
standalone: true,
imports: [NgFor],
templateUrl: './donate.component.html',
styleUrl: './donate.component.scss',
})
export class DonateComponent {
readonly tiers: Tier[] = [
{
name: 'Seed',
amount: 5,
icon: '🌱',
benefits: [
'Digital thank-you card',
'Name listed on our website',
'Quarterly newsletter',
],
},
{
name: 'Stream',
amount: 15,
icon: '🎵',
benefits: [
'All Seed benefits',
'KMountain Flower pin',
'Priority show-request line',
'Annual station update call',
],
},
{
name: 'Ridge',
amount: 30,
icon: '🏔️',
benefits: [
'All Stream benefits',
'Official member patch',
'Exclusive show invitations',
'Annual station tour',
],
},
{
name: 'Summit',
amount: 60,
icon: '⭐',
benefits: [
'All Ridge benefits',
'Featured on the Summit Wall',
'Name on-air during anniversary special',
'Lifetime patron recognition',
],
},
];
}
+34
View File
@@ -0,0 +1,34 @@
<section class="events">
<div class="container">
<div class="events-header">
<img src="assets/svg/small-flower.svg" alt="" class="events-flower" aria-hidden="true">
<h2>Upcoming <span class="text-accent">Events</span></h2>
<p class="section-intro">
Come meet the team, enjoy live music, and support public radio in
person. All events are open to the community — friends and family of
listeners welcome.
</p>
</div>
<div class="events-list">
<div class="event-card" *ngFor="let event of upcomingEvents">
<div class="event-date">
<span class="event-month">Jul</span>
<span class="event-day">12</span>
<span class="event-year">2026</span>
</div>
<div class="event-content">
<h3>{{ event.title }}</h3>
<p>{{ event.description }}</p>
<div class="event-meta">
<span class="event-location">
<span aria-hidden="true">📍</span> {{ event.location }}
</span>
<a href="#" class="event-rsvp">Learn More →</a>
</div>
</div>
<div class="event-icon" aria-hidden="true">{{ event.icon }}</div>
</div>
</div>
</div>
</section>
+135
View File
@@ -0,0 +1,135 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.events {
padding: $spacing-3xl 0;
background: var(--color-white);
}
.events-header {
text-align: center;
margin-bottom: $spacing-2xl;
.events-flower {
width: 50px;
height: 50px;
margin: 0 auto $spacing-md;
opacity: 0.5;
}
}
.events-list {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: $spacing-lg;
}
.event-card {
@include card-style;
display: flex;
background: var(--color-white);
overflow: hidden;
.event-date {
flex-shrink: 0;
background: $mixed-gradient;
color: var(--color-white);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: $spacing-md $spacing-lg;
min-width: 90px;
text-align: center;
.event-month {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 600;
}
.event-day {
font-size: 2.2rem;
font-weight: 700;
line-height: 1;
}
.event-year {
font-size: 0.75rem;
opacity: 0.8;
}
}
.event-content {
flex: 1;
padding: $spacing-lg;
h3 {
font-size: 1.3rem;
color: var(--color-primary);
margin-bottom: $spacing-sm;
}
p {
font-size: 0.95rem;
color: var(--color-dark);
line-height: 1.6;
margin-bottom: $spacing-sm;
}
}
.event-meta {
display: flex;
gap: $spacing-md;
align-items: center;
flex-wrap: wrap;
font-size: 0.85rem;
color: var(--color-medium);
.event-location {
display: flex;
align-items: center;
gap: 4px;
}
.event-rsvp {
color: var(--color-accent);
font-weight: 600;
transition: color $transition-fast;
&:hover {
color: var(--color-accent-dark);
}
}
}
.event-icon {
flex-shrink: 0;
font-size: 3rem;
display: flex;
align-items: center;
padding-right: $spacing-md;
opacity: 0.7;
}
}
@media (max-width: #{$bp-md - 1px}) {
.event-card {
flex-direction: column;
.event-date {
flex-direction: row;
gap: $spacing-sm;
padding: $spacing-sm $spacing-md;
}
.event-icon {
padding-right: 0;
padding-top: $spacing-sm;
justify-content: flex-end;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
interface Event {
date: string;
title: string;
description: string;
location: string;
icon: string;
}
@Component({
selector: 'app-events',
standalone: true,
imports: [NgFor],
templateUrl: './events.component.html',
styleUrl: './events.component.scss',
})
export class EventsComponent {
readonly upcomingEvents: Event[] = [
{
date: 'July 12, 2026',
title: 'Summer Sounds Festival',
description: 'A full day of live local music, community booths, and station meet-and-greet at the foothills park. Free admission — donations welcome.',
location: 'Foothills Community Park',
icon: '🎵',
},
{
date: 'August 5, 2026',
title: 'Annual Fundraiser Gala',
description: 'Our biggest fundraiser of the year! Enjoy dinner, silent auction, and performances from past guest artists.',
location: 'Mountain View Ballroom',
icon: '🎙️',
},
{
date: 'September 18, 2026',
title: 'Volunteer Open House',
description: 'Tour the studio, meet the team, and learn how you can help keep our free stream alive. New volunteers always welcome!',
location: 'KMTN Studio — 42 Pine St',
icon: '🤝',
},
{
date: 'October 30, 2026',
title: 'Harvest Moon Broadcast',
description: 'Special live broadcast from the harvest moon gathering. Featuring folk, bluegrass, and indigenous music.',
location: 'Ridgefield Amphitheater',
icon: '🌙',
},
];
}
+58
View File
@@ -0,0 +1,58 @@
<footer class="footer">
<div class="container">
<div class="footer-top">
<div class="footer-brand">
<img src="assets/svg/small-flower.svg" alt="" class="footer-flower" aria-hidden="true">
<h3>
<span class="brand-m">K</span>MOUNTAIN
<span class="brand-a">FLOWER</span> Radio
</h3>
<p class="footer-tagline">
Community-powered nonprofit public radio — free to stream, funded by you.
</p>
<p class="footer-legal">
KMountain Flower Radio is a 501(c)(3) nonprofit organization.<br>
EIN: 84-XXXXXXX. All donations are tax-deductible.
</p>
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Quick Links</h4>
<ul>
<li><a routerLink="/about">About Us</a></li>
<li><a routerLink="/schedule">Schedule</a></li>
<li><a routerLink="/events">Events</a></li>
<li><a routerLink="/donate">Donate</a></li>
<li><a routerLink="/contact">Contact</a></li>
</ul>
</div>
<div class="footer-col">
<h4>Support</h4>
<ul>
<li><a routerLink="/donate">Become a Member</a></li>
<li><a href="#">Volunteer</a></li>
<li><a href="#">Corporate Giving</a></li>
<li><a href="#">Planned Giving</a></li>
</ul>
</div>
<div class="footer-col">
<h4>Follow Us</h4>
<div class="social-links">
<a href="#" aria-label="Facebook" class="social-link">📘</a>
<a href="#" aria-label="Twitter" class="social-link">🐦</a>
<a href="#" aria-label="Instagram" class="social-link">📷</a>
<a href="#" aria-label="YouTube" class="social-link">📺</a>
</div>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 19872026 KMountain Flower Radio. All rights reserved.</p>
<p class="footer-made">
Made with 🧡 in the mountains
</p>
</div>
</div>
</footer>
+132
View File
@@ -0,0 +1,132 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.footer {
background: var(--color-primary-dark);
color: rgba(255, 255, 255, 0.8);
padding: $spacing-3xl 0 $spacing-lg;
}
.footer-top {
display: grid;
grid-template-columns: 1.2fr 2fr;
gap: $spacing-2xl;
padding-bottom: $spacing-2xl;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-brand {
.footer-flower {
width: 40px;
height: 40px;
margin-bottom: $spacing-sm;
filter: brightness(1.1);
}
h3 {
font-size: 1.4rem;
color: var(--color-white);
margin-bottom: $spacing-sm;
line-height: 1.2;
.brand-m {
color: var(--color-white);
font-weight: 700;
}
.brand-a {
@include gradient-text(var(--color-accent-light), var(--color-accent));
}
}
.footer-tagline {
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: $spacing-md;
}
.footer-legal {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
}
}
.footer-links {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $spacing-xl;
align-self: start;
}
.footer-col {
h4 {
color: var(--color-accent-light);
font-size: 1rem;
margin-bottom: $spacing-md;
font-weight: 600;
}
ul {
list-style: none;
li {
margin-bottom: $spacing-xs;
a {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
transition: color $transition-fast;
&:hover {
color: var(--color-accent-light);
}
}
}
}
}
.social-links {
display: flex;
gap: $spacing-sm;
.social-link {
font-size: 1.5rem;
text-decoration: none;
transition: transform $transition-fast;
&:hover {
transform: scale(1.15);
}
}
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: $spacing-lg;
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.5);
.footer-made {
font-style: italic;
color: rgba(255, 255, 255, 0.4);
}
}
@media (max-width: #{$bp-md - 1px}) {
.footer-top {
grid-template-columns: 1fr;
}
.footer-links {
grid-template-columns: 1fr;
}
.footer-bottom {
flex-direction: column;
gap: $spacing-sm;
text-align: center;
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-footer',
standalone: true,
imports: [],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss',
})
export class FooterComponent {}
+42
View File
@@ -0,0 +1,42 @@
<section class="hero">
<!-- Mountain background layer -->
<div class="hero-mountains" aria-hidden="true">
<img src="assets/svg/mountain-silhouette.svg" alt="">
</div>
<!-- Gradient overlay -->
<div class="hero-gradient"></div>
<!-- Content -->
<div class="hero-content container">
<div class="hero-inner">
<div class="hero-icon">
<img src="assets/svg/orange-flower.svg" alt="" class="hero-flower">
</div>
<h1 class="hero-title">
<span class="hero-line1">KMountain</span>
<span class="hero-line2 text-accent">Flower Radio</span>
</h1>
<p class="hero-subtitle">
Your community-powered, nonprofit radio station — bringing music,
stories, and connection to the mountain towns and beyond since 1987.
</p>
<p class="hero-subtitle hero-tagline">
Free to stream. Funded by you. Always on-air.
</p>
<div class="hero-actions">
<a routerLink="/donate" class="btn btn-hero-primary">Support the Station</a>
<a routerLink="/schedule" class="btn btn-hero-secondary">View Schedule</a>
</div>
<div class="hero-listeners">
<span class="listeners-dot"></span>
<span class="listeners-text">Live on-air now — Stream free worldwide</span>
</div>
</div>
</div>
<!-- Mountain divider at bottom -->
<div class="hero-divider" aria-hidden="true">
<img src="assets/svg/mountain-divider.svg" alt="">
</div>
</section>
+166
View File
@@ -0,0 +1,166 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.hero {
position: relative;
min-height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.hero-mountains {
position: absolute;
inset: 0;
z-index: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center bottom;
}
}
.hero-gradient {
position: absolute;
inset: 0;
z-index: 1;
background: $sky-gradient;
opacity: 0.85;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
padding: $spacing-3xl $spacing-lg;
}
.hero-inner {
@include fade-in;
}
.hero-icon {
margin-bottom: $spacing-lg;
animation: float 4s ease-in-out infinite;
.hero-flower {
width: 100px;
height: 100px;
margin: 0 auto;
filter: drop-shadow(0 4px 12px rgba(232, 122, 46, 0.4));
}
}
.hero-title {
margin-bottom: $spacing-md;
}
.hero-line1 {
display: block;
font-size: 3.5rem;
font-weight: 700;
color: var(--color-white);
letter-spacing: 0.05em;
}
.hero-line2 {
display: block;
font-size: 3rem;
font-weight: 700;
@include gradient-text(var(--color-accent-light), var(--color-accent));
}
.hero-subtitle {
font-size: 1.15rem;
color: rgba(255, 255, 255, 0.85);
max-width: 600px;
margin: 0 auto $spacing-sm;
line-height: 1.7;
}
.hero-tagline {
font-style: italic;
color: var(--color-accent-muted);
font-size: 1.05rem;
}
.hero-actions {
display: flex;
gap: $spacing-md;
justify-content: center;
flex-wrap: wrap;
margin: $spacing-lg 0;
}
.btn-hero-primary {
@include button-style($accent-orange, $neutral-white, $accent-orange-dark);
font-size: 1.1rem;
padding: $spacing-sm $spacing-xl;
letter-spacing: 0.03em;
}
.btn-hero-secondary {
@include button-style(transparent, var(--color-white), rgba(255,255,255,0.15));
border: 2px solid rgba(255, 255, 255, 0.4);
font-size: 1.1rem;
padding: $spacing-sm $spacing-xl;
letter-spacing: 0.03em;
&:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.7);
}
}
.hero-listeners {
margin-top: $spacing-xl;
display: flex;
align-items: center;
justify-content: center;
gap: $spacing-sm;
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.7);
}
.listeners-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: $radius-full;
background: $success-green;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74, 158, 79, 0.4); }
50% { opacity: 0.7; box-shadow: 0 0 0 6px rgba(74, 158, 79, 0); }
}
.hero-divider {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
img {
width: 100%;
}
}
// Responsive
@media (max-width: #{$bp-md - 1px}) {
.hero-line1 { font-size: 2.5rem; }
.hero-line2 { font-size: 2.2rem; }
.hero-subtitle { font-size: 1rem; }
.hero-flower { width: 70px; height: 70px; }
.hero-actions { flex-direction: column; align-items: center; }
}
@media (max-width: #{$bp-sm - 1px}) {
.hero-line1 { font-size: 2rem; }
.hero-line2 { font-size: 1.8rem; }
}
+10
View File
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-hero',
standalone: true,
imports: [],
templateUrl: './hero.component.html',
styleUrl: './hero.component.scss',
})
export class HeroComponent {}
+28
View File
@@ -0,0 +1,28 @@
<nav class="navbar" role="navigation" aria-label="Main navigation">
<div class="container navbar-inner">
<a routerLink="/" class="navbar-brand" routerLinkActive="active">
<img src="assets/svg/small-flower.svg" alt="" class="brand-flower" aria-hidden="true">
<span class="brand-name">
<span class="brand-mountain">K</span>MOUNTAIN
<span class="brand-accent">FLOWER</span>
</span>
<span class="brand-tagline">Community Radio — Est. 1987</span>
</a>
<button class="navbar-toggle" aria-label="Toggle menu" (click)="toggleMenu()">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
<div class="navbar-menu" [class.open]="menuOpen">
<ul class="nav-links">
<li><a routerLink="/about" routerLinkActive="active" (click)="closeMenu()">About</a></li>
<li><a routerLink="/schedule" routerLinkActive="active" (click)="closeMenu()">Schedule</a></li>
<li><a routerLink="/events" routerLinkActive="active" (click)="closeMenu()">Events</a></li>
<li><a routerLink="/contact" routerLinkActive="active" (click)="closeMenu()">Contact</a></li>
</ul>
<a routerLink="/donate" class="btn btn-donate-nav" (click)="closeMenu()">Donate Now</a>
</div>
</div>
</nav>
+143
View File
@@ -0,0 +1,143 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.navbar {
background: var(--color-primary-dark);
padding: $spacing-sm 0;
position: sticky;
top: 0;
z-index: $z-sticky;
box-shadow: $shadow-md;
}
.navbar-inner {
@include flex-between;
}
.navbar-brand {
display: flex;
align-items: center;
gap: $spacing-sm;
text-decoration: none;
color: var(--color-white);
}
.brand-flower {
width: 36px;
height: 36px;
}
.brand-name {
display: flex;
flex-direction: column;
line-height: 1.1;
}
.brand-mountain {
font-family: var(--font-heading);
font-size: 1.4rem;
font-weight: 700;
color: var(--color-white);
letter-spacing: 0.08em;
}
.brand-accent {
font-family: var(--font-heading);
font-size: 1.1rem;
font-weight: 700;
@include gradient-text(var(--color-accent-light), var(--color-accent));
}
.brand-tagline {
font-size: 0.65rem;
color: var(--color-medium);
letter-spacing: 0.1em;
text-transform: uppercase;
}
.navbar-toggle {
display: none;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
padding: $spacing-xs;
.bar {
display: block;
width: 24px;
height: 2px;
background: var(--color-white);
border-radius: 1px;
transition: $transition-normal;
}
}
.navbar-menu {
display: flex;
align-items: center;
gap: $spacing-lg;
}
.nav-links {
display: flex;
list-style: none;
gap: $spacing-md;
a {
color: var(--color-light);
font-size: 0.95rem;
font-weight: 500;
padding: $spacing-xs $spacing-sm;
border-radius: $radius-sm;
transition: color $transition-fast, background $transition-fast;
&:hover,
&.active {
color: var(--color-accent-light);
background: rgba(232, 122, 46, 0.1);
}
}
}
.btn-donate-nav {
@include button-style($accent-orange, $neutral-white, $accent-orange-dark);
padding: $spacing-xs $spacing-md;
font-size: 0.9rem;
}
// Responsive
@media (max-width: #{$bp-md - 1px}) {
.navbar-toggle {
display: flex;
}
.navbar-menu {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--color-primary-dark);
flex-direction: column;
padding: $spacing-md;
gap: $spacing-md;
box-shadow: $shadow-lg;
transform: translateY(-10px);
opacity: 0;
pointer-events: none;
transition: transform $transition-normal, opacity $transition-normal;
&.open {
transform: translateY(0);
opacity: 1;
pointer-events: auto;
}
}
.nav-links {
flex-direction: column;
width: 100%;
align-items: center;
}
}
+21
View File
@@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
@Component({
selector: 'app-navbar',
standalone: true,
imports: [RouterLink, RouterLinkActive],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss',
})
export class NavbarComponent {
menuOpen = false;
toggleMenu(): void {
this.menuOpen = !this.menuOpen;
}
closeMenu(): void {
this.menuOpen = false;
}
}
+74
View File
@@ -0,0 +1,74 @@
<section class="schedule">
<div class="container">
<div class="schedule-header">
<img src="assets/svg/small-flower.svg" alt="" class="schedule-flower" aria-hidden="true">
<h2>Today's <span class="text-accent">Program Schedule</span></h2>
<p class="section-intro">
All times are local (Mountain Time). Every show is free to listen to
live at 98.7 FM or via our online stream.
</p>
</div>
<div class="schedule-list">
<div class="schedule-day-label">Monday Friday</div>
<div class="schedule-table">
<div *ngFor="let program of programs" class="schedule-row">
<div class="schedule-time">{{ program.time }}</div>
<div class="schedule-divider"></div>
<div class="schedule-details">
<h4 class="schedule-title">{{ program.title }}</h4>
<div class="schedule-meta">
<span class="schedule-host">
<span aria-hidden="true">🎙️</span> {{ program.host }}
</span>
<span class="schedule-genre">{{ program.genre }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="schedule-weekend">
<div class="weekend-heading">
<img src="assets/svg/small-flower.svg" alt="" class="schedule-flower-sm" aria-hidden="true">
<h3>Weekend Programming</h3>
</div>
<div class="weekend-grid">
<div class="weekend-card">
<div class="card-day">Saturday</div>
<div class="card-programs">
<div class="card-item">
<strong>9:00 AM</strong> — Country Roads Hour
</div>
<div class="card-item">
<strong>11:00 AM</strong> — Community Hour (call-ins welcome!)
</div>
<div class="card-item">
<strong>2:00 PM</strong> — Afternoon Bluegrass
</div>
<div class="card-item">
<strong>4:00 PM</strong> — Record Request Hour
</div>
</div>
</div>
<div class="weekend-card">
<div class="card-day">Sunday</div>
<div class="card-programs">
<div class="card-item">
<strong>10:00 AM</strong> — Sunday Morning Hymns & Folk
</div>
<div class="card-item">
<strong>12:00 PM</strong> — Gospel & Roots
</div>
<div class="card-item">
<strong>3:00 PM</strong> — The Long Wave (ambient / nature sounds)
</div>
<div class="card-item">
<strong>6:00 PM</strong> — Sunday Sunset Jazz
</div>
</div>
</div>
</div>
</div>
</div>
</section>
+173
View File
@@ -0,0 +1,173 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.schedule {
padding: $spacing-3xl 0;
background: var(--color-white);
}
.schedule-header {
text-align: center;
margin-bottom: $spacing-2xl;
.schedule-flower {
width: 50px;
height: 50px;
margin: 0 auto $spacing-md;
opacity: 0.5;
}
}
.schedule-list {
max-width: 800px;
margin: 0 auto $spacing-3xl;
}
.schedule-day-label {
text-align: center;
font-family: var(--font-heading);
font-size: 1.2rem;
color: var(--color-primary);
margin-bottom: $spacing-md;
font-weight: 600;
}
.schedule-table {
display: flex;
flex-direction: column;
gap: 0;
}
.schedule-row {
display: flex;
align-items: stretch;
padding: $spacing-md 0;
border-bottom: 1px solid var(--color-light);
transition: background $transition-fast;
&:hover {
background: var(--color-cream);
}
}
.schedule-time {
width: 100px;
flex-shrink: 0;
font-family: var(--font-mono);
font-size: 0.95rem;
color: var(--color-accent);
font-weight: 600;
padding-right: $spacing-md;
text-align: right;
padding-top: 2px;
}
.schedule-divider {
width: 2px;
background: var(--color-accent-muted);
margin: 0 $spacing-sm;
border-radius: 1px;
opacity: 0.4;
}
.schedule-details {
flex: 1;
padding: $spacing-xs 0;
}
.schedule-title {
font-size: 1.05rem;
color: var(--color-primary);
margin-bottom: $spacing-xs;
}
.schedule-meta {
display: flex;
gap: $spacing-md;
font-size: 0.85rem;
color: var(--color-medium);
.schedule-host {
display: flex;
align-items: center;
gap: 4px;
}
.schedule-genre {
background: var(--color-light);
padding: 2px 10px;
border-radius: 10px;
font-style: italic;
}
}
// Weekend
.schedule-weekend {
margin-top: $spacing-xl;
}
.weekend-heading {
text-align: center;
margin-bottom: $spacing-lg;
.schedule-flower-sm {
width: 35px;
height: 35px;
margin: 0 auto $spacing-sm;
opacity: 0.5;
}
h3 {
font-size: 1.6rem;
color: var(--color-primary);
}
}
.weekend-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: $spacing-lg;
}
.weekend-card {
@include card-style;
padding: $spacing-xl;
background: var(--color-white);
.card-day {
font-family: var(--font-heading);
font-size: 1.4rem;
font-weight: 700;
color: var(--color-accent);
margin-bottom: $spacing-md;
padding-bottom: $spacing-sm;
border-bottom: 2px solid var(--color-light);
}
.card-programs {
display: flex;
flex-direction: column;
gap: $spacing-sm;
}
.card-item {
font-size: 0.95rem;
color: var(--color-dark);
padding: $spacing-sm;
border-radius: $radius-sm;
transition: background $transition-fast;
strong {
color: var(--color-primary);
}
&:hover {
background: var(--color-cream);
}
}
}
@media (max-width: #{$bp-md - 1px}) {
.schedule-time { width: 80px; font-size: 0.85rem; }
.weekend-grid { grid-template-columns: 1fr; }
}
+31
View File
@@ -0,0 +1,31 @@
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
interface Program {
time: string;
title: string;
host: string;
genre: string;
}
@Component({
selector: 'app-schedule',
standalone: true,
imports: [NgFor],
templateUrl: './schedule.component.html',
styleUrl: './schedule.component.scss',
})
export class ScheduleComponent {
readonly programs: Program[] = [
{ time: '6:00 AM', title: 'Morning Mountain Mist', host: 'Diana Walsh', genre: 'Ambient / Nature' },
{ time: '8:00 AM', title: 'Community Roundup', host: 'Tom Breen', genre: 'News / Talk' },
{ time: '10:00 AM', title: 'Bluegrass Trails', host: 'Sarah Lynn', genre: 'Bluegrass' },
{ time: '12:00 PM', title: 'Lunchtime Jazz', host: 'Mike Darrow', genre: 'Jazz' },
{ time: '2:00 PM', title: 'Folk Roots Hour', host: 'Nadia Cole', genre: 'Folk' },
{ time: '4:00 PM', title: 'Afternoon Acoustics', host: 'Jen Reeves', genre: 'Acoustic' },
{ time: '6:00 PM', title: 'Evening Echoes', host: 'Carlos Mendez', genre: 'Indie / Alternative' },
{ time: '8:00 PM', title: 'Classical Mountains', host: 'Elena Cross', genre: 'Classical' },
{ time: '10:00 PM', title: 'Night Owl Session', host: 'DJ Kofi', genre: 'Electronic' },
{ time: '12:00 AM', title: 'Late Night Jazz', host: 'Mike Darrow', genre: 'Jazz' },
];
}