Radio show website: Full Astro build with 194 episodes imported
Complete website for The Computer Guru Show (radio.azcomputerguru.com): - Astro 6.0.4 static site with React islands - 194 episodes imported from gurushow.com RSS feed - Dark/light mode HSL design system - Persistent audio player with session persistence - Episode archive with search and season filtering - Home page with animated hero, stats, latest episodes - All pages: About, Subscribe, Community, Live, Contact, Blog, 404 - Podcast RSS feed with iTunes namespace - Session log updated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
56
projects/radio-show/website/src/pages/404.astro
Normal file
56
projects/radio-show/website/src/pages/404.astro
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Page Not Found" description="The page you are looking for could not be found.">
|
||||
<section class="not-found">
|
||||
<div class="container">
|
||||
<div class="not-found__content fade-in">
|
||||
<span class="not-found__code">404</span>
|
||||
<h1 class="not-found__title">Page Not Found</h1>
|
||||
<p class="not-found__desc">
|
||||
The page you are looking for does not exist or has been moved.
|
||||
</p>
|
||||
<a href="/" class="btn btn--primary">Go Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.not-found {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
}
|
||||
.not-found__content {
|
||||
max-width: 480px;
|
||||
}
|
||||
.not-found__code {
|
||||
display: block;
|
||||
font-size: 8rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.not-found__title {
|
||||
font-size: var(--text-2xl);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.not-found__desc {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.not-found__code {
|
||||
font-size: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
298
projects/radio-show/website/src/pages/about.astro
Normal file
298
projects/radio-show/website/src/pages/about.astro
Normal file
@@ -0,0 +1,298 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="About" description="Learn about The Computer Guru Show and host Mike Swanson.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">About</span>
|
||||
<h1 class="hero__title fade-in">About the Show</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
Technology: Fun and Simple. A podcast dedicated to breaking down the biggest stories in tech for everyone.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Host Bio -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="host-card card fade-in">
|
||||
<div class="host-card__icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2a5 5 0 0 1 5 5v3a5 5 0 0 1-10 0V7a5 5 0 0 1 5-5z"/>
|
||||
<path d="M17 10v1a5 5 0 0 1-10 0v-1"/>
|
||||
<line x1="12" y1="19" x2="12" y2="22"/>
|
||||
<line x1="8" y1="22" x2="16" y2="22"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="host-card__name">Mike Swanson</h2>
|
||||
<p class="host-card__title">The Computer Guru</p>
|
||||
<p class="host-card__bio">
|
||||
Technology professional based in Tucson, Arizona. Owner of Arizona Computer Guru,
|
||||
providing IT services to businesses and individuals throughout Southern Arizona.
|
||||
Host of The Computer Guru Show since 2014, bringing technology news, analysis,
|
||||
and how-tos to listeners in a way that is fun, accessible, and practical.
|
||||
</p>
|
||||
<div class="host-card__meta">
|
||||
<span class="host-card__location">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
Tucson, Arizona
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Show History Timeline -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">Show History</h2>
|
||||
<div class="timeline">
|
||||
<div class="timeline__item fade-in">
|
||||
<div class="timeline__marker"></div>
|
||||
<div class="timeline__content card">
|
||||
<span class="timeline__year">2014</span>
|
||||
<h3>Show Launches</h3>
|
||||
<p>The Computer Guru Show debuts, bringing technology discussion to the airwaves with a mission to make tech fun and simple for everyone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline__item fade-in">
|
||||
<div class="timeline__marker"></div>
|
||||
<div class="timeline__content card">
|
||||
<span class="timeline__year">2014 - 2018</span>
|
||||
<h3>10 Seasons, 194 Episodes</h3>
|
||||
<p>Over four years the show covers the biggest stories in technology -- net neutrality battles, the rise of streaming, smartphone evolution, hacking scandals, self-driving cars, and the early days of AI. Every episode is part of the archive.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline__item fade-in">
|
||||
<div class="timeline__marker"></div>
|
||||
<div class="timeline__content card">
|
||||
<span class="timeline__year">2018</span>
|
||||
<h3>Hiatus</h3>
|
||||
<p>The show goes on hiatus after Season 10 to recharge and rethink the format for a rapidly changing tech landscape.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline__item fade-in">
|
||||
<div class="timeline__marker timeline__marker--active"></div>
|
||||
<div class="timeline__content card">
|
||||
<span class="timeline__year">2026</span>
|
||||
<h3>The Return</h3>
|
||||
<p>The Computer Guru Show returns with a fresh perspective, tackling artificial intelligence, privacy, space technology, and everything shaping our digital future.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- What We Cover -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">What We Cover</h2>
|
||||
<div class="grid grid--3">
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
||||
</div>
|
||||
<h3>Tech News</h3>
|
||||
<p>The stories that matter, explained without the jargon.</p>
|
||||
</div>
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
||||
</div>
|
||||
<h3>Security and Privacy</h3>
|
||||
<p>Protecting your data, your devices, and your digital life.</p>
|
||||
</div>
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
</div>
|
||||
<h3>Artificial Intelligence</h3>
|
||||
<p>Understanding AI tools, trends, and their impact on daily life.</p>
|
||||
</div>
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||
</div>
|
||||
<h3>Space Technology</h3>
|
||||
<p>The new space race and the technology making it possible.</p>
|
||||
</div>
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||
</div>
|
||||
<h3>Gadgets</h3>
|
||||
<p>Hands-on coverage of the devices worth your attention.</p>
|
||||
</div>
|
||||
<div class="card topic-card fade-in">
|
||||
<div class="topic-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||
</div>
|
||||
<h3>How-Tos</h3>
|
||||
<p>Practical guides to get more from your technology.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact CTA -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="cta-card card fade-in">
|
||||
<h2>Get in Touch</h2>
|
||||
<p>Have a question, topic suggestion, or just want to say hello? We would love to hear from you.</p>
|
||||
<a href="/contact" class="btn btn--primary">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Host Card */
|
||||
.host-card {
|
||||
text-align: center;
|
||||
max-width: 640px;
|
||||
margin-inline: auto;
|
||||
padding: var(--space-12) var(--space-8);
|
||||
}
|
||||
.host-card__icon {
|
||||
color: var(--color-accent);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.host-card__name {
|
||||
font-size: var(--text-3xl);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.host-card__title {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.host-card__bio {
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.host-card__meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.host-card__location {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
max-width: 700px;
|
||||
margin-inline: auto;
|
||||
padding-left: var(--space-8);
|
||||
}
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
.timeline__item {
|
||||
position: relative;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
.timeline__item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.timeline__marker {
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--space-8) + 8px);
|
||||
top: var(--space-6);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 2px solid var(--color-border);
|
||||
z-index: 1;
|
||||
}
|
||||
.timeline__marker--active {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 12px var(--color-accent-glow);
|
||||
}
|
||||
.timeline__content h3 {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.timeline__content p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.timeline__year {
|
||||
display: inline-block;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 700;
|
||||
color: var(--color-accent);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* Topic Cards */
|
||||
.topic-card {
|
||||
text-align: center;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
}
|
||||
.topic-card__icon {
|
||||
color: var(--color-accent);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.topic-card h3 {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.topic-card p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* CTA Card */
|
||||
.cta-card {
|
||||
text-align: center;
|
||||
padding: var(--space-12) var(--space-8);
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.cta-card h2 {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.cta-card p {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
</style>
|
||||
186
projects/radio-show/website/src/pages/blog/[...slug].astro
Normal file
186
projects/radio-show/website/src/pages/blog/[...slug].astro
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog');
|
||||
return posts.map(post => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<BaseLayout title={post.data.title} description={post.data.description}>
|
||||
<article class="article section">
|
||||
<div class="container--narrow">
|
||||
<!-- Back Link -->
|
||||
<a href="/blog" class="back-link fade-in">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
|
||||
Back to Blog
|
||||
</a>
|
||||
|
||||
<!-- Article Header -->
|
||||
<header class="article__header fade-in">
|
||||
<div class="article__meta">
|
||||
<time datetime={post.data.pubDate.toISOString()}>
|
||||
{post.data.pubDate.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</time>
|
||||
<span class="article__author">by {post.data.author}</span>
|
||||
</div>
|
||||
<h1 class="article__title">{post.data.title}</h1>
|
||||
{post.data.tags.length > 0 && (
|
||||
<div class="article__tags">
|
||||
{post.data.tags.map((tag: string) => (
|
||||
<span class="badge">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<!-- Article Content -->
|
||||
<div class="article__content prose fade-in">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
<!-- Back Link Bottom -->
|
||||
<div class="article__footer fade-in">
|
||||
<a href="/blog" class="btn btn--ghost">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
|
||||
Back to Blog
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
.back-link:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.article__header {
|
||||
margin-bottom: var(--space-12);
|
||||
padding-bottom: var(--space-8);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.article__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.article__meta time {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.article__author {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.article__title {
|
||||
font-size: var(--text-4xl);
|
||||
line-height: 1.15;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.article__tags {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Prose styles for rendered markdown */
|
||||
.prose {
|
||||
line-height: 1.8;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.prose :global(h2) {
|
||||
font-size: var(--text-2xl);
|
||||
color: var(--color-text-primary);
|
||||
margin-top: var(--space-12);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.prose :global(h3) {
|
||||
font-size: var(--text-xl);
|
||||
color: var(--color-text-primary);
|
||||
margin-top: var(--space-8);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.prose :global(p) {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.prose :global(ul),
|
||||
.prose :global(ol) {
|
||||
margin-bottom: var(--space-6);
|
||||
padding-left: var(--space-6);
|
||||
}
|
||||
.prose :global(li) {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.prose :global(strong) {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.prose :global(a) {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.prose :global(blockquote) {
|
||||
border-left: 3px solid var(--color-accent);
|
||||
padding-left: var(--space-6);
|
||||
margin-block: var(--space-6);
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
.prose :global(code) {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.9em;
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.prose :global(pre) {
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: var(--space-6);
|
||||
border-radius: var(--radius-md);
|
||||
overflow-x: auto;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.prose :global(pre code) {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
.prose :global(hr) {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-block: var(--space-8);
|
||||
}
|
||||
|
||||
.article__footer {
|
||||
margin-top: var(--space-12);
|
||||
padding-top: var(--space-8);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.article__title {
|
||||
font-size: var(--text-3xl);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
134
projects/radio-show/website/src/pages/blog/index.astro
Normal file
134
projects/radio-show/website/src/pages/blog/index.astro
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const allPosts = await getCollection('blog', ({ data }) => !data.draft);
|
||||
const posts = allPosts.sort((a, b) =>
|
||||
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
|
||||
);
|
||||
---
|
||||
|
||||
<BaseLayout title="Blog" description="Articles, guides, and updates from The Computer Guru Show.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">Blog</span>
|
||||
<h1 class="hero__title fade-in">Blog</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
{posts.length} {posts.length === 1 ? 'post' : 'posts'} -- articles, updates, and guides.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Posts Grid -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{posts.length === 0 ? (
|
||||
<div class="empty-state fade-in">
|
||||
<p>No blog posts yet. Check back soon.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div class="grid grid--2">
|
||||
{posts.map((post) => (
|
||||
<a href={`/blog/${post.id}`} class="card post-card fade-in">
|
||||
<div class="post-card__meta">
|
||||
<time datetime={post.data.pubDate.toISOString()}>
|
||||
{post.data.pubDate.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</time>
|
||||
</div>
|
||||
<h2 class="post-card__title">{post.data.title}</h2>
|
||||
<p class="post-card__desc">{post.data.description}</p>
|
||||
<div class="post-card__footer">
|
||||
{post.data.tags.length > 0 && (
|
||||
<div class="post-card__tags">
|
||||
{post.data.tags.map((tag: string) => (
|
||||
<span class="badge">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<span class="post-card__link">Read More →</span>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--space-16);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
/* Post Card */
|
||||
.post-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--color-text-primary);
|
||||
padding: var(--space-8);
|
||||
}
|
||||
.post-card:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.post-card__meta {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.post-card__meta time {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.post-card__title {
|
||||
font-size: var(--text-xl);
|
||||
margin-bottom: var(--space-3);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.post-card__desc {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.7;
|
||||
flex: 1;
|
||||
}
|
||||
.post-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--space-6);
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.post-card__tags {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.post-card__link {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
</style>
|
||||
187
projects/radio-show/website/src/pages/community.astro
Normal file
187
projects/radio-show/website/src/pages/community.astro
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Community" description="Join The Computer Guru Show community on Discord, Discourse, and more.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">Community</span>
|
||||
<h1 class="hero__title fade-in">Join the Community</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
Connect with fellow tech enthusiasts. Ask questions, share discoveries, and be part of the conversation.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Discord -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="community-card card fade-in">
|
||||
<div class="community-card__icon community-card__icon--discord">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.369a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.332-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.332-.946 2.418-2.157 2.418z"/></svg>
|
||||
</div>
|
||||
<div class="community-card__body">
|
||||
<h2>Discord Server</h2>
|
||||
<p>
|
||||
Chat with fellow tech enthusiasts in real-time. Ask questions, share links,
|
||||
debate the latest tech news, and hang out during live shows.
|
||||
</p>
|
||||
<a href="#" class="btn btn--primary community-card__action">Join Discord</a>
|
||||
</div>
|
||||
<!-- Placeholder for future WidgetBot embed -->
|
||||
<div class="discord-embed-placeholder" aria-hidden="true">
|
||||
<!-- WidgetBot embed will be added here when the Discord server is configured -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Forum -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="community-card card fade-in">
|
||||
<div class="community-card__icon community-card__icon--forum">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
</div>
|
||||
<div class="community-card__body">
|
||||
<h2>Discussion Forum</h2>
|
||||
<p>
|
||||
Deep-dive conversations on Discourse. Long-form discussions, tutorials,
|
||||
and community-driven content that goes beyond what we can cover on the show.
|
||||
</p>
|
||||
<a href="#" class="btn btn--primary community-card__action">Visit Forum</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Newsletter -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="newsletter-card card fade-in">
|
||||
<div class="community-card__icon community-card__icon--newsletter">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
||||
</div>
|
||||
<h2>Stay Updated</h2>
|
||||
<p>Get episode announcements, show notes, and exclusive content delivered to your inbox.</p>
|
||||
<form action="#" method="post" class="newsletter-form">
|
||||
<div class="newsletter-form__row">
|
||||
<label for="newsletter-email" class="sr-only">Email address</label>
|
||||
<input
|
||||
type="email"
|
||||
id="newsletter-email"
|
||||
name="email"
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
class="newsletter-input"
|
||||
autocomplete="email"
|
||||
/>
|
||||
<button type="submit" class="btn btn--primary">Subscribe</button>
|
||||
</div>
|
||||
<p class="newsletter-form__note">No spam. Unsubscribe anytime.</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 560px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Community Cards */
|
||||
.community-card {
|
||||
max-width: 700px;
|
||||
margin-inline: auto;
|
||||
padding: var(--space-8);
|
||||
text-align: center;
|
||||
}
|
||||
.community-card__icon {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.community-card__icon--discord {
|
||||
color: #5865F2;
|
||||
}
|
||||
.community-card__icon--forum {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.community-card__icon--newsletter {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
.community-card__body h2 {
|
||||
font-size: var(--text-2xl);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.community-card h2 {
|
||||
font-size: var(--text-2xl);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.community-card__body p,
|
||||
.community-card > p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.8;
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.community-card__action {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Newsletter */
|
||||
.newsletter-card {
|
||||
max-width: 700px;
|
||||
margin-inline: auto;
|
||||
padding: var(--space-8);
|
||||
text-align: center;
|
||||
}
|
||||
.newsletter-card p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
max-width: 480px;
|
||||
margin-inline: auto;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.newsletter-form__row {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
max-width: 420px;
|
||||
margin-inline: auto;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.newsletter-input {
|
||||
flex: 1;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.newsletter-input:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.newsletter-input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.newsletter-form__note {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
</style>
|
||||
233
projects/radio-show/website/src/pages/contact.astro
Normal file
233
projects/radio-show/website/src/pages/contact.astro
Normal file
@@ -0,0 +1,233 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Contact" description="Get in touch with The Computer Guru Show for questions, sponsorships, or guest appearances.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">Contact</span>
|
||||
<h1 class="hero__title fade-in">Get in Touch</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
Questions, feedback, or business inquiries -- we would love to hear from you.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Form -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="form-card card fade-in">
|
||||
<form action="#" method="post" class="contact-form">
|
||||
<div class="form-group">
|
||||
<label for="contact-name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="contact-name"
|
||||
name="name"
|
||||
required
|
||||
autocomplete="name"
|
||||
placeholder="Your name"
|
||||
class="form-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="contact-email"
|
||||
name="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
placeholder="your@email.com"
|
||||
class="form-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-subject">Subject</label>
|
||||
<select id="contact-subject" name="subject" required class="form-select">
|
||||
<option value="" disabled selected>Select a subject</option>
|
||||
<option value="general">General</option>
|
||||
<option value="sponsorship">Sponsorship</option>
|
||||
<option value="guest">Guest Appearance</option>
|
||||
<option value="technical">Technical Issue</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-message">Message</label>
|
||||
<textarea
|
||||
id="contact-message"
|
||||
name="message"
|
||||
required
|
||||
rows="6"
|
||||
placeholder="Your message..."
|
||||
class="form-textarea"
|
||||
></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn--primary form-submit">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sponsorship -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="sponsor-card card fade-in">
|
||||
<div class="sponsor-card__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
||||
</div>
|
||||
<h2>Sponsor the Show</h2>
|
||||
<p>
|
||||
Reach a dedicated audience of tech enthusiasts. The Computer Guru Show offers sponsorship
|
||||
opportunities including pre-roll, mid-roll, and post-roll placements, as well as dedicated
|
||||
segments and custom integrations.
|
||||
</p>
|
||||
<p class="sponsor-card__note">
|
||||
For sponsorship inquiries, select "Sponsorship" in the contact form above or reach out directly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Social Links -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">Find Us Online</h2>
|
||||
<div class="grid grid--3">
|
||||
<a href="#" class="card social-card fade-in">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
||||
<span>X / Twitter</span>
|
||||
</a>
|
||||
<a href="#" class="card social-card fade-in">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.369a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.332-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.332-.946 2.418-2.157 2.418z"/></svg>
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<a href="/feed.xml" class="card social-card fade-in">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg>
|
||||
<span>RSS Feed</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Form Card */
|
||||
.form-card {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--space-2);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-sm);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.form-input::placeholder,
|
||||
.form-textarea::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23777' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: var(--space-8);
|
||||
}
|
||||
.form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
.form-submit {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
}
|
||||
|
||||
/* Sponsor Card */
|
||||
.sponsor-card {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
.sponsor-card__icon {
|
||||
color: var(--color-warning);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.sponsor-card h2 {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.sponsor-card p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.sponsor-card__note {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Social Cards */
|
||||
.social-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-8) var(--space-6);
|
||||
text-align: center;
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.social-card:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.social-card span {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
458
projects/radio-show/website/src/pages/episodes/[...slug].astro
Normal file
458
projects/radio-show/website/src/pages/episodes/[...slug].astro
Normal file
@@ -0,0 +1,458 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import EpisodeCard from '../../components/episodes/EpisodeCard.astro';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const episodes = await getCollection('episodes');
|
||||
return episodes.map((episode) => ({
|
||||
params: { slug: episode.id },
|
||||
props: { episode },
|
||||
}));
|
||||
}
|
||||
|
||||
interface Props {
|
||||
episode: CollectionEntry<'episodes'>;
|
||||
}
|
||||
|
||||
const { episode } = Astro.props;
|
||||
const { title, season, episode: episodeNum, pubDate, duration, audioUrl, tags, chapters, originalUrl } = episode.data;
|
||||
|
||||
const { Content } = await render(episode);
|
||||
|
||||
const formattedDate = new Date(pubDate).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
const seasonLabel = season === 0 ? 'Special' : `Season ${season}`;
|
||||
const episodeLabel = season === 0 ? 'Special' : `S${season} E${String(episodeNum).padStart(2, '0')}`;
|
||||
|
||||
const allEpisodes = await getCollection('episodes');
|
||||
const relatedEpisodes = allEpisodes
|
||||
.filter((ep) => ep.data.season === season && ep.id !== episode.id)
|
||||
.sort((a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime())
|
||||
.slice(0, 4);
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={`${episodeLabel} of The Computer Guru Show`}>
|
||||
<article class="episode-detail">
|
||||
<div class="container container--narrow">
|
||||
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol class="breadcrumb__list">
|
||||
<li class="breadcrumb__item">
|
||||
<a href="/episodes">Episodes</a>
|
||||
</li>
|
||||
<li class="breadcrumb__separator" aria-hidden="true">/</li>
|
||||
<li class="breadcrumb__item">
|
||||
<a href={`/episodes?season=${season}`}>{seasonLabel}</a>
|
||||
</li>
|
||||
<li class="breadcrumb__separator" aria-hidden="true">/</li>
|
||||
<li class="breadcrumb__item breadcrumb__item--current" aria-current="page">
|
||||
{title}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<header class="episode-detail__header">
|
||||
<span class="badge">{episodeLabel}</span>
|
||||
<h1 class="episode-detail__title">{title}</h1>
|
||||
|
||||
<div class="episode-detail__meta">
|
||||
<time datetime={pubDate.toISOString()} class="episode-detail__date">
|
||||
{formattedDate}
|
||||
</time>
|
||||
<span class="episode-detail__divider" aria-hidden="true">|</span>
|
||||
<span class="episode-detail__duration">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
{duration}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="episode-detail__player">
|
||||
<audio
|
||||
controls
|
||||
preload="metadata"
|
||||
class="episode-detail__audio"
|
||||
src={audioUrl}
|
||||
>
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
<button
|
||||
class="btn btn--ghost episode-detail__play-persistent"
|
||||
data-audio-url={audioUrl}
|
||||
data-episode-title={title}
|
||||
data-season={String(season)}
|
||||
data-episode-num={String(episodeNum)}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="5 3 19 12 5 21 5 3" fill="currentColor"></polygon>
|
||||
</svg>
|
||||
Play in Persistent Player
|
||||
</button>
|
||||
</section>
|
||||
|
||||
{chapters && chapters.length > 0 && (
|
||||
<section class="episode-detail__chapters">
|
||||
<h2 class="episode-detail__section-title">Chapters</h2>
|
||||
<ol class="chapters-list">
|
||||
{chapters.map((chapter) => (
|
||||
<li class="chapters-list__item">
|
||||
<span class="chapters-list__time">{chapter.time}</span>
|
||||
<span class="chapters-list__title">{chapter.title}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section class="episode-detail__content">
|
||||
<h2 class="episode-detail__section-title">Show Notes</h2>
|
||||
<div class="prose">
|
||||
<Content />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<section class="episode-detail__tags">
|
||||
<h2 class="episode-detail__section-title">Tags</h2>
|
||||
<div class="tag-list">
|
||||
{tags.map((tag: string) => (
|
||||
<span class="badge">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section class="episode-detail__platforms">
|
||||
<h2 class="episode-detail__section-title">Listen On</h2>
|
||||
<div class="platform-links">
|
||||
<a
|
||||
href={audioUrl}
|
||||
class="btn btn--ghost"
|
||||
download
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Download MP3
|
||||
</a>
|
||||
{originalUrl && (
|
||||
<a
|
||||
href={originalUrl}
|
||||
class="btn btn--ghost"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
Original Post
|
||||
</a>
|
||||
)}
|
||||
<a
|
||||
href="/feed.xml"
|
||||
class="btn btn--ghost"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
||||
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
||||
<circle cx="5" cy="19" r="1"></circle>
|
||||
</svg>
|
||||
RSS Feed
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{relatedEpisodes.length > 0 && (
|
||||
<section class="episode-detail__related section">
|
||||
<div class="container">
|
||||
<h2 class="section__title">More from {seasonLabel}</h2>
|
||||
<div class="grid grid--3">
|
||||
{relatedEpisodes.map((ep) => (
|
||||
<EpisodeCard episode={ep} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</article>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.breadcrumb {
|
||||
margin-bottom: var(--space-8);
|
||||
padding-top: var(--space-6);
|
||||
}
|
||||
|
||||
.breadcrumb__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.breadcrumb__item a {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.breadcrumb__item a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.breadcrumb__item--current {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 300px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.breadcrumb__separator {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.episode-detail__header {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.episode-detail__header .badge {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.episode-detail__title {
|
||||
font-size: var(--text-3xl);
|
||||
margin-top: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.episode-detail__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.episode-detail__duration {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.episode-detail__divider {
|
||||
color: var(--color-border);
|
||||
}
|
||||
|
||||
.episode-detail__player {
|
||||
margin-bottom: var(--space-8);
|
||||
padding: var(--space-6);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.episode-detail__audio {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.episode-detail__play-persistent {
|
||||
margin-top: var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.episode-detail__audio::-webkit-media-controls-panel {
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.episode-detail__section-title {
|
||||
font-size: var(--text-xl);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.episode-detail__chapters {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.chapters-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chapters-list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.chapters-list__item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.chapters-list__item:hover {
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.chapters-list__time {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-accent);
|
||||
min-width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chapters-list__title {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.episode-detail__content {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.prose {
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.8;
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.prose :global(p) {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.prose :global(a) {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.prose :global(a:hover) {
|
||||
color: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.prose :global(ul),
|
||||
.prose :global(ol) {
|
||||
margin-bottom: var(--space-4);
|
||||
padding-left: var(--space-6);
|
||||
}
|
||||
|
||||
.prose :global(li) {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.prose :global(strong) {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.prose :global(h2),
|
||||
.prose :global(h3) {
|
||||
margin-top: var(--space-8);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.episode-detail__tags {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.episode-detail__platforms {
|
||||
margin-bottom: var(--space-8);
|
||||
padding: var(--space-6);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.platform-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.episode-detail__related {
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.episode-detail__title {
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.episode-detail__meta {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.episode-detail__divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.platform-links {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.platform-links .btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const btn = document.querySelector<HTMLButtonElement>('.episode-detail__play-persistent');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const audioUrl = btn.dataset.audioUrl;
|
||||
const title = btn.dataset.episodeTitle;
|
||||
const season = parseInt(btn.dataset.season ?? '0', 10);
|
||||
const episode = parseInt(btn.dataset.episodeNum ?? '0', 10);
|
||||
|
||||
if (audioUrl) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('play-episode', {
|
||||
detail: { audioUrl, title, season, episode },
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
250
projects/radio-show/website/src/pages/episodes/index.astro
Normal file
250
projects/radio-show/website/src/pages/episodes/index.astro
Normal file
@@ -0,0 +1,250 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import EpisodeCard from '../../components/episodes/EpisodeCard.astro';
|
||||
import SeasonFilter from '../../components/episodes/SeasonFilter.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const allEpisodes = await getCollection('episodes');
|
||||
|
||||
const sortedEpisodes = allEpisodes.sort((a, b) =>
|
||||
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
|
||||
);
|
||||
|
||||
const seasonMap = new Map<number, number>();
|
||||
for (const ep of sortedEpisodes) {
|
||||
const s = ep.data.season;
|
||||
seasonMap.set(s, (seasonMap.get(s) || 0) + 1);
|
||||
}
|
||||
|
||||
const seasonCounts = Array.from(seasonMap.entries())
|
||||
.filter(([season]) => season > 0)
|
||||
.sort((a, b) => b[0] - a[0])
|
||||
.map(([season, count]) => ({
|
||||
season,
|
||||
count,
|
||||
label: `Season ${season}`,
|
||||
}));
|
||||
|
||||
const specialCount = seasonMap.get(0) || 0;
|
||||
if (specialCount > 0) {
|
||||
seasonCounts.push({ season: 0, count: specialCount, label: 'Specials' });
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout title="Episodes" description="Browse all episodes of The Computer Guru Show">
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="episodes-header">
|
||||
<h1 class="episodes-header__title">Episodes</h1>
|
||||
<span class="episodes-header__count">{sortedEpisodes.length} episodes</span>
|
||||
</div>
|
||||
|
||||
<div class="episodes-search">
|
||||
<div class="episodes-search__input-wrap">
|
||||
<svg class="episodes-search__icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
id="episode-search"
|
||||
class="episodes-search__input"
|
||||
placeholder="Search episodes..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="search-clear"
|
||||
class="episodes-search__clear"
|
||||
aria-label="Clear search"
|
||||
hidden
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SeasonFilter seasonCounts={seasonCounts} totalCount={sortedEpisodes.length} />
|
||||
|
||||
<p id="results-count" class="results-count" hidden></p>
|
||||
<p id="no-results" class="no-results" hidden>
|
||||
No episodes match your search. Try different keywords or clear the filter.
|
||||
</p>
|
||||
|
||||
<div class="grid grid--3" id="episodes-grid">
|
||||
{sortedEpisodes.map((episode) => (
|
||||
<EpisodeCard episode={episode} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
const searchInput = document.getElementById('episode-search');
|
||||
const clearBtn = document.getElementById('search-clear');
|
||||
const grid = document.getElementById('episodes-grid');
|
||||
const cards = Array.from(grid.querySelectorAll('.episode-card'));
|
||||
const filterTabs = document.querySelectorAll('.season-filter__tab');
|
||||
const resultsCount = document.getElementById('results-count');
|
||||
const noResults = document.getElementById('no-results');
|
||||
|
||||
let activeSeason = 'all';
|
||||
let searchTerm = '';
|
||||
|
||||
function filterCards() {
|
||||
let visibleCount = 0;
|
||||
|
||||
cards.forEach(function (card) {
|
||||
const cardSeason = card.getAttribute('data-season');
|
||||
const cardTitle = card.getAttribute('data-title') || '';
|
||||
const cardDescription = card.getAttribute('data-description') || '';
|
||||
|
||||
const matchesSeason = activeSeason === 'all' || cardSeason === activeSeason;
|
||||
const matchesSearch = !searchTerm ||
|
||||
cardTitle.includes(searchTerm) ||
|
||||
cardDescription.includes(searchTerm);
|
||||
|
||||
if (matchesSeason && matchesSearch) {
|
||||
card.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
if (searchTerm || activeSeason !== 'all') {
|
||||
resultsCount.textContent = visibleCount + ' episode' + (visibleCount !== 1 ? 's' : '') + ' found';
|
||||
resultsCount.hidden = false;
|
||||
} else {
|
||||
resultsCount.hidden = true;
|
||||
}
|
||||
|
||||
noResults.hidden = visibleCount > 0;
|
||||
}
|
||||
|
||||
filterTabs.forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
filterTabs.forEach(function (t) {
|
||||
t.classList.remove('season-filter__tab--active');
|
||||
});
|
||||
tab.classList.add('season-filter__tab--active');
|
||||
activeSeason = tab.getAttribute('data-season');
|
||||
filterCards();
|
||||
});
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', function () {
|
||||
searchTerm = searchInput.value.toLowerCase().trim();
|
||||
clearBtn.hidden = searchTerm.length === 0;
|
||||
filterCards();
|
||||
});
|
||||
|
||||
clearBtn.addEventListener('click', function () {
|
||||
searchInput.value = '';
|
||||
searchTerm = '';
|
||||
clearBtn.hidden = true;
|
||||
filterCards();
|
||||
searchInput.focus();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.episodes-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.episodes-header__title {
|
||||
font-size: var(--text-3xl);
|
||||
}
|
||||
|
||||
.episodes-header__count {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.episodes-search {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.episodes-search__input-wrap {
|
||||
position: relative;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.episodes-search__icon {
|
||||
position: absolute;
|
||||
left: var(--space-4);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-text-muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.episodes-search__input {
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
padding-left: calc(var(--space-4) + 18px + var(--space-3));
|
||||
padding-right: calc(var(--space-4) + 16px + var(--space-3));
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--text-base);
|
||||
font-family: var(--font-sans);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.episodes-search__input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.episodes-search__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-glow);
|
||||
}
|
||||
|
||||
.episodes-search__clear {
|
||||
position: absolute;
|
||||
right: var(--space-3);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
padding: var(--space-1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.episodes-search__clear:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.results-count {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
padding: var(--space-16) var(--space-4);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
</style>
|
||||
37
projects/radio-show/website/src/pages/feed.xml.ts
Normal file
37
projects/radio-show/website/src/pages/feed.xml.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getCollection } from 'astro:content';
|
||||
import type { APIContext } from 'astro';
|
||||
|
||||
export async function GET(context: APIContext) {
|
||||
const episodes = await getCollection('episodes');
|
||||
const sortedEpisodes = episodes.sort(
|
||||
(a, b) =>
|
||||
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
|
||||
);
|
||||
|
||||
return rss({
|
||||
title: 'The Computer Guru Show',
|
||||
description:
|
||||
'Technology: Fun and Simple. Hosted by Mike Swanson from Tucson, Arizona.',
|
||||
site: context.site!.toString(),
|
||||
items: sortedEpisodes.map((ep) => ({
|
||||
title: ep.data.title,
|
||||
pubDate: ep.data.pubDate,
|
||||
description: ep.body?.substring(0, 300) || ep.data.title,
|
||||
link: `/episodes/${ep.id}`,
|
||||
customData: `<enclosure url="${ep.data.audioUrl}" length="${ep.data.audioSize}" type="audio/mpeg"/>
|
||||
<itunes:duration>${ep.data.duration}</itunes:duration>
|
||||
<itunes:episode>${ep.data.episode}</itunes:episode>
|
||||
<itunes:season>${ep.data.season}</itunes:season>`,
|
||||
})),
|
||||
customData: `<language>en-us</language>
|
||||
<itunes:author>Mike Swanson</itunes:author>
|
||||
<itunes:category text="Technology"/>
|
||||
<itunes:image href="https://radio.azcomputerguru.com/og-image.jpg"/>
|
||||
<itunes:explicit>false</itunes:explicit>
|
||||
<itunes:type>episodic</itunes:type>`,
|
||||
xmlns: {
|
||||
itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
||||
},
|
||||
});
|
||||
}
|
||||
20
projects/radio-show/website/src/pages/index.astro
Normal file
20
projects/radio-show/website/src/pages/index.astro
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import HeroSection from '../components/home/HeroSection.astro';
|
||||
import LatestEpisodes from '../components/home/LatestEpisodes.astro';
|
||||
import FeaturedClassics from '../components/home/FeaturedClassics.astro';
|
||||
import StatsSection from '../components/home/StatsSection.astro';
|
||||
import BlogHighlights from '../components/home/BlogHighlights.astro';
|
||||
import AboutPreview from '../components/home/AboutPreview.astro';
|
||||
import SubscribeCTA from '../components/home/SubscribeCTA.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Home" description="The Computer Guru Show - Technology: Fun and Simple. Your source for making sense of the tech world, hosted by Mike Swanson from Tucson, Arizona.">
|
||||
<HeroSection />
|
||||
<LatestEpisodes />
|
||||
<FeaturedClassics />
|
||||
<StatsSection />
|
||||
<BlogHighlights />
|
||||
<AboutPreview />
|
||||
<SubscribeCTA />
|
||||
</BaseLayout>
|
||||
249
projects/radio-show/website/src/pages/live.astro
Normal file
249
projects/radio-show/website/src/pages/live.astro
Normal file
@@ -0,0 +1,249 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Live" description="Watch The Computer Guru Show live and interact in real-time.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">Live</span>
|
||||
<h1 class="hero__title fade-in">Live Show</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
The Computer Guru Show streams live. Check back for schedule details.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Status -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="status-card card fade-in">
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot status-dot--offline" aria-hidden="true"></span>
|
||||
<span class="status-label">Currently Off Air</span>
|
||||
</div>
|
||||
<p class="status-desc">
|
||||
The show is not currently live. Subscribe to get notified when we go live, or check the schedule below.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Schedule -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">Show Schedule</h2>
|
||||
<div class="schedule-card card fade-in">
|
||||
<div class="schedule-placeholder">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||||
<p>Schedule coming soon</p>
|
||||
<span class="schedule-note">Show times will be announced here once the schedule is finalized.</span>
|
||||
</div>
|
||||
<div class="schedule-table" aria-label="Schedule placeholder">
|
||||
<div class="schedule-row schedule-row--header">
|
||||
<span>Day</span>
|
||||
<span>Time (MST)</span>
|
||||
<span>Format</span>
|
||||
</div>
|
||||
<div class="schedule-row schedule-row--empty">
|
||||
<span colspan="3">To be announced</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- When Live -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">When We Are Live</h2>
|
||||
<div class="grid grid--2">
|
||||
<div class="card live-feature fade-in">
|
||||
<div class="live-feature__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
|
||||
</div>
|
||||
<h3>Live Stream</h3>
|
||||
<p>Watch the show in real-time with video and audio right here on the site.</p>
|
||||
<div class="live-feature__placeholder">
|
||||
<span>Player will appear here during live broadcasts</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card live-feature fade-in">
|
||||
<div class="live-feature__icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
</div>
|
||||
<h3>Live Chat</h3>
|
||||
<p>Chat with other listeners and interact with the host during the live show via Discord.</p>
|
||||
<div class="live-feature__placeholder">
|
||||
<span>Discord chat will be embedded here during live broadcasts</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Archive Reminder -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="archive-cta card fade-in">
|
||||
<h2>Catch Up on Past Episodes</h2>
|
||||
<p>Browse all 194 episodes from the complete archive while you wait for the next live show.</p>
|
||||
<a href="/episodes" class="btn btn--primary">Browse Episodes</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Status Card */
|
||||
.status-card {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.status-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.status-dot--offline {
|
||||
background: var(--color-text-muted);
|
||||
box-shadow: 0 0 6px hsl(220 6% 48% / 0.4);
|
||||
}
|
||||
.status-dot--live {
|
||||
background: var(--color-danger);
|
||||
box-shadow: 0 0 8px hsl(0 75% 55% / 0.5);
|
||||
animation: pulse-live 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-live {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
.status-label {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.status-desc {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* Schedule */
|
||||
.schedule-card {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
.schedule-placeholder {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.schedule-placeholder svg {
|
||||
margin-bottom: var(--space-3);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.schedule-placeholder p {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.schedule-note {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.schedule-table {
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding-top: var(--space-4);
|
||||
}
|
||||
.schedule-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.schedule-row--header {
|
||||
font-weight: 600;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.05em;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.schedule-row--empty {
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
|
||||
/* Live Features */
|
||||
.live-feature {
|
||||
text-align: center;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
}
|
||||
.live-feature__icon {
|
||||
color: var(--color-accent);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.live-feature h3 {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.live-feature p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.live-feature__placeholder {
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--space-8) var(--space-4);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* Archive CTA */
|
||||
.archive-cta {
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
padding: var(--space-12) var(--space-8);
|
||||
}
|
||||
.archive-cta h2 {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.archive-cta p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
</style>
|
||||
224
projects/radio-show/website/src/pages/subscribe.astro
Normal file
224
projects/radio-show/website/src/pages/subscribe.astro
Normal file
@@ -0,0 +1,224 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { platforms } from '../data/platforms';
|
||||
---
|
||||
|
||||
<BaseLayout title="Subscribe" description="Subscribe to The Computer Guru Show on your favorite podcast platform.">
|
||||
<!-- Hero -->
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<span class="badge fade-in">Subscribe</span>
|
||||
<h1 class="hero__title fade-in">Subscribe to The Computer Guru Show</h1>
|
||||
<p class="hero__subtitle fade-in">
|
||||
Never miss an episode. Choose your favorite platform.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Platform Grid -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="grid grid--3">
|
||||
{platforms.map((platform) => (
|
||||
<a href={platform.url} class="card platform-card fade-in" target={platform.url.startsWith('http') ? '_blank' : undefined} rel={platform.url.startsWith('http') ? 'noopener noreferrer' : undefined}>
|
||||
<div class="platform-card__icon" data-platform={platform.icon}>
|
||||
{platform.icon === 'apple' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
|
||||
)}
|
||||
{platform.icon === 'spotify' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>
|
||||
)}
|
||||
{platform.icon === 'google' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.372 0 0 5.373 0 12s5.372 12 12 12c6.627 0 12-5.373 12-12S18.627 0 12 0zm-.001 4.5a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5 7.5 7.5 0 0 1-7.5-7.5 7.5 7.5 0 0 1 7.5-7.5zm0 2a5.5 5.5 0 0 0-5.5 5.5 5.5 5.5 0 0 0 5.5 5.5 5.5 5.5 0 0 0 5.5-5.5 5.5 5.5 0 0 0-5.5-5.5zm0 2a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5A3.5 3.5 0 0 1 8.5 12a3.5 3.5 0 0 1 3.499-3.5z"/></svg>
|
||||
)}
|
||||
{platform.icon === 'overcast' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14l2-4 2 4"/><path d="M12 14l2-4 2 4"/></svg>
|
||||
)}
|
||||
{platform.icon === 'pocketcasts' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="M2 12h2"/><path d="M20 12h2"/></svg>
|
||||
)}
|
||||
{platform.icon === 'rss' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg>
|
||||
)}
|
||||
</div>
|
||||
<h3 class="platform-card__name">{platform.name}</h3>
|
||||
<span class="btn btn--ghost platform-card__btn">Subscribe</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- RSS Feed Section -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="rss-section fade-in">
|
||||
<h2 class="section__title">RSS Feed</h2>
|
||||
<p class="rss-section__desc">
|
||||
Prefer a dedicated podcast app or RSS reader? Copy the feed URL below and add it manually.
|
||||
RSS (Really Simple Syndication) lets you subscribe to content updates without needing
|
||||
a specific platform account. Most podcast apps have an "Add by URL" option where you
|
||||
can paste this link.
|
||||
</p>
|
||||
<div class="rss-input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="rss-url"
|
||||
value="https://radio.azcomputerguru.com/feed.xml"
|
||||
readonly
|
||||
class="rss-input"
|
||||
aria-label="RSS Feed URL"
|
||||
/>
|
||||
<button id="copy-rss" class="btn btn--primary" type="button">Copy</button>
|
||||
</div>
|
||||
<p id="copy-feedback" class="rss-feedback" aria-live="polite"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- What to Expect -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h2 class="section__title fade-in">What to Expect</h2>
|
||||
<div class="grid grid--3">
|
||||
<div class="card expect-card fade-in">
|
||||
<h3>New Episodes</h3>
|
||||
<p>Fresh episodes are on the way. Subscribe now so you are ready when they drop.</p>
|
||||
<span class="badge">Coming Soon</span>
|
||||
</div>
|
||||
<div class="card expect-card fade-in">
|
||||
<h3>Full Archive</h3>
|
||||
<p>All 194 episodes from 10 seasons of the original run are available right now.</p>
|
||||
<span class="badge">Available</span>
|
||||
</div>
|
||||
<div class="card expect-card fade-in">
|
||||
<h3>Show Notes</h3>
|
||||
<p>Every episode includes detailed show notes with links to the topics discussed.</p>
|
||||
<span class="badge">Available</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
document.getElementById('copy-rss').addEventListener('click', function() {
|
||||
var input = document.getElementById('rss-url');
|
||||
var feedback = document.getElementById('copy-feedback');
|
||||
navigator.clipboard.writeText(input.value).then(function() {
|
||||
feedback.textContent = 'Copied to clipboard.';
|
||||
feedback.className = 'rss-feedback rss-feedback--success';
|
||||
setTimeout(function() {
|
||||
feedback.textContent = '';
|
||||
feedback.className = 'rss-feedback';
|
||||
}, 3000);
|
||||
}).catch(function() {
|
||||
input.select();
|
||||
feedback.textContent = 'Press Ctrl+C to copy.';
|
||||
feedback.className = 'rss-feedback';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding-block: var(--space-16) var(--space-8);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-top: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.hero__subtitle {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Platform Cards */
|
||||
.platform-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
cursor: pointer;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.platform-card:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.platform-card__icon {
|
||||
color: var(--color-accent);
|
||||
margin-bottom: var(--space-4);
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
.platform-card:hover .platform-card__icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.platform-card__name {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.platform-card__btn {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* RSS Section */
|
||||
.rss-section {
|
||||
max-width: 640px;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
}
|
||||
.rss-section__desc {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-6);
|
||||
line-height: 1.8;
|
||||
}
|
||||
.rss-input-group {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
max-width: 500px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.rss-input {
|
||||
flex: 1;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.rss-input:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.rss-feedback {
|
||||
font-size: var(--text-sm);
|
||||
margin-top: var(--space-2);
|
||||
min-height: 1.4em;
|
||||
}
|
||||
.rss-feedback--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
/* Expect Cards */
|
||||
.expect-card {
|
||||
text-align: center;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
}
|
||||
.expect-card h3 {
|
||||
font-size: var(--text-lg);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.expect-card p {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user