Add BunnyCDN integration, on-air website badge, publish script fixes
- On-air toggle uploads status.json to BunnyCDN + purges cache, website polls it every 15s to show live ON AIR / OFF AIR badge - Publish script downloads Castopod's copy of audio for CDN upload (byte-exact match), removes broken slug fallback, syncs all episode media to CDN after publishing - Fix f-string syntax error in publish_episode.py (Python <3.12) - Enable CORS on BunnyCDN pull zone for json files - CDN URLs for website OG images, stem recorder bug fixes, LLM token budget tweaks, session context in CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,75 @@ a:hover {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* On-Air Badge */
|
||||
.on-air-badge {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
background: var(--accent-red);
|
||||
color: #fff;
|
||||
padding: 0.4rem 1.2rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
animation: on-air-glow 2s ease-in-out infinite;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.on-air-badge.visible {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.on-air-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
animation: on-air-blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes on-air-glow {
|
||||
0%, 100% { box-shadow: 0 0 8px rgba(204, 34, 34, 0.5); }
|
||||
50% { box-shadow: 0 0 20px rgba(204, 34, 34, 0.8); }
|
||||
}
|
||||
|
||||
@keyframes on-air-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
/* Off-Air Badge */
|
||||
.off-air-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #444;
|
||||
color: var(--text-muted);
|
||||
padding: 0.35rem 1.1rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.off-air-badge.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phone.live .phone-number {
|
||||
color: var(--accent-red);
|
||||
text-shadow: 0 0 16px rgba(204, 34, 34, 0.35);
|
||||
}
|
||||
|
||||
.phone.live .phone-label {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Subscribe buttons */
|
||||
.subscribe-row {
|
||||
display: flex;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<meta property="og:title" content="How It Works — Luke at the Roost">
|
||||
<meta property="og:description" content="The tech behind a one-of-a-kind AI radio show. Real callers, AI callers, voice synthesis, and a live control room.">
|
||||
<meta property="og:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
<meta property="og:url" content="https://lukeattheroost.com/how-it-works">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
<!-- OG / Social -->
|
||||
<meta property="og:title" content="Luke at the Roost — Life advice for biologically questionable organisms">
|
||||
<meta property="og:description" content="The call-in talk show where Luke gives life advice to biologically questionable organisms — from a desert hermit's RV. Call in: 208-439-LUKE.">
|
||||
<meta property="og:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
<meta property="og:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
<meta property="og:url" content="https://lukeattheroost.com">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Luke at the Roost">
|
||||
<meta name="twitter:description" content="The call-in talk show where Luke gives life advice to biologically questionable organisms. Call in: 208-439-LUKE">
|
||||
<meta name="twitter:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
<meta name="twitter:image" content="https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 4c-2 0-4 2-4 5 0 1 .3 2 .8 3C26 13 24 16 24 20c0 2 .5 4 1.5 5.5L22 28c-2 1-4 3-5 6l-3 10c-.5 2 .5 3 2 3h4l1-4 2 4h6l-1-6 3 6h6l-1-6 3 6h4c1.5 0 2.5-1 2-3l-3-10c-1-3-3-5-5-6l-3.5-2.5C35.5 24 36 22 36 20c0-4-2-7-4.8-8 .5-1 .8-2 .8-3 0-3-2-5-4-5z' fill='%23e8791d'/%3E%3Ccircle cx='30' cy='17' r='1.5' fill='%231a1209'/%3E%3Cpath d='M36 15c1-1 3-1 4 0s1 3 0 4' fill='none' stroke='%23cc2222' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M28 22c2 1 4 1 6 0' fill='none' stroke='%23e8791d' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E">
|
||||
@@ -33,7 +33,7 @@
|
||||
"name": "Luke at the Roost",
|
||||
"description": "The call-in talk show where Luke gives life advice to biologically questionable organisms. Broadcast from a desert hermit's RV, featuring a mix of real callers and AI-generated callers.",
|
||||
"url": "https://lukeattheroost.com",
|
||||
"image": "https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3",
|
||||
"image": "https://cdn.lukeattheroost.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=3",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Luke MacNeil"
|
||||
@@ -71,7 +71,14 @@
|
||||
<div class="hero-info">
|
||||
<h1>Luke at the Roost</h1>
|
||||
<p class="tagline">The call-in talk show where Luke gives life advice to biologically questionable organisms.</p>
|
||||
<div class="phone">
|
||||
<div class="phone" id="phone-section">
|
||||
<div class="on-air-badge" id="on-air-badge">
|
||||
<span class="on-air-dot"></span>
|
||||
ON AIR
|
||||
</div>
|
||||
<div class="off-air-badge" id="off-air-badge">
|
||||
OFF AIR
|
||||
</div>
|
||||
<span class="phone-label">Call in live</span>
|
||||
<span class="phone-number">208-439-LUKE</span>
|
||||
<span class="phone-digits">(208-439-5853)</span>
|
||||
|
||||
@@ -300,6 +300,24 @@ function initTestimonials() {
|
||||
resetAutoplay();
|
||||
}
|
||||
|
||||
// On-Air Status
|
||||
function checkOnAir() {
|
||||
fetch(`https://cdn.lukeattheroost.com/status.json?_=${Date.now()}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const badge = document.getElementById('on-air-badge');
|
||||
const offBadge = document.getElementById('off-air-badge');
|
||||
const phone = document.getElementById('phone-section');
|
||||
const live = !!data.on_air;
|
||||
if (badge) badge.classList.toggle('visible', live);
|
||||
if (offBadge) offBadge.classList.toggle('hidden', live);
|
||||
if (phone) phone.classList.toggle('live', live);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// Init
|
||||
fetchEpisodes();
|
||||
initTestimonials();
|
||||
checkOnAir();
|
||||
setInterval(checkOnAir, 15000);
|
||||
|
||||
Reference in New Issue
Block a user