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' },
];
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 80" width="100%" height="80" preserveAspectRatio="none">
<defs>
<linearGradient id="divGrad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#1a3a5c;stop-opacity:0"/>
<stop offset="20%" style="stop-color:#1a3a5c;stop-opacity:0.3"/>
<stop offset="80%" style="stop-color:#1a3a5c;stop-opacity:0.3"/>
<stop offset="100%" style="stop-color:#1a3a5c;stop-opacity:0"/>
</linearGradient>
</defs>
<path d="M0,80 L0,50 Q180,10 360,40 Q540,70 720,40 Q900,10 1080,40 Q1260,70 1440,50 L1440,80 Z" fill="url(#divGrad1)"/>
<!-- Small flower accents along divider -->
<g transform="translate(360,45) scale(0.3)" opacity="0.4">
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(0,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(72,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(144,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(216,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(288,30,30)"/>
<circle cx="30" cy="30" r="6" fill="#f5c842"/>
</g>
<g transform="translate(720,38) scale(0.35)" opacity="0.5">
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(0,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(72,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(144,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(216,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(288,30,30)"/>
<circle cx="30" cy="30" r="6" fill="#f5c842"/>
</g>
<g transform="translate(1080,45) scale(0.3)" opacity="0.4">
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(0,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(72,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(144,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(216,30,30)"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(288,30,30)"/>
<circle cx="30" cy="30" r="6" fill="#f5c842"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 400" preserveAspectRatio="none" width="100%" height="100%">
<defs>
<linearGradient id="mountainGrad1" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#2a5a8c;stop-opacity:0.9"/>
<stop offset="100%" style="stop-color:#1a3a5c;stop-opacity:1"/>
</linearGradient>
<linearGradient id="mountainGrad2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#3a6a9c;stop-opacity:0.7"/>
<stop offset="100%" style="stop-color:#2a5a8c;stop-opacity:0.8"/>
</linearGradient>
<linearGradient id="mountainGrad3" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#0e2440;stop-opacity:0.6"/>
<stop offset="100%" style="stop-color:#1a3a5c;stop-opacity:0.7"/>
</linearGradient>
<linearGradient id="snowGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.9"/>
<stop offset="100%" style="stop-color:#e8e8e8;stop-opacity:0.3"/>
</linearGradient>
</defs>
<!-- Far mountains -->
<path d="M0,400 L0,280 L120,200 L240,260 L360,150 L480,230 L540,180 L600,210 L720,120 L840,200 L960,160 L1080,220 L1200,140 L1320,200 L1440,170 L1440,400 Z" fill="url(#mountainGrad3)"/>
<!-- Mid mountains -->
<path d="M0,400 L0,320 L180,240 L300,300 L420,180 L540,270 L660,200 L780,280 L900,170 L1020,260 L1140,210 L1260,270 L1380,220 L1440,250 L1440,400 Z" fill="url(#mountainGrad2)"/>
<!-- Near mountains (prominent) -->
<path d="M0,400 L0,340 L160,280 L280,330 L400,200 L500,280 L600,220 L700,290 L820,190 L920,260 L1040,210 L1160,280 L1280,230 L1360,280 L1440,260 L1440,400 Z" fill="url(#mountainGrad1)"/>
<!-- Snow caps -->
<path d="M400,200 L385,215 L415,215 Z" fill="url(#snowGrad)" opacity="0.7"/>
<path d="M820,190 L805,208 L835,208 Z" fill="url(#snowGrad)" opacity="0.7"/>
<path d="M1280,230 L1265,248 L1295,248 Z" fill="url(#snowGrad)" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<radialGradient id="petalGrad" cx="50%" cy="40%" r="50%">
<stop offset="0%" style="stop-color:#f0a86a"/>
<stop offset="100%" style="stop-color:#e87a2e"/>
</radialGradient>
<radialGradient id="centerGrad" cx="45%" cy="40%" r="50%">
<stop offset="0%" style="stop-color:#f5c842"/>
<stop offset="100%" style="stop-color:#d4941a"/>
</radialGradient>
</defs>
<!-- Stem and leaves -->
<path d="M100,160 Q95,180 98,198" stroke="#4a7a3e" stroke-width="3" fill="none" stroke-linecap="round"/>
<path d="M98,175 Q75,165 65,170 Q78,180 98,175" fill="#5a8a4e" opacity="0.8"/>
<path d="M99,185 Q125,178 135,182 Q122,192 99,185" fill="#5a8a4e" opacity="0.8"/>
<!-- Outer petals -->
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(0,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(45,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(90,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(135,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(180,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(225,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(270,100,90)" opacity="0.9"/>
<ellipse cx="100" cy="50" rx="22" ry="35" fill="url(#petalGrad)" transform="rotate(315,100,90)" opacity="0.9"/>
<!-- Inner petals -->
<ellipse cx="100" cy="65" rx="14" ry="22" fill="#f09a4e" transform="rotate(22,100,90)" opacity="0.8"/>
<ellipse cx="100" cy="65" rx="14" ry="22" fill="#f09a4e" transform="rotate(97,100,90)" opacity="0.8"/>
<ellipse cx="100" cy="65" rx="14" ry="22" fill="#f09a4e" transform="rotate(172,100,90)" opacity="0.8"/>
<ellipse cx="100" cy="65" rx="14" ry="22" fill="#f09a4e" transform="rotate(247,100,90)" opacity="0.8"/>
<ellipse cx="100" cy="65" rx="14" ry="22" fill="#f09a4e" transform="rotate(322,100,90)" opacity="0.8"/>
<!-- Flower center -->
<circle cx="100" cy="90" r="18" fill="url(#centerGrad)"/>
<circle cx="100" cy="90" r="12" fill="#d4941a" opacity="0.5"/>
<circle cx="100" cy="90" r="6" fill="#b87a10" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
<g>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(0,30,30)" opacity="0.85"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(72,30,30)" opacity="0.85"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(144,30,30)" opacity="0.85"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#f09a4e" transform="rotate(216,30,30)" opacity="0.85"/>
<ellipse cx="30" cy="12" rx="8" ry="14" fill="#e87a2e" transform="rotate(288,30,30)" opacity="0.85"/>
<circle cx="30" cy="30" r="6" fill="#f5c842"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 721 B

+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>RadioStation</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
+102
View File
@@ -0,0 +1,102 @@
// ===== Global style entry point ======
// All SCSS partials are imported here. To change the color scheme,
// only edit _variables.scss — every component inherits from it.
@use './styles/variables' as *;
@use './styles/mixins' as *;
@use './styles/themes';
// ===== Base reset and global styles =====
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
scroll-behavior: smooth;
}
body {
font-family: var(--font-primary);
color: var(--color-dark);
background-color: var(--color-cream);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
color: var(--color-primary);
line-height: 1.2;
margin-bottom: $spacing-md;
}
h1 { font-size: 2.8rem; }
h2 { font-size: 2.2rem; }
h3 { font-size: 1.6rem; }
a {
color: var(--color-accent);
text-decoration: none;
transition: color $transition-fast;
&:hover {
color: var(--color-accent-dark);
text-decoration: underline;
}
}
img, svg {
max-width: 100%;
height: auto;
display: block;
}
// Utility classes
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 $spacing-lg;
}
.text-center { text-align: center; }
.text-accent { color: var(--color-accent); }
.section-intro {
max-width: 700px;
margin: 0 auto $spacing-2xl;
text-align: center;
font-size: 1.1rem;
color: var(--color-medium);
}
.btn {
@include button-style($accent-orange, $neutral-white);
display: inline-flex;
align-items: center;
gap: $spacing-xs;
text-decoration: none;
font-family: var(--font-primary);
letter-spacing: 0.02em;
}
.btn-secondary {
@include button-style($primary-blue, $neutral-white, $primary-blue-dark);
}
.btn-outline {
background: transparent;
border: 2px solid $accent-orange;
color: $accent-orange;
&:hover {
background: $accent-orange;
color: $neutral-white;
}
}
+106
View File
@@ -0,0 +1,106 @@
@use './variables' as *;
// ========== Reusable SCSS mixins for layout and styling ==========
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
@mixin gradient-text($from: $accent-orange, $to: $accent-orange-light) {
background: linear-gradient(135deg, $from, $to);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@mixin card-style {
background: $neutral-white;
border-radius: $radius-lg;
box-shadow: $shadow-md;
transition: transform $transition-normal, box-shadow $transition-normal;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
}
@mixin section-padding($top: $spacing-3xl, $bottom: $spacing-3xl) {
padding-top: $top;
padding-bottom: $bottom;
}
@mixin responsive($breakpoint) {
@if $breakpoint == sm { @media (max-width: ($bp-sm - 1px)) { @content; } }
@if $breakpoint == md { @media (max-width: ($bp-md - 1px)) { @content; } }
@if $breakpoint == lg { @media (max-width: ($bp-lg - 1px)) { @content; } }
@if $breakpoint == xl { @media (max-width: ($bp-xl - 1px)) { @content; } }
}
@mixin button-style($bg: $accent-orange, $text: $neutral-white, $hover-bg: $accent-orange-dark) {
background: $bg;
color: $text;
border: none;
border-radius: $radius-md;
padding: $spacing-sm $spacing-lg;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background $transition-fast, transform $transition-fast;
&:hover {
background: $hover-bg;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
@mixin mountain-divider($color: $primary-blue, $height: 80px) {
content: '';
display: block;
width: 100%;
height: $height;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 320'%3E%3Cpath fill='%231a3a5c' fill-opacity='1' d='M0,224L48,213.3C96,203,192,181,288,181.3C384,181,480,203,576,218.7C672,235,768,245,864,234.7C960,224,1056,192,1152,186.7C1248,181,1344,203,1392,213.3L1440,224L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z'%3E%3C/path%3E%3C/svg%3E") no-repeat center bottom;
background-size: cover;
margin-top: -$height;
}
@mixin flowery-border($color: $accent-orange) {
position: relative;
&::after {
content: '';
display: block;
width: 60px;
height: 60px;
margin: $spacing-md auto 0;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='12' fill='%23e87a2e'/%3E%3Ccircle cx='35' cy='50' r='12' fill='%23e87a2e'/%3E%3Ccircle cx='65' cy='50' r='12' fill='%23e87a2e'/%3E%3Ccircle cx='40' cy='62' r='12' fill='%23e87a2e'/%3E%3Ccircle cx='60' cy='62' r='12' fill='%23e87a2e'/%3E%3Ccircle cx='50' cy='50' r='8' fill='%23f0a86a'/%3E%3C/svg%3E") no-repeat center;
background-size: contain;
opacity: 0.6;
}
}
@mixin fade-in {
animation: fadeIn 0.6s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
+50
View File
@@ -0,0 +1,50 @@
@use './variables' as *;
// ========== CSS Custom Properties (exposed to the browser) ==========
// These map SCSS variables to CSS custom properties so Angular components
// can consume them via var() at runtime.
:root {
// Primary
--color-primary: #{$primary-blue};
--color-primary-light: #{$primary-blue-light};
--color-primary-dark: #{$primary-blue-dark};
--color-primary-muted: #{$primary-blue-muted};
// Accent
--color-accent: #{$accent-orange};
--color-accent-light: #{$accent-orange-light};
--color-accent-dark: #{$accent-orange-dark};
--color-accent-muted: #{$accent-orange-muted};
// Neutrals
--color-white: #{$neutral-white};
--color-cream: #{$neutral-cream};
--color-light: #{$neutral-light};
--color-medium: #{$neutral-medium};
--color-dark: #{$neutral-dark};
--color-black: #{$neutral-black};
// Semantic
--color-success: #{$success-green};
--color-danger: #{$danger-red};
--color-info: #{$info-blue};
// Typography
--font-primary: #{$font-primary};
--font-heading: #{$font-heading};
--font-mono: #{$font-mono};
// Radii
--radius-sm: #{$radius-sm};
--radius-md: #{$radius-md};
--radius-lg: #{$radius-lg};
--radius-xl: #{$radius-xl};
--radius-full: #{$radius-full};
// Shadows
--shadow-sm: #{$shadow-sm};
--shadow-md: #{$shadow-md};
--shadow-lg: #{$shadow-lg};
--shadow-xl: #{$shadow-xl};
}
+78
View File
@@ -0,0 +1,78 @@
// =============================================================================
// Color Palette — edit these to change the entire site's color scheme
// =============================================================================
// Primary palette
$primary-blue: #1a3a5c;
$primary-blue-light: #2a5a8c;
$primary-blue-dark: #0e2440;
$primary-blue-muted: #3a6a9c;
// Accent palette
$accent-orange: #e87a2e;
$accent-orange-light: #f09a4e;
$accent-orange-dark: #c85a1e;
$accent-orange-muted: #f0a86a;
// Neutrals
$neutral-white: #ffffff;
$neutral-cream: #faf6f0;
$neutral-light: #f0ebe3;
$neutral-medium: #8a8580;
$neutral-dark: #3a3632;
$neutral-black: #1a1816;
// Semantic
$success-green: #4a9e4f;
$danger-red: #c83030;
$info-blue: #3a8abf;
// Gradients
$sky-gradient: linear-gradient(135deg, $primary-blue-dark 0%, $primary-blue 40%, $primary-blue-light 100%);
$sunset-gradient: linear-gradient(135deg, $accent-orange-dark 0%, $accent-orange 50%, $accent-orange-light 100%);
$mixed-gradient: linear-gradient(135deg, $primary-blue-dark 0%, $primary-blue 50%, $accent-orange 100%);
// Typography
$font-primary: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
$font-heading: 'Georgia', 'Times New Roman', serif;
$font-mono: 'Courier New', Courier, monospace;
// Spacing
$spacing-xs: 0.25rem;
$spacing-sm: 0.5rem;
$spacing-md: 1rem;
$spacing-lg: 2rem;
$spacing-xl: 3rem;
$spacing-2xl: 4rem;
$spacing-3xl: 6rem;
// Border radius
$radius-sm: 4px;
$radius-md: 8px;
$radius-lg: 16px;
$radius-xl: 24px;
$radius-full: 50%;
// Shadows
$shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
$shadow-md: 0 4px 12px rgba(0,0,0,0.15);
$shadow-lg: 0 8px 30px rgba(0,0,0,0.18);
$shadow-xl: 0 12px 40px rgba(0,0,0,0.22);
// Breakpoints
$bp-sm: 576px;
$bp-md: 768px;
$bp-lg: 992px;
$bp-xl: 1200px;
// Transitions
$transition-fast: 150ms ease;
$transition-normal: 300ms ease;
$transition-slow: 500ms ease;
// Z-index scale
$z-dropdown: 100;
$z-sticky: 200;
$z-overlay: 300;
$z-modal: 400;
$z-toast: 500;