Add podcast platform badges and Headliner widget

This commit is contained in:
nmemmert
2026-05-06 10:54:38 -04:00
parent 475abff3cf
commit 42bac71fd6
8 changed files with 198 additions and 15 deletions
+23 -1
View File
@@ -31,7 +31,29 @@
"studyGuideUrl": "https://a.co/d/01sG2tOJ",
"shareHeading": "Help one more person hear the Word this week.",
"shareP": "Scan the QR code or text the show link to a friend who needs encouragement today.",
"customLinks": [],
"customLinks": [
{
"id": "pocket-casts",
"label": "Pocket Casts",
"url": "https://pca.st/76jisz3d",
"placement": "platforms",
"imageUrl": "/images/listen-on-pocket-casts.svg"
},
{
"id": "castbox",
"label": "Castbox",
"url": "https://castbox.fm/channel/id7140701?country=us",
"placement": "platforms",
"imageUrl": "/images/listen-on-castbox.svg"
},
{
"id": "goodpods",
"label": "Goodpods",
"url": "https://goodpods.com/podcasts/verse-by-verse-with-nate-732127",
"placement": "platforms",
"imageUrl": "https://storage.googleapis.com/goodpods-images-bucket/badges/generic-badge-3.svg"
}
],
"customBlocks": [],
"archivedSeries": [],
"redirects": [
+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="210" height="48" viewBox="0 0 210 48">
<rect width="210" height="48" rx="6" fill="#131921"/>
<!-- Amazon Music icon: headphone/music note style - simple "a" with music mark -->
<g transform="translate(11,11)">
<!-- Amazon arrow swoosh shape -->
<circle cx="13" cy="13" r="12" fill="#00A8E1"/>
<!-- Simplified headphone icon in white -->
<path fill="#ffffff" d="M13 4C8.03 4 4 8.03 4 13v1h2v-1c0-3.87 3.13-7 7-7s7 3.13 7 7v1h2v-1c0-4.97-4.03-9-9-9zm-3 10H8c-.55 0-1 .45-1 1v3c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-3c0-.55-.45-1-1-1zm6 0h-2c-.55 0-1 .45-1 1v3c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-3c0-.55-.45-1-1-1z"/>
</g>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#b3b3b3" font-size="9.5" x="52" y="19">Listen on</text>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#ffffff" font-size="15" font-weight="bold" x="52" y="36">Amazon Music</text>
</svg>

After

Width:  |  Height:  |  Size: 972 B

+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="183" height="48" viewBox="0 0 183 48">
<rect width="183" height="48" rx="6" fill="#1a1a1a"/>
<!-- Castbox icon: headphones/podcast icon in orange -->
<g transform="translate(11,11)">
<circle cx="13" cy="13" r="12" fill="#F55223"/>
<!-- Headphones shape in white -->
<path fill="#ffffff" d="M13 5C8.58 5 5 8.58 5 13v.5H7V13c0-3.31 2.69-6 6-6s6 2.69 6 6v.5h2V13c0-4.42-3.58-8-8-8zm-3.5 9H7c-.55 0-1 .45-1 1v2.5c0 .55.45 1 1 1h2.5c.55 0 1-.45 1-1V15c0-.55-.45-1-1-1zm7 0h-2.5c-.55 0-1 .45-1 1v2.5c0 .55.45 1 1 1H17c.55 0 1-.45 1-1V15c0-.55-.45-1-1-1z"/>
</g>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#b3b3b3" font-size="9.5" x="52" y="19">Listen on</text>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#ffffff" font-size="16" font-weight="bold" x="52" y="36">Castbox</text>
</svg>

After

Width:  |  Height:  |  Size: 904 B

+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="195" height="48" viewBox="0 0 195 48">
<rect width="195" height="48" rx="6" fill="#F43E37"/>
<!-- Pocket Casts logo: circle with play-in-wave icon -->
<g transform="translate(11,11)">
<circle cx="13" cy="13" r="12" fill="rgba(0,0,0,0.25)"/>
<!-- Outer arc -->
<path fill="#ffffff" d="M13 2C7 2 2 7 2 13s5 11 11 11 11-5 11-11S19 2 13 2zm0 19c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/>
<!-- Inner arc -->
<path fill="#ffffff" d="M13 6c-3.87 0-7 3.13-7 7s3.13 7 7 7 7-3.13 7-7-3.13-7-7-7zm0 11c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/>
<!-- Center dot -->
<circle cx="13" cy="13" r="2.5" fill="#ffffff"/>
</g>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="rgba(255,255,255,0.8)" font-size="9.5" x="52" y="19">Listen on</text>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#ffffff" font-size="14.5" font-weight="bold" x="52" y="36">Pocket Casts</text>
</svg>

After

Width:  |  Height:  |  Size: 1019 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="48" viewBox="0 0 180 48">
<rect width="180" height="48" rx="6" fill="#191414"/>
<!-- Spotify icon (24x24 viewBox path, scaled to fit 26x26 at offset 11,11) -->
<g transform="translate(11,11) scale(1.083)">
<path fill="#1DB954" 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"/>
</g>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#b3b3b3" font-size="9.5" x="48" y="19">Listen on</text>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#ffffff" font-size="16" font-weight="bold" x="48" y="36">Spotify</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="190" height="48" viewBox="0 0 190 48">
<rect width="190" height="48" rx="6" fill="#0f0f0f"/>
<!-- YouTube icon: red rounded rect with white play triangle -->
<g transform="translate(11,11)">
<!-- YouTube red background rect -->
<rect x="1" y="4.5" width="24" height="17" rx="4" fill="#FF0000"/>
<!-- White play triangle -->
<polygon points="10,9 20,13 10,17" fill="#ffffff"/>
</g>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#b3b3b3" font-size="9.5" x="52" y="19">Watch on</text>
<text font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="#ffffff" font-size="16" font-weight="bold" x="52" y="36">YouTube</text>
</svg>

After

Width:  |  Height:  |  Size: 732 B

+32
View File
@@ -460,6 +460,25 @@
align-items: center;
}
.headliner-widget-wrap {
margin-top: 2rem;
}
.headliner-widget-frame {
display: block;
width: 100%;
min-height: 580px;
border: 0;
border-radius: 12px;
}
.headliner-widget-empty {
margin: 0;
text-align: center;
color: rgba(255, 255, 255, 0.72);
font-size: 0.95rem;
}
.platform-btn {
display: inline-flex;
align-items: center;
@@ -495,6 +514,19 @@
display: block;
}
.platform-btn-image {
background: transparent;
padding: 0;
border-radius: 0;
}
.platform-badge {
display: block;
width: 150px;
max-width: 100%;
height: auto;
}
/* ── About ── */
.section-about {
background: #0d0d0d;
+84 -14
View File
@@ -11,6 +11,69 @@ import './App.css'
const SPOTIFY_EMBED_URL =
'https://open.spotify.com/embed/show/0Gq1TzoJOdReSZ1gYQi8Xl?utm_source=generator&theme=0'
const CONSENT_KEY = 'vbn_analytics_consent_choice'
const HEADLINER_WIDGET_ID = 'WI_cmou3b4q7000701p0o9qmmcfj'
function HeadlinerWidget() {
const [iframeSrc, setIframeSrc] = useState('')
const [status, setStatus] = useState<'loading' | 'ready' | 'empty'>('loading')
useEffect(() => {
const getDiscoUrl = () => {
const canonicalHref = document.querySelector('link[rel="canonical"]')?.getAttribute('href')
const ogUrl = document.querySelector('meta[property="og:url"]')?.getAttribute('content')
const base = canonicalHref || ogUrl
if (!base) return window.location.href
try {
const parsed = new URL(base)
return `${parsed.origin}${window.location.pathname}`
} catch {
return window.location.href
}
}
const discoUrl = getDiscoUrl()
const discoTitle = document.querySelector('meta[property="og:title"]')?.getAttribute('content') || document.title
const src = `https://disco.headliner.link/d/web/widget.html?widgetId=${encodeURIComponent(HEADLINER_WIDGET_ID)}&url=${encodeURIComponent(discoUrl)}&title=${encodeURIComponent(discoTitle)}`
const sessionId = `SS_siteforge_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
const query = new URLSearchParams({
sessionId,
url: discoUrl,
widgetId: HEADLINER_WIDGET_ID,
})
fetch(`https://api.headliner.link/api/v1/widget/widget-request?${query.toString()}`)
.then(r => (r.ok ? r.json() : Promise.reject(new Error('widget request failed'))))
.then((data: { results?: unknown[] }) => {
if (Array.isArray(data.results) && data.results.length > 0) {
setIframeSrc(src)
setStatus('ready')
return
}
setStatus('empty')
})
.catch(() => {
// If API probing fails, still attempt iframe rendering.
setIframeSrc(src)
setStatus('ready')
})
}, [])
return (
status === 'empty' ? null : (
<div className="headliner-widget-wrap" aria-label="Featured listening widget">
{status === 'ready' && iframeSrc && (
<iframe
src={iframeSrc}
title="Featured listening widget"
loading="lazy"
className="headliner-widget-frame"
/>
)}
</div>
)
)
}
function StudyDownloadForm({ buttonText = 'Download Guide' }: { buttonText?: string }) {
const [fields, setFields] = useState({ firstName: '', lastName: '', email: '' })
@@ -421,28 +484,34 @@ function PlatformButtons({ content }: { content: SiteContent }) {
return (
<div className="platform-buttons">
<a href={spotifyUrl} target="_blank" rel="noreferrer" className="platform-btn spotify-btn">
<SpotifyIcon />
Listen on Spotify
<a href={appleUrl} target="_blank" rel="noreferrer" className="platform-btn platform-btn-image">
<img src="/images/apple-podcasts-badge.svg" alt="Listen on Apple Podcasts" className="platform-badge" />
</a>
<a href={youtubeUrl} target="_blank" rel="noreferrer" className="platform-btn youtube-btn">
<YouTubeIcon />
YouTube
<a href={spotifyUrl} target="_blank" rel="noreferrer" className="platform-btn platform-btn-image">
<img src="/images/listen-on-spotify.svg" alt="Listen on Spotify" className="platform-badge" />
</a>
<a href={amazonUrl} target="_blank" rel="noreferrer" className="platform-btn amazon-btn">
<AmazonMusicIcon />
Amazon Music
<a href={youtubeUrl} target="_blank" rel="noreferrer" className="platform-btn platform-btn-image">
<img src="/images/watch-on-youtube.svg" alt="Watch on YouTube" className="platform-badge" />
</a>
<a href={amazonUrl} target="_blank" rel="noreferrer" className="platform-btn platform-btn-image">
<img src="/images/listen-on-amazon-music.svg" alt="Listen on Amazon Music" className="platform-badge" />
</a>
<a href={facebookUrl} target="_blank" rel="noreferrer" className="platform-btn facebook-btn">
<FacebookIcon />
Facebook
</a>
<a href={appleUrl} target="_blank" rel="noreferrer" className="platform-btn apple-btn">
<img src="/images/apple-podcasts-badge.svg" alt="Listen on Apple Podcasts" className="apple-badge" />
</a>
{(content.customLinks ?? []).filter(l => l.placement === 'platforms').map(l => (
<a key={l.id} href={l.url} target="_blank" rel="noreferrer" className="platform-btn custom-btn">
{l.label}
<a
key={l.id}
href={l.url}
target="_blank"
rel="noreferrer"
className={`platform-btn ${l.imageUrl ? 'platform-btn-image' : 'custom-btn'}`}
aria-label={`Listen on ${l.label}`}
>
{l.imageUrl
? <img src={l.imageUrl} alt={`Listen on ${l.label}`} className="platform-badge" />
: l.label}
</a>
))}
</div>
@@ -1100,6 +1169,7 @@ function EpisodesPage({ content }: { content: SiteContent }) {
? spotifyFallback
: renderEpisodeCards(currentEpisodes)}
<PlatformButtons content={content} />
<HeadlinerWidget />
</div>
</section>