/* OBBM 16:9 featured image cap — applies sitewide on every single post. Adjust max-width below to resize globally. !important wins over Elementor widget CSS. wrapper required because Elementor Custom Code injects raw on some templates. */ body.single img.wp-post-image, body.single .wp-post-image { max-width: 640px !important; width: 100% !important; height: auto !important; display: block !important; margin: 0 auto 1em !important; aspect-ratio: 16 / 9 !important; object-fit: cover !important; } /* OBBM big red "MENU" button (2026-05-21, per Larry). Restyles the EXISTING mobile hamburger toggle into a labeled red button that matches the Change Location button. It reuses the toggle's own click handler, so it opens the exact same menu — no new menu, no JS. Mobile / tablet only (<=1024px); the desktop nav is untouched. Appearance-only (no display override) so any hidden toggle variant STAYS hidden (no double button). */ @media (max-width: 1024px) { /* --- ElementsKit hamburger (primary mobile nav) --- */ button.elementskit-menu-hamburger.elementskit-menu-toggler { background:#d6261f !important; border-radius:8px !important; padding:12px 24px !important; width:auto !important; height:auto !important; box-shadow:0 2px 6px rgba(0,0,0,.25) !important; white-space:nowrap !important; line-height:1 !important; } button.elementskit-menu-hamburger .elementskit-menu-hamburger-icon, button.elementskit-menu-hamburger .elementskit-menu-hamburger-icon:before, button.elementskit-menu-hamburger .elementskit-menu-hamburger-icon:after { background-color:#fff !important; } button.elementskit-menu-hamburger .elementskit-menu-hamburger-icon, button.elementskit-menu-hamburger i, button.elementskit-menu-hamburger svg { color:#fff !important; fill:#fff !important; } button.elementskit-menu-hamburger.elementskit-menu-toggler::after { content:"MENU"; color:#fff !important; font-weight:800 !important; font-size:18px !important; letter-spacing:.5px !important; margin-left:10px !important; vertical-align:middle !important; } /* --- Elementor Nav-Menu toggle (covers the other possible markup) --- */ .elementor-menu-toggle { background:#d6261f !important; border-radius:8px !important; padding:12px 24px !important; width:auto !important; box-shadow:0 2px 6px rgba(0,0,0,.25) !important; white-space:nowrap !important; line-height:1 !important; } .elementor-menu-toggle i, .elementor-menu-toggle svg { color:#fff !important; fill:#fff !important; } .elementor-menu-toggle::after { content:"MENU"; color:#fff !important; font-weight:800 !important; font-size:18px !important; letter-spacing:.5px !important; margin-left:10px !important; vertical-align:middle !important; } } /* Keep "Change Location" on ONE line inside its red box (2026-05-21, take 3). Root cause: the button is a flex item in the header bar; the bar squeezed it so the red bg shrank and the nowrap label spilled — and my earlier wrap attempt (min-width:0 + overflow-wrap) collapsed it to one char per line. Correct fix: stop the squeeze (flex-shrink:0), size to content, never wrap. */ @media (max-width: 1024px) { .adrevv-loc-btn { flex-shrink: 0 !important; width: auto !important; max-width: none !important; white-space: nowrap !important; } .adrevv-loc-btn__label { white-space: nowrap !important; overflow: visible !important; min-width: auto !important; } } /* Floating "Back" button on single-article pages — returns the reader to the screen they came from (the section/category listing). Added 2026-05-24 per Larry: article pages had no back control on mobile. */ #obbm-back-btn{position:fixed;left:14px;bottom:16px;z-index:99999;display:none; background:#b71c1c;color:#fff;border:0;border-radius:24px;padding:11px 18px; font:600 15px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; box-shadow:0 2px 8px rgba(0,0,0,.30);cursor:pointer;text-decoration:none;} #obbm-back-btn:hover{background:#a11717;} #obbm-back-btn:active{background:#8e1414;} (function(){ function add(){ if(!document.body) return; var isSingle = document.body.classList.contains('single-post') || !!document.querySelector('[data-elementor-type="single-post"]'); if(!isSingle) return; // single articles only if(document.getElementById('obbm-back-btn')) return; // idempotent var b=document.createElement('a'); b.id='obbm-back-btn'; b.href='#'; b.setAttribute('aria-label','Go back'); b.textContent='‹ Back'; b.addEventListener('click',function(e){ e.preventDefault(); if(history.length>1){history.back();} else {location.href='/';} }); document.body.appendChild(b); b.style.display='inline-block'; } if(document.readyState!=='loading'){add();} else{document.addEventListener('DOMContentLoaded',add);} })(); /* Replace the article's social-icons (follow) row with ONE "Share" button on single-article pages. Added 2026-05-24 per Larry. Native share sheet on mobile; popup fallback on desktop. Scoped to single-post so header/footer socials are untouched (only the 2 in-article social-icons widgets are hidden). */ body.single-post .elementor-widget-nekit-social-share{display:none !important;} #obbm-share-btn{display:inline-flex;align-items:center;gap:6px;background:#b71c1c;color:#fff; border:0;border-radius:24px;padding:9px 18px;margin:6px 0 4px; font:600 15px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer;text-decoration:none;} #obbm-share-btn:hover{background:#a11717;} #obbm-share-pop{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:100001;display:none; align-items:center;justify-content:center;} #obbm-share-pop .box{background:#fff;border-radius:12px;padding:18px;max-width:300px;width:84%; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;} #obbm-share-pop a,#obbm-share-pop button{display:block;width:100%;box-sizing:border-box;text-align:left; padding:12px 14px;border:0;background:#f4f4f4;border-radius:8px;margin:6px 0;font-size:15px;color:#222; text-decoration:none;cursor:pointer;} (function(){ function add(){ if(!document.body) return; var single = document.body.classList.contains('single-post') || !!document.querySelector('[data-elementor-type="single-post"]'); if(!single) return; if(document.getElementById('obbm-share-btn')) return; var row = document.querySelector('.elementor-widget-nekit-social-share'); var url = location.href, title = document.title; var btn = document.createElement('a'); btn.id='obbm-share-btn'; btn.href='#'; btn.setAttribute('aria-label','Share this article'); btn.innerHTML='⤴ Share'; btn.addEventListener('click', function(e){ e.preventDefault(); if(navigator.share){ navigator.share({title:title, url:url}).catch(function(){}); } else { pop(); } }); if(row && row.parentNode){ row.parentNode.insertBefore(btn, row); } else { var t=document.querySelector('h1'); if(t&&t.parentNode){ t.parentNode.insertBefore(btn, t.nextSibling); } } function pop(){ var p=document.getElementById('obbm-share-pop'); if(!p){ p=document.createElement('div'); p.id='obbm-share-pop'; var u=encodeURIComponent(url), tt=encodeURIComponent(title); p.innerHTML='
Share this article
'+ 'Share on X'+ 'Share on Facebook'+ 'Share on LinkedIn'+ 'Email this article'+ ''+ '
'; document.body.appendChild(p); p.addEventListener('click', function(ev){ if(ev.target===p) p.style.display='none'; }); p.querySelector('#obbm-share-close').addEventListener('click', function(){ p.style.display='none'; }); p.querySelector('#obbm-share-copy').addEventListener('click', function(){ if(navigator.clipboard){ navigator.clipboard.writeText(url); this.textContent='Link copied!'; } }); } p.style.display='flex'; } } if(document.readyState!=='loading'){ add(); } else { document.addEventListener('DOMContentLoaded', add); } })(); /* Visible series link BUTTONS on single-article pages (2026-05-24, per Larry). (a) Watch full episodes on OBBM Network TV (Endavo /series/ link already in the article body); (b) Recent episodes -> per-series archive (/-archive/), slug from the post's tag-series- class. Placed JUST ABOVE the featured image (Larry's preference). Anchored to News-Elementor article widgets — never a bare

(the only

is the hidden PWA splash #hlpwa-splash-title). */ .obbm-series-btns{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0 16px;} .obbm-series-btns a{display:inline-flex;align-items:center;gap:6px;text-decoration:none; font:700 15px/1.15 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;border-radius:8px;padding:12px 18px;} .obbm-series-btns a.obbm-sb-primary{background:#b71c1c;color:#fff;} .obbm-series-btns a.obbm-sb-primary:hover{background:#a11717;} .obbm-series-btns a.obbm-sb-outline{background:#fff;color:#b71c1c;border:2px solid #b71c1c;} .obbm-series-btns a.obbm-sb-outline:hover{background:#fbeaea;} (function(){ function add(){ if(!document.body) return; var single = document.body.classList.contains('single-post') || !!document.querySelector('[data-elementor-type="single-post"]'); if(!single) return; if(document.getElementById('obbm-series-btns')) return; var sl = document.querySelector('a[href*="obbmnetwork.tv/series/"]'); var watchHref = sl ? sl.href : ''; var archHref = ''; var el = document.querySelector('[class*="tag-series-"]'); if(el && typeof el.className==='string'){ var m = el.className.match(/\btag-series-([a-z0-9-]+)\b/); if(m){ archHref = '/' + m[1] + '-archive/'; } } if(!watchHref && !archHref) return; var row = document.createElement('div'); row.id='obbm-series-btns'; row.className='obbm-series-btns'; var html=''; if(watchHref){ html += '▶ Watch full episodes on OBBM Network TV'; } if(archHref){ html += '📺 Recent episodes'; } row.innerHTML=html; // PRIMARY: just above the featured image var fi = document.querySelector('.elementor-widget-nekit-single-featured-image'); if(fi && fi.parentNode){ fi.parentNode.insertBefore(row, fi); return; } // fallbacks (never a bare

) var title = document.querySelector('.elementor-widget-nekit-single-title'); if(title && title.parentNode){ title.parentNode.insertBefore(row, title.nextSibling); return; } var content = document.querySelector('.elementor-widget-nekit-single-content .elementor-widget-container') || document.querySelector('.elementor-widget-nekit-single-content'); if(content){ content.insertBefore(row, content.firstChild); return; } var sb = document.getElementById('obbm-share-btn') || document.querySelector('.elementor-widget-nekit-social-share'); if(sb && sb.parentNode){ sb.parentNode.insertBefore(row, sb); return; } document.body.insertBefore(row, document.body.firstChild); } if(document.readyState!=='loading'){ add(); } else { document.addEventListener('DOMContentLoaded', add); } })(); /* Hide the author byline + author bio box on the per-series "Recent Episodes" archive pages (2026-05-28, per Larry: these funnel pages don't need an author). Scoped via :has(.obbm-sa) — .obbm-sa is the archive wrapper our build tool emits, present ONLY on these pages, so real articles keep their bylines. The separate date widget is left intact. JS fallback below for any browser w/o :has(). */ body:has(.obbm-sa) .elementor-widget-nekit-single-author, body:has(.obbm-sa) .elementor-widget-nekit-single-author-box { display:none !important; } (function(){ function hide(){ if(!document.querySelector('.obbm-sa')) return; // archive pages only ['.elementor-widget-nekit-single-author','.elementor-widget-nekit-single-author-box'] .forEach(function(sel){ document.querySelectorAll(sel).forEach(function(el){ el.style.setProperty('display','none','important'); }); }); } if(document.readyState!=='loading'){hide();} else {document.addEventListener('DOMContentLoaded',hide);} })(); /* Local Podcasts hub (2026-05-28, per Larry). Geo-aware directory rendered into #obbm-lp-root on /local-podcasts/. LOCAL = visitor's state (city/county roll up to state); NATIONAL always shown. */ #obbm-lp-root .obbm-lp-h{font:800 22px/1.2 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:26px 0 12px;color:#161616;} #obbm-lp-root .obbm-lp-sub{color:#666;font-size:14px;margin:-6px 0 14px;} #obbm-lp-root .obbm-lp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:18px;} #obbm-lp-root .obbm-lp-card{display:block;text-decoration:none;color:inherit;border:1px solid #eee;border-radius:8px;overflow:hidden;background:#fff;transition:box-shadow .15s;} #obbm-lp-root .obbm-lp-card:hover{box-shadow:0 3px 12px rgba(0,0,0,.12);} #obbm-lp-root .obbm-lp-thumb{width:100%;aspect-ratio:16/9;background:#0b1326 center/cover no-repeat;} #obbm-lp-root .obbm-lp-name{padding:10px 12px;font-weight:700;font-size:14px;line-height:1.3;color:#161616;} @media(max-width:600px){#obbm-lp-root .obbm-lp-grid{grid-template-columns:1fr 1fr;gap:12px;}} (function(){ function ck(n){var m=document.cookie.match(new RegExp('(?:^|; )'+n+'=([^;]*)'));return m?decodeURIComponent(m[1]):'';} function esc(s){return (s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} function slg(x){return (x||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'');} function render(){ var root=document.getElementById('obbm-lp-root'); if(!root||root._obbm) return; root._obbm=1; var vcity=slg(ck('adrevv_city')); // CITY ONLY (per Larry 2026-05-28: no county/state rollup) fetch('https://media.obbmnetwork.tv/catalog/podcasts.json').then(function(r){return r.json();}).then(function(d){ var shows=((d&&d.shows)||[]).filter(function(s){return s.archive_url;}); // only HLL-archive-linkable; never Endavo var local=shows.filter(function(s){return s.scope==='city'&&s.city_slug&&vcity&&String(s.city_slug).toLowerCase()===vcity;}); var national=shows.filter(function(s){return s.scope==='national';}); var items,title,sub; if(local.length){ items=local; title='Local Podcasts'; sub='Local shows from your city.'; } else { items=national; title='OBBM Network Podcasts'; sub='No local podcasts in your city yet — here are shows from across the network.'; } function card(s){ var bg=s.thumb?(' style="background-image:url('+esc(s.thumb)+')"'):''; return '
'+esc(s.name)+'
'; } root.innerHTML='

'+title+'

'+sub+'
'+items.map(card).join('')+'
'; }).catch(function(){ root.innerHTML='

Podcasts are loading — please refresh in a moment.

'; }); } if(document.readyState!=='loading'){render();} else {document.addEventListener('DOMContentLoaded',render);} setTimeout(render,1500); setTimeout(render,3500); })(); /* Hide the "Don't show this again" button on the Hyperlocal Loop PWA splash — Larry 2026-06-03: "we can always put it back; I want people to see the daily message" (the splash will host a daily quote/saying once the daily-messages admin tab ships). Reversible: delete this rule and the button returns. */ #hlpwa-splash-disable { display: none !important; } (function(){ try { var ck = document.cookie || ''; // Trigger on ANY adrevv_city cookie (v6: all cities, not just wylie) var hasCity = /(?:^|;\s*)adrevv_city=[a-z0-9-]+(?:\b|;|$)/i.test(ck) || /^\/city\/[a-z0-9-]+\/?$/i.test(location.pathname); if (hasCity) document.documentElement.classList.add('obbm-wylie-active'); } catch(e){} })(); html.obbm-wylie-active .elementor-element-6d33748 .elementor-shortcode > *:not(.obbm-wmr-tile) { visibility: hidden !important; } html.obbm-wylie-active .elementor-element-6d33748 .elementor-shortcode .obbm-wmr-tile, html.obbm-wylie-active .elementor-element-6d33748 .elementor-shortcode .obbm-wmr-tile * { visibility: visible !important; } html.obbm-wylie-active .elementor-element-6d33748 .elementor-shortcode { min-height: 480px; } /* v7 — static Sarah poster (2026-06-27) replaces animated GIF; v.poster is the same image so audio-only playback (pipeline change coming) still shows Sarah. v6 — generalized to ALL HLL city editions; reads city slug from adrevv_city+adrevv_state cookies. */ .obbm-wmr-tile { position: relative; overflow: hidden; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,.12); background: #000; margin-bottom: 10px; } .obbm-wmr-tile:last-child { margin-bottom: 0; } .obbm-wmr-tile img, .obbm-wmr-tile video { width: 100%; height: 100%; display: block; object-fit: cover; } .obbm-wmr-gif { cursor: pointer; aspect-ratio: 16/9; } .obbm-wmr-gif::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 64px; height: 64px; border-radius: 50%; background: rgba(0,0,0,.55) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E") no-repeat center / 32px; pointer-events: none; transition: background-color .2s; } .obbm-wmr-gif:hover::after { background-color: rgba(226,48,40,.85); } .obbm-wmr-gif.obbm-wmr-playing::after { display: none; } .obbm-wmr-ad { aspect-ratio: 7.8 / 1; background: #000; border-radius: 4px; overflow: hidden; display: block; } .obbm-wmr-ad ins.ins-zone, .obbm-wmr-ad ins.ins-zone a, .obbm-wmr-ad ins.ins-zone img { display: block !important; width: 100% !important; max-width: 100% !important; height: 100% !important; object-fit: cover; } .obbm-wmr-tv { display: block; text-decoration: none; aspect-ratio: 16/9; background-size: cover; background-position: center; } .obbm-wmr-tv-overlay { position: absolute; inset: 0; display: flex; align-items: flex-end; background: linear-gradient(to bottom, rgba(0,0,0,0) 40%, rgba(0,0,0,.75) 100%); padding: 14px 16px; } .obbm-wmr-tv-text { color: #fff; font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; font-weight: 800; letter-spacing: .03em; } .obbm-wmr-tv-text-1 { font-size: 19px; line-height: 1.15; } .obbm-wmr-tv-text-1 .obbm-wmr-tv-sup { font-size: .55em; vertical-align: super; } .obbm-wmr-tv-text-2 { font-size: 12px; font-weight: 600; margin-top: 4px; color: #ff5757; letter-spacing: .08em; text-transform: uppercase; } .obbm-wmr-host-img-hidden { display: none !important; } /* "Now Playing" dynamic tile v2 — Larry 2026-07-10 (revised same day). First cut filled the WHOLE tile with the episode's landscape image + a text overlay — Larry's feedback: that reads as "just another article thumbnail" like every other card on the page. Revised design: a distinct dark "TV monitor" motif — text block on one side, the actual episode thumbnail nested INSIDE a smaller bezeled screen-frame on the other side, with a red pill "Watch Live" CTA. This is deliberately NOT a full-bleed image card so it reads as "a live broadcast," not "an article." */ .obbm-wmr-tv-now { display: flex; align-items: center; gap: 14px; height: 100%; width: 100%; box-sizing: border-box; padding: 16px 18px; background: #0b0d10; font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; } .obbm-wmr-tv-now-text { flex: 1 1 auto; min-width: 0; color: #fff; } .obbm-wmr-tv-now-eyebrow { font-size: 11px; font-weight: 800; letter-spacing: .1em; color: #ff5757; text-transform: uppercase; margin-bottom: 5px; } .obbm-wmr-tv-now-city { font-size: 12px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: #b9c2cf; margin-bottom: 8px; } .obbm-wmr-tv-now-title { font-size: 15px; font-weight: 700; line-height: 1.28; color: #fff; margin: 0 0 12px; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .obbm-wmr-tv-now-cta { display: inline-block; background: #e0342b; color: #fff !important; font-weight: 800; font-size: 12px; letter-spacing: .04em; text-transform: uppercase; text-decoration: none !important; padding: 8px 18px; border-radius: 999px; } .obbm-wmr-tv-now-monitorcol { flex: 0 0 auto; width: 56%; max-width: 210px; display: flex; flex-direction: column; align-items: center; } .obbm-wmr-tv-now-monitor { /* Matches the real episode graphic dimensions (1920x1080 = 16:9) exactly — was 16/10, which forced background-size:cover to crop the top/bottom off every thumbnail. Larry 2026-07-10. */ width: 100%; aspect-ratio: 16/9; box-sizing: border-box; background: #2b2f36; border-radius: 8px; padding: 5px; box-shadow: 0 3px 12px rgba(0,0,0,.45); } .obbm-wmr-tv-now-screen { width: 100%; height: 100%; border-radius: 3px; background-size: cover; background-position: center; background-color: #000; } .obbm-wmr-tv-now-neck { width: 22%; height: 8px; background: #2b2f36; margin-top: -1px; } .obbm-wmr-tv-now-base { width: 46%; height: 6px; background: #3a3f47; border-radius: 3px; margin-top: 1px; } /* Brief loading placeholder shown WHILE the now-playing fetch is in flight (typically ~1-3 sec) — Larry 2026-07-10. Prevents the jarring "shows old static content, then suddenly swaps to different content" flash; a plain loading state reads as intentional instead of looking like a glitch. */ .obbm-wmr-tv-loading { display: flex; align-items: center; justify-content: center; height: 100%; width: 100%; background: #0b0d10; color: #7b8794; font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; font-size: 13px; font-weight: 600; letter-spacing: .02em; } (function(){ // Read adrevv_city + adrevv_state cookies → city slug like "wylie-tx" or "denver-co" // The HLL geo plugin sets both for every visitor based on IP/location. function getCookie(name){ var re = new RegExp('(?:^|;\\s*)' + name + '=([^;]+)'); var m = (document.cookie || '').match(re); return m ? decodeURIComponent(m[1]) : ''; } // Map full state names → 2-letter USPS abbreviations. AdRevv geo-plugin // sets adrevv_state to the FULL name ("texas") but our OBBM channel_id // convention uses abbrev suffixes ("plano-tx"), so we must translate. // (v12 fix, 2026-07-15: was producing "plano-texas" and every catalog // lookup returned undefined → static fallback fired on every load.) var STATE_ABBR = { 'alabama':'al','alaska':'ak','arizona':'az','arkansas':'ar','california':'ca', 'colorado':'co','connecticut':'ct','delaware':'de','florida':'fl','georgia':'ga', 'hawaii':'hi','idaho':'id','illinois':'il','indiana':'in','iowa':'ia', 'kansas':'ks','kentucky':'ky','louisiana':'la','maine':'me','maryland':'md', 'massachusetts':'ma','michigan':'mi','minnesota':'mn','mississippi':'ms','missouri':'mo', 'montana':'mt','nebraska':'ne','nevada':'nv','new hampshire':'nh','new jersey':'nj', 'new mexico':'nm','new york':'ny','north carolina':'nc','north dakota':'nd','ohio':'oh', 'oklahoma':'ok','oregon':'or','pennsylvania':'pa','rhode island':'ri','south carolina':'sc', 'south dakota':'sd','tennessee':'tn','texas':'tx','utah':'ut','vermont':'vt', 'virginia':'va','washington':'wa','west virginia':'wv','wisconsin':'wi','wyoming':'wy', 'district of columbia':'dc' }; function stateAbbr(s){ s = (s || '').toLowerCase().trim(); if (s.length === 2) return s; // already an abbrev // v13 fix (2026-09-08, arch #308): the adrevv_state cookie holds multi-word // states HYPHENATED ("new-mexico") but STATE_ABBR is keyed with SPACES // ("new mexico"), so every city in a 2-word state missed the map and fell // through to the RAW value — building "las-cruces-new-mexico", which is not // a channel id (404 on catalog + morning-reports). 22 of 175 OBBM channels // sit in multi-word states. Normalising separators can only turn a MISS // into a HIT, never the reverse — verified 10 fixed / 0 regressions. s = s.replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim(); return STATE_ABBR[s] || s; // fall through to raw if unknown } function getCitySlug(){ // Cookie first (set by HLL geo-injection plugin) var c = (getCookie('adrevv_city') || '').toLowerCase().trim(); var s = stateAbbr(getCookie('adrevv_state')); if (c && s) return c + '-' + s; // URL fallback — /city// (HLL URLs don't carry state; returns bare city) var m = location.pathname.match(/^\/city\/([a-z0-9-]+)\/?$/i); if (m) return m[1].toLowerCase(); return ''; } function cityDisplayName(slug){ // "denver-co" → "DENVER" / "oklahoma-city-ok" → "OKLAHOMA CITY" var name = (slug || '').replace(/-[a-z]{2}$/i, ''); // strip trailing -XX state return name.replace(/-/g, ' ').toUpperCase(); } var SLUG = getCitySlug(); if (!SLUG) return; // no city detected → silent no-op var MEDIA_BASE = 'https://media.obbmnetwork.tv/media/morning-reports/' + SLUG; // POSTER is SHARED across all cities — static Sarah image per Larry 2026-06-27. // (Was an animated GIF; replaced with static JPG. Pipeline-Claude will also // strip the video track from report.mp3 so it's audio-only — the poster // stays visible during playback, giving viewers a "radio with image" UX.) var POSTER_URL = 'https://media.obbmnetwork.tv/media/morning-reports/_shared/sarah_poster.jpg'; // Cache-bust: use the manifest's per-city 'updated' timestamp. // Every batch (4×/day) writes a NEW timestamp → NEW URL → guaranteed fresh // delivery with zero stale window at any cache layer (browser, CF, corp proxy). // Manifest served with max-age=60 so browsers refresh it every minute. // Falls back to slot-label logic if manifest fetch fails. (Larry 2026-07-01) var _d = new Date(); var _h = _d.getUTCHours(); var _slot = (_h >= 14 && _h = 20 || _h = 1 && _h < 5) ? 'evening' : 'night'; var _ymd = _d.getUTCFullYear() + String(_d.getUTCMonth()+1).padStart(2,'0') + String(_d.getUTCDate()).padStart(2,'0'); // Placeholder URL — active until the manifest fetch resolves (usually <300ms). // -r6 forces a one-time global CF cache invalidation for the deploy of this refactor. var MP4_URL = MEDIA_BASE + '/report.mp3?v=' + _ymd + '-' + _slot + '-r6'; // Kick off the manifest fetch RIGHT NOW so it races DOMContentLoaded. // When it resolves, upgrade MP4_URL + any already-mounted