1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>CalmArizona â Music Beautiful as Prescott</title> 7 <link rel="icon" type="image/png" href="images/logo-calmaz.png" /> 8 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800;900&display=swap" rel="stylesheet" /> 9 <link rel="stylesheet" href="css/calmaz-base.css?v=30" /> 10 <link rel="stylesheet" href="css/calmaz.css?v=48" /> 11 <style id="nav-critical"> 12 nav.main-nav{background:#fff7ec !important;position:relative !important;top:auto !important;z-index:auto !important;} 13 .main-nav-inner{width:100%;padding:0 48px;display:flex;align-items:center;justify-content:space-between;min-height:150px;} 14 .nav-logo img{height:120px;width:auto;object-fit:contain;} 15 .nav-search{background:none;border:none;cursor:pointer;color:#1a2f4e;padding:6px;display:flex;align-items:center;} 16 .nav-hamburger{display:none;background:none;border:none;cursor:pointer;color:#1a2f4e;padding:6px;} 17 @media(max-width:900px){.main-nav-inner{padding:0 20px;}.nav-links li a{padding:6px 7px;font-size:11px;}} 18 @media(max-width:640px){.nav-links-wrap{display:none !important;}.nav-hamburger{display:flex !important;}.nav-logo img{height:100px !important;min-width:0 !important;max-width:180px !important;}.nav-logo{min-width:0;flex-shrink:1;overflow:hidden;}.nav-right{min-width:0;flex-shrink:0;}.main-nav-inner{flex-wrap:nowrap;overflow:hidden;}} 19 </style> 20 21</head> 22<body class="calmaz-home"> 23 <div id="site-header"></div> 24 25 <!-- HERO CAROUSEL --> 26 <section id="hero-section" style="width:100%;background:#504395;padding:40px 0;border-bottom:3px solid rgba(255,255,255,0.12);"> 27 <div id="calmaz-carousel-wrap" style="width:75%;margin:0 auto;position:relative;border-radius:10px;overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,0.35);background:#504395;min-height:200px;"> 28 29 <!-- Slides container --> 30 <div id="calmaz-carousel-slides" style="display:flex;transition:transform 0.5s cubic-bezier(0.4,0,0.2,1);will-change:transform;"></div> 31 32 <!-- Caption overlay --> 33 <div id="calmaz-carousel-caption" style="display:none;position:absolute;bottom:0;left:0;right:0;padding:20px 24px 16px;background:linear-gradient(transparent,rgba(0,0,0,0.72));pointer-events:none;"> 34 <p id="calmaz-carousel-caption-text" style="margin:0;font-size:15px;font-weight:600;color:#fff;line-height:1.4;text-shadow:0 1px 4px rgba(0,0,0,0.5);"></p> 35 </div> 36 37 <!-- Arrows --> 38 <button id="calmaz-carousel-prev" onclick="calmazCarouselMove(-1)" style="display:none;position:absolute;left:12px;top:50%;transform:translateY(-50%);background:rgba(0,0,0,0.45);border:none;color:#fff;font-size:20px;width:40px;height:40px;border-radius:50%;cursor:pointer;z-index:10;transition:background .15s;line-height:1;" onmouseover="this.style.background='rgba(0,0,0,0.75)'" onmouseout="this.style.background='rgba(0,0,0,0.45)'">‹</button> 39 <button id="calmaz-carousel-next" onclick="calmazCarouselMove(1)" style="display:none;position:absolute;right:12px;top:50%;transform:translateY(-50%);background:rgba(0,0,0,0.45);border:none;color:#fff;font-size:20px;width:40px;height:40px;border-radius:50%;cursor:pointer;z-index:10;transition:background .15s;line-height:1;" onmouseover="this.style.background='rgba(0,0,0,0.75)'" onmouseout="this.style.background='rgba(0,0,0,0.45)'">›</button> 40 41 <!-- Dots --> 42 <div id="calmaz-carousel-dots" style="display:none;position:absolute;bottom:10px;right:14px;display:flex;gap:6px;z-index:10;"></div> 43 44 <!-- Placeholder while loading --> 45 <div id="calmaz-carousel-placeholder" style="width:100%;aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;background:#504395;"> 46 <span style="color:rgba(255,255,255,0.25);font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;">Loading…</span> 47 </div> 48 </div> 49 </section> 50 51 <!-- ON AIR BAR --> 52 <div class="on-air-bar" id="on-air-bar" style="display:none;"> 53 <span class="on-air-badge">Now Playing</span> 54 <span class="on-air-show" id="on-air-show"></span> 55 <span class="on-air-host" id="on-air-host"></span> 56 <div style="display:flex;gap:10px;margin-left:auto;flex-shrink:0;align-items:center;"> 57 <a href="#" data-listen="calmaz" style="display:inline-flex;align-items:center;gap:6px;background:#b7048b;color:#fff;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;text-decoration:none;white-space:nowrap;">▶ Listen Live</a> 58 <button id="watch-live-btn" style="display:none;align-items:center;gap:6px;background:rgba(255,255,255,.15);color:#fff;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;cursor:pointer;white-space:nowrap;border:1px solid rgba(255,255,255,.3);">📺 Watch Live</button> 59 </div> 60 </div> 61 62 63 64 65 <!-- WEATHER BAND --> 66 <div id="calmaz-weather-band" style="background:#504395;border-bottom:1px solid rgba(255,255,255,0.08);overflow:hidden;height:120px;
66display:flex;align-items:center;"> 67 <div style="flex:1;overflow:hidden;height:95px;position:relative;margin-top:4px;"> 68 <a class="weatherwidget-io" href="https://forecast7.com/en/34d54n112d47/prescott/?unit=us" data-label_1="PRESCOTT" data-label_2="WEATHER" data-theme="original" data-basecolor="#504395" data-days="7">PRESCOTT WEATHER</a> 69 </div> 70 <div id="calmaz-weather-report" style="flex-shrink:0;display:none;align-items:center;gap:8px;padding:0 20px;border-left:1px solid rgba(255,255,255,0.1);min-width:520px;background:#504395;"> 71 <span style="font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:rgba(255,255,255,0.5);">🎵 Today's Report</span> 72 <div id="calmaz-weather-player"></div> 73 </div> 74 </div> 75 76 <div id="calmaz-ad-banner" style="display:none;text-align:center;padding:12px 0;background:#f8f9fa;border-bottom:1px solid #e8ecf0;"></div> 77 78 <!-- NEWS + VIDEO SIDEBAR (2/3 + 1/3) --> 79 <div id="home-top-row" style="display:grid;grid-template-columns:3fr 1fr;gap:0;align-items:start;background:#f8f9fa;"> 80 81 <!-- LEFT 2/3: News --> 82 <section style="padding:48px 48px;background:#f8f9fa;" id="news-section"> 83 <div class="section-header"> 84 <h2 class="section-title" >Latest News</h2> 85 <a href="pages/news.html" class="section-link">View All News ›</a> 86 </div> 87 <div class="news-grid" id="news-grid"> 88 <div style="color:#546e7a;font-size:14px;padding:24px 0;">Loading newsâ¦</div> 89 </div> 90 </section> 91 92 <!-- RIGHT 1/3: Video sidebar --> 93 <div id="home-video-sidebar" style="background:#504395;padding:24px 20px;display:flex;flex-direction:column;gap:24px;align-self:stretch;"> 94 95 <!-- Livestreams (CMS-managed, station=calmaz) --> 96 <div id="livestreams-container"></div> 97 98 99 <!-- Recently Played --> 100 <div id="calmaz-recently-played"> 101 <div style="font-size:10px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:rgba(255,255,255,0.5);margin-bottom:10px;">♫ Recently Played</div> 102 <div id="calmaz-rp-list" style="display:flex;flex-direction:column;gap:1px;"> 103 <div id="calmaz-rp-loading" style="font-size:12px;color:rgba(255,255,255,0.3);padding:8px 0;">Loadingâ¦</div> 104 </div> 105 106 </div> 107 108 </div> 109 </div> 110 111 <!-- SCHEDULE: Full width below --> 112 <section style="display:none;padding:48px 48px;background:#fff;" id="schedule-section"> 113 <div> 114 <div class="section-header"> 115 <h2 class="section-title" >Today's Lineup</h2> 116 <a href="pages/schedule.html" class="section-link">Full Schedule ›</a> 117 </div> 118 <div class="schedule-grid" id="schedule-grid"> 119 <div style="color:#546e7a;font-size:14px;padding:24px 0;">Loading scheduleâ¦</div> 120 </div> 121 </div> 122 </section> 123 124 <!-- Responsive --> 125 <style> 126 @media (max-width: 900px) { 127 #home-top-row { grid-template-columns: 1fr !important; } 128 #home-video-sidebar { align-self:auto !important; } 129 } 130 </style> 131 132 <div id="site-footer"></div> 133
133<script src="js/layout.js?v=7" data-depth="0"></script>
vendor: 1 bytes, line 133
133
134<script src="js/video-popup.js?v=1" data-site="calmaz"></script>
vendor: 1 bytes, line 134
134
135<script src="js/livestreams.js?v=1"></script>
vendor: 1 bytes, line 135
135
136<script>document.addEventListener("DOMContentLoaded", function(){ if (window.renderLivestreams) renderLivestreams("calmaz", "livestreams-container"); });</script>
136 137
137<script> 138 // ââ HOMEPAGE ON-AIR BAR â standalone, no dependency on Promise chain ââââââ 139 (function() { 140 var CMS = 'https://pbc-cms-production.up.railway.app'; 141 function tc(s) { 142 return (s || '').toLowerCase().replace(/(^|\s)(\S)/g, function(m, s1, s2) { return s1 + s2.toUpperCase(); }); 143 } 144 function updateBar() { 145 var bar = document.getElementById('on-air-bar'); 146 if (!bar) { setTimeout(updateBar, 100); return; } 147 fetch(CMS + '/api/recent-tracks/kahm?ts=' + Date.now()) 148 .then(function(r) { return r.json(); }) 149 .then(function(tracks) { 150 if (!tracks || !tracks.length) return; 151 var t = tracks[0]; 152 var showEl = document.getElementById('on-air-show'); 153 var hostEl = document.getElementById('on-air-host'); 154 if (showEl) showEl.textContent = tc(t.title); 155 if (hostEl) hostEl.textContent = tc(t.artist); 156 bar.style.display = 'flex'; 157 }).catch(function() {}); 158 } 159 document.addEventListener('DOMContentLoaded', updateBar); 160 setInterval(updateBar, 60000); 161 window.addEventListener('focus', updateBar); 162 })(); 163 </script>
163 164
165<script> 166(function() { 167 var API = 'https://pbc-cms-production.up.railway.app'; 168 169 Promise.all([ 170 fetch(API + '/api/stations/calmaz').then(function(r){return r.json();}), 171 fetch(API + '/api/pages/calmaz-home').then(function(r){return r.json();}).catch(function(){return {}; }), 172 fetch(API + '/api/station-globals/calmaz').then(function(r){return r.json();}).catch(function(){return {};}) 173 ]).then(function(res) { 174 var s = res[0]; 175 var p = res[1]; 176 var globals = res[2] || {}; 177 function activePages() { 178 if (!globals.active_pages) return ['home','news','events','podcasts','schedule','shows','advertise','about','fcc','contact','public-inspection-help','employment','archive']; 179 if (Array.isArray(globals.active_pages)) return globals.active_pages; 180 try { return JSON.parse(globals.active_pages); } catch(e) { return ['home','news','events','podcasts','schedule','shows','advertise','about','fcc','contact','public-inspection-help','employment','archive']; } 181 } 182 var scheduleActive = activePages().indexOf('schedule') !== -1; 183 var scheduleSection = document.getElementById('schedule-section'); 184 if (scheduleSection) scheduleSection.style.display = scheduleActive ? 'block' : 'none'; 185 // home_video_sidebar_enabled controls video blocks only â sidebar always visible (recently played lives here) 186 var videosEnabled = !(globals.home_video_sidebar_enabled === false || globals.home_video_sidebar_enabled === 'false' || globals.home_video_sidebar_enabled === '0'); 187 if (videosEnabled) { 188 // Show the video blocks when enabled 189 var videoBlocks = document.querySelectorAll('#home-video-sidebar > div[style*="display:none"]'); 190 videoBlocks.forEach(function(el) { el.style.display = 'block'; }); 191 } 192 193 // on-air bar handled by standalone script below 194 195 // ââ HERO IMAGE âââââââââââââââââââââââââââââââââââââââââââââââ 196 // p.hero_image from CMS â prefix with API base URL 197 if (p.hero_image) { 198 var heroSection = document.getElementById('hero-section'); 199 var heroSrc = p.hero_image.startsWith('http') ? p.hero_image : p.hero_image.startsWith('/') ? 'https://pbc-cms-production.up.railway.app' + p.hero_image : 'https://pbc-cms-production.up.railway.app/' + p.hero_image; 200 if (heroSection) { 201 heroSection.style.minHeight = ''; 202 heroSection.style.display = 'block'; 203 heroSection.style.background = '#504395'; 204 heroSection.innerHTML = '<img src="' + heroSrc + '" alt="CALMAZ" style="width:100%;display:block;height:auto;" onerror="this.style.display=\'none\'" />'; 205 } 206 } 207 208 209 210 // ââ NEWS âââââââââââââââââââââââââââââââââââââââââââââââââââââ 211 fetch(API + '/api/news?limit=6').then(function(r){return r.json();}).then(function(d) { 212 var items = (d.items || d) || []; 213 var grid = document.getElementById('news-grid'); 214 if (!items.length) { grid.innerHTML = '<p style="color:#546e7a">No news yet.</p>'; return; } 215 grid.innerHTML = items.map(function(n) { 216 var img = n.image ? (n.image.startsWith('http') ? n.image : n.image.startsWith('/uploads/') ? 'https://pbc-cms-production.up.railway.app' + n.image : 'https://pbc-cms-production.up.railway.app/' + n.image) : 'images/news-placeholder.png'; 217 var date = n.published_at ? new Date(n.published_at.slice(0,10)+'T12:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}) : ''; 218 return '<a href="pages/article.html?id=' + n.id + '" class="news-card" style="text-decoration:none;color:inherit;display:block;">' 219 + '<img src="'+img+'" class="news-card-img" alt="'+n.headline+'" style="object-fit:contain;background:#f4f6f8;" onerror="this.src="images/news-placeholder.png"" />' 220 + '<div class="news-card-body">' 221 + (n.category ? '<div class="news-card-cat">'+n.category+'</div>' : '') 222 + '<div class="news-card-title">'+n.headline+'</div>' 223 + '<div class="news-card-date">'+date+'</div>' 224 + '</div></a>'; 225 }).join(''); 226 }).catch(function(){}); 227 228 // ââ SCHEDULE â from new schedule API, current + upcoming âââââ 229 fetch(API + '/api/schedule/calmaz').then(function(r){return r.json();}).then(function(d) { 230 var slots = d.slots || []; 231 var grid = document.getElementById('schedule-grid'); 232 if (!slots.length) { grid.innerHTML = '<p style="color:#546e7a">Schedule coming soon.</p>'; return; } 233 234 var now = new Date(); 235 var nowH = now.getHours(); 236 // Determine today's day group 237 var dow = now.getDay(); // 0=Sun,1=Mon,2=Tue,3=Wed,4=Thu,5=Fri,6=Sat 238 var todayGroup = dow === 0 ? 'sun' : dow === 6 ? 'sat' : dow === 1 ? 'mon' : 'tue-fri'; 239 240 // Get today's slots, sorted by start_hour 241 var todaySlots = slots.filter(function(sl) { 242 return sl.day_group === todayGroup || (todayGroup === 'mon' && sl.day_group === 'tue-fri'); 243 }); 244 // Prefer exact day_group match over tue-fri fallback for mon 245 if (todayGroup === 'mon') { 246 var monSlots = slots.filter(function(sl){ return sl.day_group === 'mon'; }); 247 var tuefriSlots = slots.filter(function(sl){ return sl.day_group === 'tue-fri'; }); 248 // Merge: for each hour, prefer mon slot, fall back to tue-fri 249 var merged = {};
250 tuefriSlots.forEach(function(sl){ merged[sl.start_hour] = sl; }); 251 monSlots.forEach(function(sl){ merged[sl.start_hour] = sl; }); // mon overrides 252 todaySlots = Object.values(merged).sort(function(a,b){ return a.start_hour - b.start_hour; }); 253 } else { 254 todaySlots = todaySlots.sort(function(a,b){ return a.start_hour - b.start_hour; }); 255 } 256 257 // Find current slot and next 5 258 var currentIdx = -1; 259 for (var i = 0; i < todaySlots.length; i++) { 260 if (todaySlots[i].start_hour <= nowH && todaySlots[i].end_hour > nowH) { 261 currentIdx = i; break; 262 } 263 } 264 var startFrom = currentIdx >= 0 ? currentIdx : todaySlots.findIndex(function(sl){ return sl.start_hour > nowH; }); 265 if (startFrom < 0) startFrom = 0; 266 var display = todaySlots.slice(startFrom, startFrom + 6); 267 268 function fmtH(h) { 269 if (h === 0) return '12:00 AM'; 270 if (h === 12) return '12:00 PM'; 271 return h < 12 ? h + ':00 AM' : (h-12) + ':00 PM'; 272 } 273 274 grid.innerHTML = display.map(function(sl, i) { 275 var isNow = sl.start_hour <= nowH && sl.end_hour > nowH; 276 return '<div class="schedule-card'+(isNow?' on-air-now':'')+'">' 277 + '<div class="schedule-time">'+(isNow?'▶ On Air Now — ':'')+fmtH(sl.start_hour)+' – '+fmtH(sl.end_hour)+'</div>' 278 + '<div class="schedule-show">'+(sl.name||'')+'</div>' 279 + '</div>'; 280 }).join(''); 281 }).catch(function(){}); 282 283 }).catch(function(e){ console.error('CALMAZ home error:', e); }); 284 285})(); 286</script>
vendor: 1 bytes, line 286
286
287<script src="js/player.js?v=17"></script>
287 288
288<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src='https://weatherwidget.io/js/widget.min.js';fjs.parentNode.insertBefore(js,fjs);}}(document,'script','weatherwidget-io-js');</script>
288 289
290<script> 291(function() { 292 var CMS_API = 'https://pbc-cms-production.up.railway.app'; 293 var POLL_MS = 30000; 294 var rpTimer = null; 295 296 function toTitleCase(str) { 297 var smalls = {'a':1,'an':1,'the':1,'and':1,'but':1,'or':1,'for':1,'in':1,'on':1,'at':1,'to':1,'of':1}; 298 return str.toLowerCase().replace(/\w\S*/g, function(word, idx) { 299 if (idx > 0 && smalls[word]) return word; 300 return word.charAt(0).toUpperCase() + word.slice(1); 301 }); 302 } 303 304 function parseMeta(meta) { 305 if (!meta) return { artist: '', title: 'Unknown' }; 306 var dash = meta.indexOf(' - '); 307 if (dash !== -1) { 308 return { 309 artist: toTitleCase(meta.substring(0, dash).trim()), 310 title: toTitleCase(meta.substring(dash + 3).trim()) 311 }; 312 } 313 return { artist: '', title: toTitleCase(meta.trim()) }; 314 } 315 316 function fmtTime(dateStr) { 317 if (!dateStr) return ''; 318 try { 319 var d = new Date(dateStr.replace(' ', 'T') + 'Z'); 320 if (isNaN(d.getTime())) return ''; 321 // Arizona is UTC-7 year-round (no DST) 322 var az = new Date(d.getTime() - 7 * 60 * 60 * 1000); 323 var h = az.getUTCHours(), m = az.getUTCMinutes(); 324 var ampm = h >= 12 ? 'PM' : 'AM'; 325 h = h % 12 || 12; 326 return h + ':' + (m < 10 ? '0' : '') + m + 'â¯' + ampm; 327 } catch(e) { return ''; } 328 } 329 330 function renderTracks(tracks) { 331 var list = document.getElementById('calmaz-rp-list'); 332 var footer = document.getElementById('calmaz-rp-footer'); 333 if (!list) return; 334 335 if (!tracks || !tracks.length) { 336 list.innerHTML = '<div style="font-size:12px;color:rgba(255,255,255,0.3);padding:8px 0;">No track data</div>'; 337 return; 338 } 339 340 var html = ''; 341 tracks.forEach(function(t, i) { 342 var p = parseMeta(t.meta); 343 var isNow = (i === 0 || t.is_current === true); 344 var time = t.localtime || fmtTime(t.created_at); 345 346 html += '<div style="display:flex;align-items:center;gap:10px;padding:8px 0;' + 347 (i > 0 ? 'border-top:1px solid rgba(255,255,255,0.06);' : '') + 348 (isNow ? 'background:rgba(72,126,166,0.1);margin:0 -20px;padding:8px 20px;border-radius:0;' : '') + 349 '">'; 350 351 // Index / now-playing dot 352 html += '<span style="font-size:9px;font-weight:800;color:' + 353 (isNow ? '#487ea6' : 'rgba(255,255,255,0.2)') + 354 ';width:14px;flex-shrink:0;text-align:center;">' + 355 (isNow ? '●' : i) + 356 '</span>'; 357 358 // Music note 359 html += '<span style="font-size:12px;color:' + 360 (isNow ? '#487ea6' : 'rgba(255,255,255,0.2)') + 361 ';flex-shrink:0;">♫</span>'; 362 363 // Track info 364 html += '<div style="flex:1;min-width:0;">'; 365 if (p.artist) { 366 html += '<div style="font-size:11px;font-weight:' + (isNow ? '800' : '600') + 367 ';color:' + (isNow ? '#fff' : 'rgba(255,255,255,0.75)') + 368 ';white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + p.artist + '</div>'; 369 html += '<div style="font-size:10px;color:rgba(255,255,255,0.42);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px;">' + p.title + '</div>'; 370 } else { 371 html += '<div style="font-size:11px;font-weight:' + (isNow ? '800' : '600') + 372 ';color:' + (isNow ? '#fff' : 'rgba(255,255,255,0.75)') + 373 ';white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + p.title + '</div>'; 374 } 375 html += '</div>'; 376 377 // Time 378 if (time) { 379 html += '<span style="font-size:9px;color:rgba(255,255,255,0.2);flex-shrink:0;white-space:nowrap;">' + time + '</span>'; 380 } 381 382 html += '</div>'; 383 }); 384 385 list.innerHTML = html; 386 } 387 388 function loadTracks() { 389 fetch(CMS_API + '/api/recent-tracks/kahm?ts=' + Date.now()) 390 .then(function(r) { return r.json(); }) 391 .then(function(tracks) { 392 if (!tracks || !tracks.length) return; 393 renderTracks(tracks.map(function(t, i) { 394 return { 395 meta: t.artist.toUpperCase() + ' - ' + t.title.toUpperCase(), 396 image_url: null, 397 created_at: null, 398 localtime: t.localtime, 399 is_current: i === 0 400 }; 401 })); 402 }) 403 .catch(function() { 404 var list = document.getElementById('calmaz-rp-list'); 405 if (list && list.children.length <= 1) { 406 list.innerHTML = '<div style="font-size:12px;color:rgba(255,255,255,0.3);padding:8px 0;">Unable to load track data</div>'; 407 } 408 }); 409 } 410 411 document.addEventListener('DOMContentLoaded', function() { 412 // Only run if the widget is on the page 413 if (!document.getElementById('calmaz-recently-played')) return; 414 loadTracks(); 415 rpTimer = setInterval(loadTracks, POLL_MS); 416 }); 417})(); 418</script>
418 419
420<script> 421// ââ CALMAZ Hero Carousel ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 422(function() { 423 var CMS = 'https://pbc-cms-production.up.railway.app'; 424 var slides = []; 425 var current = 0; 426 var timer = null; 427 var INTERVAL = 5000; // default; overridden by globals below 428 429 function renderCarousel(data) { 430 var wrap = document.getElementById('calmaz-carousel-wrap'); 431 var container = document.getElementById('calmaz-carousel-slides'); 432 var caption = document.getElementById('calmaz-carousel-caption'); 433 var captText = document.getElementById('calmaz-carousel-caption-text'); 434 var prev = document.getElementById('calmaz-carousel-prev'); 435 var next = document.getElementById('calmaz-carousel-next'); 436 var dotsEl = document.getElementById('calmaz-carousel-dots'); 437 var placeholder = document.getElementById('calmaz-carousel-placeholder'); 438 439 if (!data || !data.length) { 440 if (placeholder) placeholder.innerHTML = '<span style="color:rgba(255,255,255,0.2);font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;">No slides yet</span>'; 441 return; 442 } 443 444 slides = data; 445 if (placeholder) placeholder.style.display = 'none'; 446 447 // Build slides 448 container.innerHTML = data.map(function(s) { 449 var src = s.image_url.startsWith('http') ? s.image_url : CMS + '/' + s.image_url; 450 return '<div style="min-width:100%;width:100%;flex-shrink:0;">' 451 + '<img src="' + src + '" alt="' + (s.caption || '') + '" ' 452 + 'style="width:100%;display:block;aspect-ratio:16/9;object-fit:cover;" ' 453 + 'loading="lazy" />' 454 + '</div>'; 455 }).join(''); 456 457 // Dots 458 dotsEl.style.display = 'flex'; 459 dotsEl.innerHTML = data.map(function(_, i) { 460 return '<button onclick="calmazCarouselGoTo(' + i + ')" ' 461 + 'id="calmaz-dot-' + i + '" ' 462 + 'style="width:8px;height:8px;border-radius:50%;border:none;cursor:pointer;padding:0;transition:background .2s,transform .2s;background:' + (i===0?'#fff':'rgba(255,255,255,0.4)') + ';transform:' + (i===0?'scale(1.3)':'scale(1)') + ';">' 463 + '</button>'; 464 }).join(''); 465 466 // Arrows (only if multiple slides) 467 if (data.length > 1) { 468 prev.style.display = 'flex'; 469 next.style.display = 'flex'; 470 } 471 472 // Caption 473 if (data[0].caption) { 474 caption.style.display = 'block'; 475 captText.textContent = data[0].caption; 476 } 477 478 // Start autoplay 479 startTimer(); 480 481 // Pause on hover 482 wrap.addEventListener('mouseenter', function() { clearInterval(timer); }); 483 wrap.addEventListener('mouseleave', startTimer); 484 } 485 486 function goTo(idx) { 487 if (!slides.length) return; 488 var container = document.getElementById('calmaz-carousel-slides'); 489 var caption = document.getElementById('calmaz-carousel-caption'); 490 var captText = document.getElementById('calmaz-carousel-caption-text'); 491 492 var next = ((idx % slides.length) + slides.length) % slides.length; 493 // If wrapping (e.g. lastâfirst or firstâlast), disable transition briefly 494 if (Math.abs(next - current) > 1) { 495 container.style.transition = 'none'; 496 setTimeout(function() { 497 container.style.transition = 'transform 0.5s cubic-bezier(0.4,0,0.2,1)'; 498 }, 20); 499 } 500 current = next; 501 container.style.transform = 'translateX(-' + (current * 100) + '%)'; 502 503 // Update dots 504 slides.forEach(function(_, i) { 505 var dot = document.getElementById('calmaz-dot-' + i); 506 if (dot) { 507 dot.style.background = i === current ? '#fff' : 'rgba(255,255,255,0.4)'; 508 dot.style.transform = i === current ? 'scale(1.3)' : 'scale(1)'; 509 } 510 }); 511 512 // Update caption 513 var cap = slides[current].caption; 514 if (cap) { 515 caption.style.display = 'block'; 516 captText.textContent = cap; 517 } else { 518 caption.style.display = 'none'; 519 } 520 } 521 522 function startTimer() { 523 clearInterval(timer); 524 if (slides.length > 1) { 525 timer = setInterval(function() { goTo(current + 1); }, INTERVAL); 526 } 527 } 528 529 // Public functions for onclick handlers 530 window.calmazCarouselMove = function(dir) { clearInterval(timer); goTo(current + dir); startTimer(); }; 531 window.calmazCarouselGoTo = function(idx) { clearInterval(timer); goTo(idx); startTimer(); }; 532 533 // Load slides 534 document.addEventListener('DOMContentLoaded', function() { 535 // Read carousel delay from station globals 536 fetch(CMS + '/api/station-globals/calmaz') 537 .then(function(r) { return r.json(); }) 538 .then(function(g) {
539 var delay = parseInt(g.carousel_delay_seconds || 0, 10); 540 if (delay >= 2 && delay <= 30) INTERVAL = delay * 1000; 541 }) 542 .catch(function() {}); 543 544 fetch(CMS + '/api/hero-slides/kahm') 545 .then(function(r) { return r.json(); }) 546 .then(renderCarousel) 547 .catch(function() { 548 var ph = document.getElementById('calmaz-carousel-placeholder'); 549 if (ph) ph.innerHTML = '<span style="color:rgba(255,255,255,0.2);font-size:12px;letter-spacing:.08em;text-transform:uppercase;">No slides configured</span>'; 550 }); 551 }); 552})(); 553</script>
vendor: 1 bytes, line 553
553
554<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"2d1d2fa67a294ce096b81cf7f5f35834","r":1,"spa":2}' crossorigin="anonymous"></script>
554 555</body> 556</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.