Add clickable host bios linking to host detail pages on Nuggets; v2.0.21
Publish Container / docker (push) Has been cancelled

Each host card on /nuggets now links to /nuggets/hosts/:hostId showing
the host's full photo, bio, and listen links. Archived hosts return a
graceful "not found" page with a back link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-16 07:32:18 -04:00
1 parent 4607532440
commit b35605f411
4 files changed
+86 -5

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "siteforge",
"version": "2.0.3",
"version": "2.0.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "siteforge",
"version": "2.0.3",
"version": "2.0.20",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
"version": "2.0.20",
"version": "2.0.21",
"type": "module",
"scripts": {
"dev": "vite",
+18
View File
@@ -9890,6 +9890,24 @@
.nuggets-host-card-name { font-family: var(--brand-font-heading); font-size: 1.05rem; color: #e8d9b5; margin: 0; }
.nuggets-host-card-role { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--brand-gold); opacity: 0.85; margin: 0.2rem 0 0; }
.nuggets-host-card-bio { font-size: 0.875rem; color: var(--brand-muted); line-height: 1.6; margin: 0.875rem 0 0; }
.nuggets-host-card--link { text-decoration: none; cursor: pointer; transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s; }
.nuggets-host-card--link:hover { border-color: rgba(201, 168, 76, 0.45); box-shadow: 0 4px 18px rgba(0,0,0,0.28); transform: translateY(-2px); }
.nuggets-host-card--link .nuggets-host-card-name { color: #e8d9b5; }
.nuggets-host-card-cta { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--brand-gold); margin: 0.9rem 0 0; opacity: 0.75; transition: opacity 0.15s; }
.nuggets-host-card--link:hover .nuggets-host-card-cta { opacity: 1; }
.nuggets-host-detail-layout { display: grid; grid-template-columns: 240px 1fr; gap: 3.5rem; align-items: start; padding-top: 0.5rem; }
.nuggets-host-detail-photo-col { display: flex; justify-content: center; }
.nuggets-host-detail-photo { width: 200px; height: 200px; object-fit: cover; border-radius: 50%; border: 3px solid rgba(201, 168, 76, 0.35); box-shadow: 0 6px 24px rgba(0,0,0,0.35); }
.nuggets-host-detail-bio { font-size: 1.05rem; color: var(--brand-muted); line-height: 1.7; margin: 0 0 1.5rem; }
.nuggets-host-detail-listen { margin-top: 1.5rem; }
.nuggets-host-detail-back { font-size: 0.875rem; color: var(--brand-gold); text-decoration: none; opacity: 0.8; transition: opacity 0.15s; }
.nuggets-host-detail-back:hover { opacity: 1; }
@media (max-width: 640px) {
.nuggets-host-detail-layout { grid-template-columns: 1fr; gap: 2rem; }
.nuggets-host-detail-photo { width: 140px; height: 140px; }
}
.nuggets-episodes-note { color: var(--brand-muted); margin-top: 1.5rem; }
.nuggets-latest-episode { background: rgba(26, 26, 21, 0.9); border: 1px solid rgba(201, 168, 76, 0.25); border-radius: 12px; padding: 1.5rem 1.75rem; margin-top: 1.5rem; }
+65 -2
View File
@@ -1933,7 +1933,7 @@ function NuggetsPage({ content }: { content: SiteContent }) {
<p className="eyebrow">Meet the Hosts</p>
<div className="nuggets-hosts-list">
{activeHosts.map(host => (
<div key={host.id} className="nuggets-host-card">
<Link key={host.id} to={`/nuggets/hosts/${host.id}`} className="nuggets-host-card nuggets-host-card--link">
<div className="nuggets-host-card-header">
{host.imageUrl && <img src={host.imageUrl} alt={host.name} className="nuggets-host-card-img" loading="lazy" />}
<div className="nuggets-host-card-meta">
@@ -1942,7 +1942,8 @@ function NuggetsPage({ content }: { content: SiteContent }) {
</div>
</div>
{host.bio && <p className="nuggets-host-card-bio">{host.bio}</p>}
</div>
<p className="nuggets-host-card-cta">View Profile →</p>
</Link>
))}
</div>
</>
@@ -1992,6 +1993,67 @@ function NuggetsPage({ content }: { content: SiteContent }) {
)
}
function NuggetsHostDetailPage({ content }: { content: SiteContent }) {
const { hostId } = useParams<{ hostId: string }>()
const nuggets = content.nuggets ?? DEFAULTS.nuggets
const siteTitle = content.seo?.title || DEFAULTS.seo.title
const host = (nuggets.hosts ?? []).find(h => h.id === hostId && !h.archived)
usePageMeta(
host ? `${host.name} | ${nuggets.title} | ${siteTitle}` : `Host | ${nuggets.title} | ${siteTitle}`,
host?.bio || `Meet ${host?.name}, a host on ${nuggets.title}.`,
)
if (!host) {
return (
<div className="site">
<SiteHeader content={content} />
<SiteSearchBar content={content} />
<PageBanner eyebrow="Nuggets" title="Host Not Found" desc="This host profile doesn't exist or has been removed." />
<section className="section-nuggets-body" style={{ textAlign: 'center', paddingBottom: '4rem' }}>
<Link to="/nuggets" className="btn-secondary">&larr; Back to Nuggets</Link>
</section>
<SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>
)
}
return (
<div className="site">
<SiteHeader content={content} />
<SiteSearchBar content={content} />
<PageBanner eyebrow={nuggets.title} title={host.name} desc={host.role} />
<section className="section-nuggets-body" aria-label={`${host.name} profile`}>
<div className="section-inner nuggets-host-detail-layout">
<div className="nuggets-host-detail-photo-col">
{host.imageUrl && (
<img src={host.imageUrl} alt={host.name} className="nuggets-host-detail-photo" />
)}
</div>
<div className="nuggets-host-detail-body">
{host.bio && <p className="nuggets-host-detail-bio">{host.bio}</p>}
{(nuggets.spotifyUrl || nuggets.appleUrl) && (
<div className="nuggets-host-detail-listen">
<p className="eyebrow" style={{ marginBottom: '0.75rem' }}>Listen to {nuggets.title}</p>
<div className="nuggets-platform-links">
{nuggets.spotifyUrl && <a href={nuggets.spotifyUrl} target="_blank" rel="noopener noreferrer" className="btn-primary">Listen on Spotify</a>}
{nuggets.appleUrl && <a href={nuggets.appleUrl} target="_blank" rel="noopener noreferrer" className="btn-secondary">Listen on Apple Podcasts</a>}
</div>
</div>
)}
<div style={{ marginTop: '2rem' }}>
<Link to="/nuggets" className="nuggets-host-detail-back">&larr; Back to {nuggets.title}</Link>
</div>
</div>
</div>
</section>
<SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>
)
}
function BooksAndResourcesSection({ content }: { content: SiteContent }) {
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
@@ -3131,6 +3193,7 @@ export default function App() {
<Route path="/episodes" element={<EpisodesPage content={content} />} />
<Route path="/episodes/:id" element={<EpisodeDetailPage content={content} />} />
<Route path="/nuggets" element={<NuggetsPage content={content} />} />
<Route path="/nuggets/hosts/:hostId" element={<NuggetsHostDetailPage content={content} />} />
<Route path="/resources" element={<ResourcesPage content={content} />} />
<Route path="/downloads/:id" element={<DownloadDetailPage content={content} />} />
<Route path="/about" element={<AboutPage content={content} />} />