diff --git a/functions/feed.js b/functions/feed.js new file mode 100644 index 0000000..4dd67c8 --- /dev/null +++ b/functions/feed.js @@ -0,0 +1,12 @@ +export async function onRequest() { + const feedUrl = 'https://podcast.macneilmediagroup.com/@LukeAtTheRoost/feed.xml'; + const res = await fetch(feedUrl); + const xml = await res.text(); + return new Response(xml, { + headers: { + 'Content-Type': 'application/xml', + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'public, max-age=300', + }, + }); +} diff --git a/website/css/style.css b/website/css/style.css index 2d3f77d..7b8ce9c 100644 --- a/website/css/style.css +++ b/website/css/style.css @@ -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 { diff --git a/website/images/banner.png b/website/images/banner.png new file mode 100644 index 0000000..19be3b7 Binary files /dev/null and b/website/images/banner.png differ diff --git a/website/index.html b/website/index.html index 0017041..ecad816 100644 --- a/website/index.html +++ b/website/index.html @@ -18,12 +18,17 @@ - + + + +
@@ -49,7 +54,7 @@ YouTube - @@ -77,6 +82,7 @@ YouTube RSS
+

© 2026 Luke at the Roost

diff --git a/website/js/app.js b/website/js/app.js index fa500da..6453658 100644 --- a/website/js/app.js +++ b/website/js/app.js @@ -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 = ''; const pauseSVG = ''; +// 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 = '
Unable to load episodes. View RSS feed
'; - return; + if (xml.includes('')) break; + throw new Error('Invalid response'); + } catch (err) { + if (attempt === maxRetries) { + episodesList.innerHTML = '
Unable to load episodes. View RSS feed
'; + return; + } } }