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:
2026-03-14 20:44:42 -07:00
parent 1adc2ed3a4
commit ee89727662
236 changed files with 16513 additions and 0 deletions

View 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>

View 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>

View 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>

View 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 &rarr;</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>

View 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>

View 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>

View 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>

View 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>

View 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',
},
});
}

View 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>

View 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>

View 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>