Add banner, Apple Podcasts link, feed proxy, and fetch retry

- Add roostbanner.png hero banner image
- Add Apple Podcasts subscribe button
- Add Cloudflare Pages Function to proxy RSS feed (avoids CORS)
- Add fetch timeout and retry for episode loading
- Add contact email to footer
- Replace favicon with inline SVG rooster

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 14:03:29 -07:00
parent e979c4151d
commit 0a614eba6d
5 changed files with 63 additions and 16 deletions

View File

@@ -33,6 +33,18 @@ a:hover {
color: var(--accent-hover);
}
/* Banner */
.banner {
width: 100%;
overflow: hidden;
}
.banner-img {
width: 100%;
height: auto;
display: block;
}
/* Hero */
.hero {
padding: 3rem 1.5rem 2rem;
@@ -356,6 +368,18 @@ a:hover {
color: var(--text);
}
.footer-contact {
margin-bottom: 0.75rem;
}
.footer-contact a {
color: var(--accent);
}
.footer-contact a:hover {
color: var(--accent-hover);
}
/* Desktop */
@media (min-width: 768px) {
.hero {

BIN
website/images/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -18,12 +18,17 @@
<meta name="twitter:image" content="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=2">
<!-- Favicon -->
<link rel="icon" type="image/png" href="https://podcast.macneilmediagroup.com/media/podcasts/LukeAtTheRoost/cover_feed.png?v=2">
<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">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- Banner -->
<div class="banner">
<img src="images/banner.png" alt="Luke at the Roost — ON AIR" class="banner-img">
</div>
<!-- Hero -->
<section class="hero">
<div class="hero-inner">
@@ -49,7 +54,7 @@
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
YouTube
</a>
<a href="#" class="subscribe-btn btn-apple" title="Coming soon to Apple Podcasts">
<a href="https://podcasts.apple.com/us/podcast/luke-at-the-roost/id1875205848" target="_blank" rel="noopener" class="subscribe-btn btn-apple">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.34 0A5.328 5.328 0 0 0 0 5.34v13.32A5.328 5.328 0 0 0 5.34 24h13.32A5.328 5.328 0 0 0 24 18.66V5.34A5.328 5.328 0 0 0 18.66 0zm6.525 2.568c2.336 0 4.448.902 4.448 3.545 0 1.497-.89 2.67-1.916 3.545-.663.566-.795 .84-.795 1.347 0 .6.397 1.173.894 1.722 1.417 1.564 1.96 2.853 1.96 4.448 0 3.063-2.673 4.257-5.165 4.257-.315 0-.658-.02-.994-.063-1.523-.195-2.86-.9-3.632-.9-.82 0-1.98.623-3.377.87A5.715 5.715 0 0 1 3.15 21.4c-1.27 0-2.1-.96-2.1-2.663 0-1.2.6-2.7 1.845-4.29.63-.81 1.62-1.83 2.91-2.31-.06-.6-.09-1.14-.09-1.62 0-4.28 2.76-7.95 6.15-7.95z"/></svg>
Apple
</a>
@@ -77,6 +82,7 @@
<a href="https://www.youtube.com/watch?v=xryGLifMBTY&list=PLGq4uZyNV1yYH_rcitTTPVysPbC6-7pe-" target="_blank" rel="noopener">YouTube</a>
<a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank" rel="noopener">RSS</a>
</div>
<p class="footer-contact">Sales &amp; Collaboration: <a href="mailto:luke@macneilmediagroup.com">luke@macneilmediagroup.com</a></p>
<p>&copy; 2026 Luke at the Roost</p>
</footer>

View File

@@ -1,5 +1,4 @@
const FEED_URL = 'https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml';
const CORS_PROXY = 'https://api.allorigins.win/raw?url=';
const FEED_URL = '/feed';
const audio = document.getElementById('audio-element');
const stickyPlayer = document.getElementById('sticky-player');
@@ -57,23 +56,29 @@ function truncate(html, maxLen) {
const playSVG = '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>';
const pauseSVG = '<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
// Fetch with timeout
function fetchWithTimeout(url, ms = 8000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
return fetch(url, { signal: controller.signal }).finally(() => clearTimeout(timeout));
}
// Fetch and parse RSS feed
async function fetchEpisodes() {
let xml;
try {
// Try direct fetch first
const res = await fetch(FEED_URL);
if (!res.ok) throw new Error('Direct fetch failed');
xml = await res.text();
} catch {
const maxRetries = 2;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// Fallback to CORS proxy
const res = await fetch(CORS_PROXY + encodeURIComponent(FEED_URL));
if (!res.ok) throw new Error('Proxy fetch failed');
const res = await fetchWithTimeout(FEED_URL);
if (!res.ok) throw new Error('Fetch failed');
xml = await res.text();
} catch {
episodesList.innerHTML = '<div class="episodes-error">Unable to load episodes. <a href="' + FEED_URL + '" target="_blank">View RSS feed</a></div>';
return;
if (xml.includes('<item>')) break;
throw new Error('Invalid response');
} catch (err) {
if (attempt === maxRetries) {
episodesList.innerHTML = '<div class="episodes-error">Unable to load episodes. <a href="https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml" target="_blank">View RSS feed</a></div>';
return;
}
}
}