PageSourceSearch

https://calmarizona.com/

html calmarizona.com collected 2026-10-02 01:24:48 UTC 27,510 bytes, 556 lines download raw bytes

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)'">&#8249;</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)'">&#8250;</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&hellip;</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;">&#9654; 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);">&#128250; 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);">&#127925; Today&#39;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 &rsaquo;</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;">&#9835; 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 &rsaquo;</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=&quot;images/news-placeholder.png&quot;" />'
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?'&#9654; On Air Now &mdash; ':'')+fmtH(sl.start_hour)+' &ndash; '+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 ? '&#9679;' : 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;">&#9835;</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.