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:
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>
|
||||
Reference in New Issue
Block a user