PageSourceSearch

https://www.funradio.sk/v3/app.js?v=32

js funradio.sk collected 2026-10-02 05:31:52 UTC 77,668 bytes, 1,031 lines download raw bytes

1/* ============================================================
2   FUN RÁDIO · V3 "ORGANIC" · správanie
3   ============================================================ */
4(() => {
5  'use strict';
6
7  /* Poistka proti dvom prehrávačom naraz. Keď sa skript z akéhokoľvek dôvodu
8     spustí v tom istom dokumente druhýkrát (mäkký prechod medzi stránkami,
9     návrat cez históriu), vznikol by druhý Audio a hralo by živé vysielanie
10     spolu s epizódou. Druhé spustenie sa preto ticho ukončí. */
11  if (window.__frAppInit) return;
12  window.__frAppInit = true;
13
14  const $ = (s, r = document) => r.querySelector(s);
15  const $$ = (s, r = document) => [...r.querySelectorAll(s)];
16  const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
17  const smooth = t => t * t * (3 - 2 * t);
18
19  const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
20  if (reduced) document.body.classList.add('reduced');
21
22  /* ---------- ikonky ---------- */
23  const ICON_PLAY = '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.53.85l10-6.5a1 1 0 0 0 0-1.7l-10-6.5A1 1 0 0 0 8 5.5z"/></svg>';
24  const ICON_PAUSE = '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><rect x="6" y="5" width="4.4" height="14" rx="1.4"/><rect x="13.6" y="5" width="4.4" height="14" rx="1.4"/></svg>';
25  const size = (svg, px) => svg.replace('width="16" height="16"', `width="${px}" height="${px}"`);
26
27  /* ============================================================
28     NAVIGÁCIA · scvrknutie pri scrolle dole, rozvinutie hore
29     ============================================================ */
30  const nav = $('.nav');
31  let lastY = scrollY;
32  let navLock = false;
33  function navOnScroll() {
34    if (!nav || navLock) return;
35    const y = scrollY;
36    nav.classList.toggle('nav--scrolled', y > 8);
37    lastY = y;
38  }
39
40  /* overlay menu */
41  const burger = $('.nav-burger');
42  const menuOpen = () => { document.body.classList.add('menu-open'); navLock = true; burger?.setAttribute('aria-expanded', 'true'); };
43  const menuClose = () => { document.body.classList.remove('menu-open'); navLock = false; burger?.setAttribute('aria-expanded', 'false'); };
44  burger?.addEventListener('click', menuOpen);
45  $('.menu-close')?.addEventListener('click', menuClose);
46  $$('.menu-links a').forEach(a => a.addEventListener('click', menuClose));
47
48  /* ============================================================
49     REVEAL ON SCROLL · presunuté do /v3/motion.js (GSAP ScrollTrigger).
50     Starý .fx → .in mechanizmus je VYPNUTÝ; tu ostáva iba trigger
51     odometrových počítadiel, ktorý beží so scroll listenerom nižšie.
52     ============================================================ */
53  function countersOnScroll() {
54    countersCheck();
55  }
56
57  /* ---------- počítadlá ---------- */
58  let countersDone = false;
59  const statsBox = $('[data-stats]');
60  function countersCheck() {
61    if (countersDone || !statsBox) return;
62    const vh = innerHeight || document.documentElement.clientHeight || 800;
63    if (statsBox.getBoundingClientRect().top > vh * 0.85) return;
64    countersDone = true;
65    $$('[data-count]').forEach(el => {
66      const target = parseFloat(el.dataset.count);
67      const fmt = n => el.dataset.space !== undefined
68        ? Math.round(n).toLocaleString('sk-SK')
69        : String(Math.round(n));
70      if (!isFinite(target)) return;
71      if (reduced) { el.textContent = fmt(target); return; }
72      const ciel = fmt(target);
73      /* Odometer: písmo nemá tabuľkové číslice, takže každá medzihodnota
74         mala inú šírku a zámok cez min-width poskakovanie nezastavil.
75         Každá číslica dostane pevný stĺpik široký presne podľa cieľovej
76         číslice — šírka celku sa počas animácie nemení, číslice tikajú
77         na mieste. Po dobehnutí sa vráti čistý text: stĺpiky sú v px
78         a písmo je viewportové (clamp), po otočení telefónu by sa rozišli. */
79      const spusti = () => {
80        try {
81          const cs = getComputedStyle(el);
82          const ctx = document.createElement('canvas').getContext('2d');
83          if (!ctx) { el.textContent = ciel; return; }
84          ctx.font = `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
85          const ls = parseFloat(cs.letterSpacing) || 0;
86          el.textContent = '';
87          el.style.whiteSpace = 'nowrap';
88          const sloty = [];
89          for (const znak of ciel) {
90            const s = document.createElement('span');
91            s.textContent = znak;
92            if (/\d/.test(znak)) {
93              s.style.display = 'inline-block';
94              s.style.width = (ctx.measureText(znak).width + ls) + 'px';
95              s.style.textAlign = 'center';
96              sloty.push(s);
97            }
98            el.appendChild(s);
99          }
100          const zapis = n => {
101            /* veľké čísla majú po celý čas rovnaký počet číslic ako cieľ
102               (štart nepodlieza rád), takže sadnú na stĺpiky 1:1; malé
103               čísla od nuly sa zarovnávajú doprava ako na tachometri */
104            const cifry = fmt(n).replace(/\D/g, '');
105            const posun = sloty.length - cifry.length;
106            for (let i = 0; i < sloty.length; i++) {
107              sloty[i].textContent = i >= posun ? cifry[i - posun] : '';
108            }
109          };
110          /* Štart nesmie podliezť rád cieľa: 96 % z 1 035 000 je 993 600 —
111             menej číslic, iné zoskupenie a zlomený riadok uprostred
112             animácie. Preto sa dvíha najmenej na okrúhly rád (1 000 000). */
113          const rad = Math.pow(10, String(Math.round(target)).length - 1);
114          const from = target > 999 ? Math.max(target * 0.96, rad) : 0;
115          const dur = target > 999 ? 900 : 1100;
116          const t0 = performance.now();
117          const easeOut = t => 1 - Math.pow(1 - t, 3);   /* power3.out */
118          const tick = now => {
119            const p = clamp((now - t0) / dur, 0, 1);
120            if (p < 1) { zapis(from + (target - from) * easeOut(p)); requestAnimationFrame(tick); }
121            else el.textContent = ciel;
122          };
123          requestAnimationFrame(tick);
124        } catch (e) { el.textContent = ciel; }
125      };
126      /* Písmo sa načítava so swap — merať sa smie až hotové písmo, inak
127         sa stĺpiky zmerajú vo fallbacku a po výmene písma nesedia. */
128      if (document.fonts && document.fonts.ready) document.fonts.ready.then(spusti);
129      else spusti();
130    });
131  }
132
133  /* ============================================================
134     HERO FLOAT · jemný paralax chipov a kariet (max 12 px)
135     ============================================================ */
136  const floatEls = $$('[data-float]');
137  function floatFrame() {
138    if (reduced) return;
139    const y = Math.min(scrollY, 500);
140    for (const el of floatEls) {
141      const t = clamp(y * parseFloat(el.dataset.float), -12, 12);
142      el.style.translate = `0 ${t}px`;
143    }
144  }
145
146  /* spoločný scroll handler */
147  let raf = 0;
148  addEventListener('scroll', () => {
149    navOnScroll();
150    if (raf) return;
151    raf = requestAnimationFrame(() => { countersOnScroll(); floatFrame(); raf = 0; });
152  }, { passive: true });
153  addEventListener('resize', countersOnScroll, { passive: true });
154  addEventListener('load', countersOnScroll);
155  countersOnScroll();
156
157  /* ============================================================
158     STACK DECK · Naživo teraz
159     ============================================================ */
160  (function initDeck() {
161    const deck = $('[data-deck]');
162    if (!deck) return;
163    const cards = $$('.deck-card', deck);
164    let order = [...cards];
165    let busy = false;
166
167    /* bodky */
168    const dotsWrap = $('.deck-dots');
169    cards.forEach((c, i) => {
170      c.dataset.index = String(i);
171      const d = document.createElement('button');
172      d.className = 'dot';
173      d.setAttribute('aria-label', `Karta ${i + 1}: ${c.dataset.title || ''}`);
174      d.addEventListener('click', () => {
175        if (busy) return;
176        while (order[0] !== c) order.push(order.shift());
177        layout();
178      });
179      dotsWrap?.appendChild(d);
180    });
181    const dots = $$('.dot', dotsWrap);
182
183    const DEPTH = [
184      { y: 0,   s: 1,    o: 1 },
185      { y: -22, s: .955, o: .9 },
186      { y: -42, s: .91,  o: .6 },
187      { y: -58, s: .87,  o: 0 },
188    ];
189
190    function place(card, d) {
191      const p = DEPTH[Math.min(d, DEPTH.length - 1)];
192      card.style.zIndex = String(order.length - d);
193      card.style.transform = `translateY(${p.y}px) scale(${p.s})`;
194      card.style.opacity = String(p.o);
195      card.classList.toggle('is-top', d === 0);
196      card.style.pointerEvents = d === 0 ? 'auto' : 'none';
197      card.setAttribute('aria-hidden', d === 0 ? 'false' : 'true');
198    }
199
200    function layout(skip) {
201      order.forEach((card, d) => { if (card !== skip) place(card, d); });
202      const idx = Number(order[0].dataset.index);
203      dots.forEach((dt, i) => dt.classList.toggle('active', i === idx));
204    }
205
206    /* posun dopredu: vrchná karta odletí (dir = -1 doľava, 1 doprava) */
207    function next(dir = -1) {
208      if (busy) return;
209      const top = order[0];
210      order.push(order.shift());
211      if (reduced) { layout(); return; }
212      busy = true;
213      top.classList.remove('dragging');
214      top.classList.add('flying');
215      top.style.zIndex = '20';
216      top.style.transform = `translate(${dir * 120}%, -24px) rotate(${dir * 14}deg)`;
217      layout(top); // ostatné sa pružne posunú dopredu už počas letu
218      setTimeout(() => {
219        top.classList.remove('flying');
220        top.classList.add('no-trans');
221        top.style.boxShadow = '';
222        layout();               // karta sa bez animácie zaradí na spodok
223        void top.offsetWidth;   // reflow, aby ďalší pohyb už animoval
224        top.classList.remove('no-trans');
225        busy = false;
226      }, 460);
227    }
228
229    /* posun dozadu: spodná karta priletí navrch */
230    function prev() {
231      if (busy) return;
232      const card = order.pop();
233      order.unshift(card);
234      if (reduced) { layout(); return; }
235      busy = true;
236      card.classList.add('no-trans');
237      card.style.zIndex = '20';
238      card.style.opacity = '1';
239      card.style.transform = 'translate(115%, -24px) rotate(13deg)';
240      void card.offsetWidth;
241      card.classList.remove('no-trans');
242      layout(); // priletí na vrch, ostatné sa zasunú dozadu
243      setTimeout(() => { busy = false; }, 500);
244    }
245
246    /* drag vrchnej karty (pointer events: myš aj dotyk) */
247    let drag = null;
248    deck.addEventListener('pointerdown', e => {
249      if (reduced || busy) return;
250      const top = order[0];
251      if (!top.contains(e.target)) return;
252      if (e.target.closest('button, a')) return;
253      drag = { id: e.pointerId, x0: e.clientX, y0: e.clientY, dx: 0, active: false };
254      top.setPointerCapture(e.pointerId);
255    });
256    deck.addEventListener('pointermove', e => {
257      if (!drag || e.pointerId !== drag.id) return;
258      const top = order[0];
259      drag.dx = e.clientX - drag.x0;
260      const dy = e.clientY - drag.y0;
261      if (!drag.active && Math.abs(drag.dx) > 6) {
262        drag.active = true;
263        top.classList.add('dragging');
264      }
265      if (drag.active) {
266        top.style.transform = `translate(${drag.dx}px, ${dy * 0.35}px) rotate(${drag.dx * 0.05}deg)`;
267        const k = Math.min(Math.abs(drag.dx) / deck.offsetWidth, 1);
268        top.style.boxShadow = `0 ${16 + 26 * k}px ${40 + 44 * k}px rgba(61,18,38,${(0.10 + 0.16 * k).toFixed(3)})`;
269      }
270    });
271    function endDrag(e) {
272      if (!drag || e.pointerId !== drag.id) return;
273      const { dx, active } = drag;
274      drag = null;
275      if (!active) return;
276      const top = order[0];
277      top.classList.remove('dragging');
278      if (Math.abs(dx) > deck.offsetWidth * 0.28) {
279        next(Math.sign(dx) || -1);           // za prahom: odlet a zaradenie na spodok
280      } else {
281        top.style.boxShadow = '';
282        layout();                            // pred prahom: pružný návrat
283      }
284    }
285    deck.addEventListener('pointerup', endDrag);
286    deck.addEventListener('pointercancel', endDrag);
287
288    /* šípky a klávesnica */
289    $('[data-deck-next]')?.addEventListener('click', () => next(-1));
290    $('[data-deck-prev]')?.addEventListener('click', prev);
291    addEventListener('keydown', e => {
292      if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
293      const t = document.activeElement;
294      if (t && /^(input|textarea|select)$/i.test(t.tagName)) return;
295      if (document.body.classList.contains('player-open') || document.body.classList.contains('menu-open')) return;
296      const r = deck.getBoundingClientRect();
297      if (r.bottom < 0 || r.top > innerHeight) return;
298      e.key === 'ArrowRight' ? next(-1) : prev();
299    });
300
301    layout();
302  })();
303
304  /* ============================================================
305     STACK LIST · podcasty (accordion, naraz jedna otvorená)
306     ============================================================ */
307  $$('.stackl-card').forEach(card => {
308    const head = $('.stackl-head', card);
309    const body = $('.stackl-body', card);
310    head.addEventListener('click', () => {
311      const isOpen = card.classList.contains('open');
312      $$('.stackl-card.open').forEach(c => {
313        c.classList.remove('open');
314        $('.stackl-body', c).style.maxHeight = '0px';
315        $('.stackl-head', c).setAttribute('aria-expanded', 'false');
316      });
317      if (!isOpen) {
318        card.classList.add('open');
319        body.style.maxHeight = body.scrollHeight + 'px';
320        head.setAttribute('aria-expanded', 'true');
321      }
322    });
323  });
324  addEventListener('resize', () => {
325    const open = $('.stackl-card.open .stackl-body');
326    if (open) open.style.maxHeight = open.scrollHeight + 'px';
327  }, { passive: true });
328
329  /* ============================================================
330     MARQUEE · zduplikovanie obsahu pre nekonečnú slučku
331     ============================================================ */
332  const mqTrack = $('.marquee-track');
333  if (mqTrack) mqTrack.innerHTML += mqTrack.innerHTML;
334
335  /* ============================================================
336     MAGNETICKÉ CTA · len desktop (pointer:fine), jemný ťah max 4 px
337     translate je samostatná vlastnosť → nekoliduje s :hover transform
338     ============================================================ */
339  if (!reduced && matchMedia('(pointer: fine)').matches) {
340    $$('[data-magnetic]').forEach(el => {
341      el.addEventListener('pointermove', e => {
342        const r = el.getBoundingClientRect();
343        const mx = (e.clientX - (r.left + r.width / 2)) / ((r.width / 2) || 1);
344        const my = (e.clientY - (r.top + r.height / 2)) / ((r.height / 2) || 1);
345        el.style.translate = clamp(mx * 4, -4, 4).toFixed(1) + 'px ' + clamp(my * 4, -4, 4).toFixed(1) + 'px';
346      });
347      el.addEventListener('pointerleave', () => { el.style.translate = '0 0'; });
348    });
349  }
350
351  /* ============================================================
352     PREHRÁVAČ · jeden Audio element pre celý web
353     ============================================================ */
354  const AVATAR_ZAKLAD = '/assets/img/funradio-ikona.png';
355  const STATIONS = [
356    { id: 'fun',    name: 'Fun Rádio',      sub: 'Hity, ktoré milujete',        show: 'Hity, ktoré milujete', img: null, orb: 'magenta', tone: 'blush',      url: 'https://stream.funradio.sk:8000/fun128.mp3' },
357    { id: 'dance',  name: 'Fun Dance',      sub: 'Tanečné hity nonstop',       show: 'Fun Dance',            img: null, orb: 'magenta', tone: 'peri',       url: 'https://stream.funradio.sk:8000/dance128.mp3' },
358    { id: 'chill',  name: 'Fun Chill',      sub: 'Pohodové tempo na oddych',   show: 'Fun Chill',            img: null, orb: 'mint',    tone: 'mint-soft',  url: 'https://stream.funradio.sk:8000/chill128.mp3' },
359    { id: '8090',   name: 'Fun 80s a 90s',  sub: 'Legendy dvoch deká
359d',        show: 'Fun 80s a 90s',        img: null, orb: 'peri',    tone: 'peri-soft',  url: 'https://stream.funradio.sk:8000/80-90-128.mp3' },
360    { id: 'summer', name: 'Fun Summer',     sub: 'Letný soundtrack',           show: 'Fun Summer',           img: null, orb: 'tang',    tone: 'tang-soft',  url: 'https://stream.funradio.sk:8000/summer128.mp3' },
361    { id: 'cs',     name: 'Fun CS',         sub: 'Česko a Slovensko v hitoch', show: 'Fun CS',               img: null, orb: 'blush',   tone: 'blush-soft', url: 'https://stream.funradio.sk:8000/cs128.mp3' },
362    { id: 'mile',   name: 'Fun Mileniálky', sub: 'Hity generácie Y',           show: 'Fun Mileniálky',       img: null, orb: 'butter',  tone: 'butter-soft', url: 'https://stream.funradio.sk:8000/milenialky128.mp3' },
363  ];
364  const EPISODES = [
365    { id: "ep1667", name: "Naučila som sa, že život sa nemusí prežiť v tlaku, ale dá sa…", sub: "herečka a speváčka Nela Pocisková · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/naucila-som-sa-ze-zivot-sa-nemusi-prezit-v-tlaku-ale-da-sa-aj-tesit-herecka-a-spevacka-nela-pociskova/audio.mp3" },
366    { id: "ep1664", name: "Kavej nie je \"crazy\" komédia, je hraná na vážno", sub: "producent Dano Dangl a režisér Lukáš Zednikovič · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/kavej-nie-je-crazy-komedia-je-hrana-na-vazno-producent-dano-dangl-a-reziser-lukas-zednikovic/audio.mp3" },
367    { id: "ep1661", name: "Najdôležitejší je pocit. Človek sa v šatách musí páčiť sám sebe", sub: "návrhárka Lýdia Eckhardt · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/najdolezitejsi-je-pocit-clovek-sa-v-satach-musi-pacit-sam-sebe-navrharka-lydia-eckhardt/audio.mp3" },
368    { id: "ep1657", name: "Neurochirurgovia sa dotýkajú mozgu veľmi málo", sub: "neurochirurg Svorad Trnovec · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/neurochirurgovia-sa-dotykaju-mozgu-velmi-malo-neurochirurg-svorad-trnovec/audio.mp3" },
369    { id: "ep1653", name: "Vzory zo slovenských krojov premieňa na šperky. Neexistuje je…", sub: "šperkárka Petra Tóth · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/vzory-zo-slovenskych-krojov-premiena-na-sperky-neexistuje-jeden-slovensky-kroj-sperkarka-petra-toth/audio.mp3" },
370    { id: "ep1650", name: "Po prvej epizóde Milujem Slovensko som dostal veľkú výtku, že…", sub: "režisér Rado Štefanov · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/po-prvej-epizode-milujem-slovensko-som-dostal-velku-vytku-ze-to-je-vela-reziser-rado-stefanov/audio.mp3" },
371    { id: "ep1645", name: "Kde inde sa majú deti naučiť spolu fungovať aj keď sú rozdiel…", sub: "pedagogička Anna Symington-Maar · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/kde-inde-sa-maju-deti-naucit-spolu-fungovat-aj-ked-su-rozdielne-ak-nie-v-skole-pedagogicka-anna-symington-maar/audio.mp3" },
372    { id: "ep1644", name: "K foteniu známych ľudí som sa dostala postupne, všetko má svo…", sub: "fotografka Katarína Hoffman Šlesárová · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/k-foteniu-znamych-ludi-som-sa-dostala-postupne-vsetko-ma-svoj-cas-fotografka-katarina-hoffman-slesarova/audio.mp3" },
373    { id: "ep1638", name: "S pokročilosťou umelej inteligencie sa to preháňa. Filozofka…", sub: "Podcast · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/s-pokrocilostou-umelej-inteligencie-sa-to-prehana-filozofka-daniela-vacek/audio.mp3" },
374    { id: "ep1637", name: "Jedno dieťa alebo dve? Q&A Part 4", sub: "Podcast · Adela a Sajfa", show: "Adela a Sajfa", img: "/assets/img/show-adela-a-sajfa.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/adela-a-sajfa/jedno-dieta-alebo-dve-qa-part-4/audio.mp3" },
375    { id: "ep1585", name: "ĎUĎOVI RODIČIA VYSLALI SIGNÁL feat. Hex", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/dudovi-rodicia-vyslali-signal-feat-hex/audio.mp3" },
376    { id: "ep1584", name: "MILUJEME PIKAJDY feat. Evelyn", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/milujeme-pikajdy-feat-evelyn/audio.mp3" },
377    { id: "ep1388", name: "\"Je fér, že spievaš 40 rokov a futbalista za kratší čas zarob…", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/je-fer-ze-spievas-40-rokov-a-futbalista-za-kratsi-cas-zarobi-viac-feat-dara-rolins/audio.mp3" },
378    { id: "ep1387", name: "\"Raz ma na festivale nepustili na stage,\" feat. Tina", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/raz-ma-na-festivale-nepustili-na-stage-feat-tina/audio.mp3" },
379    { id: "ep1386", name: "\"Bol si už niekedy celý deň v pyžame?\" feat. Marián Gáborík", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/bol-si-uz-niekedy-cely-den-v-pyzame-feat-marian-gaborik/audio.mp3" },
380    { id: "ep1385", name: "\"Ty si sa mýlila, ten príbeh pokračuje ďalej,\" feat. Sandra z…", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/ty-si-sa-mylila-ten-pribeh-pokracuje-dalej-feat-sandra-z-dejepis-inak/audio.mp3" },
381    { id: "ep1384", name: "\"Keď si hladný, je dobrý aj spotený,\" feat. Peter Bič Project", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/ked-si-hladny-je-dobry-aj-spoteny-feat-peter-bic-project/audio.mp3" },
382    { id: "ep1344", name: "„Povedz vtip nie je stand-up,“ feat. Joe Trendy", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/povedz-vtip-nie-je-stand-up-feat-joe-trendy/audio.mp3" },
383    { id: "ep1345", name: "„Rišo, ty máš fest veľkú hlavu,“ feat. Zuzka Smatanová a Rich…", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/riso-ty-mas-fest-velku-hlavu-feat-zuzka-smatanova-a-richard-autner/audio.mp3" },
384    { id: "ep1343", name: "„Prdla si si počas koncertu?“ feat. Emma Drobná", sub: "Podcast · Sajfa šou", show: "Sajfa šou", img: "/assets/img/show-sajfa-sou.webp", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/sajfa-sou/prdla-si-si-pocas-koncertu-feat-emma-drobna/audio.mp3" },
385    { id: "ep1663", name: "Môj prvý bikepark? Lopata a igelit na teniskách – staviteľ tr…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/moj-prvy-bikepark-lopata-a-igelit-na-teniskach-stavitel-trailov-jaroslav-lucan/audio.mp3" },
386    { id: "ep1660", name: "V pesničke \"Nikdy nebolo lepšie\" je v úvode niečo, o čom nikt…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/v-pesnicke-nikdy-nebolo-lepsie-je-v-uvode-nieco-o-com-nikto-nevie-producent-tomas-sloboda/audio.mp3" },
387    { id: "ep1658", name: "V našej práci neexistuje home office ani polovičný úväzok – M…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/v-nasej-praci-neexistuje-home-office-ani-polovicny-uvazok-martin-bandzak-z-organizacie-magna/audio.mp3" },
388    { id: "ep1654", name: "Máš v Japonsku stretko o deviatej. Prídeš na deviatu? Meškáš!…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/mas-v-japonsku-stretko-o-deviatej-prides-na-deviatu-meskas-japonologicka-nina-frankova/audio.mp3" },
389    { id: "ep1651", name: "“Doplnky stravy dnes berieme ako Lentilky,“ holistická mentor…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/doplnky-stravy-dnes-berieme-ako-lentilky-holisticka-mentorka-zdravia-mirka-janigova/audio.mp3" },
390    { id: "ep1646", name: "Oškvarky sú najlepšou potravou na ultratrailové behy – ultram…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/oskvarky-su-najlepsou-potravou-na-ultratrailove-behy-ultramaratonec-jaroslav-dej/audio.mp3" },
391    { id: "ep1641", name: "Na svojej prvej opere som zaspal – operný spevák Pavol Kubáň", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/na-svojej-prvej-opere-som-zaspal-operny-spevak-pavol-kuban/audio.mp3" },
392    { id: "ep1639", name: "Do jaskynnej kancelárie šľape hodiny a drepuje, aby tam nezam…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/do-jaskynnej-kancelarie-slape-hodiny-a-drepuje-aby-tam-nezamrzol-svetelny-dizajner-jan-ptacin/audio.mp3" },
393    { id: "ep1634", name: "Zvieratá v ZOO jedia kvalitnejšie jedlo ako my – hovorkyňa ZO…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/zvierata-v-zoo-jedia-kvalitnejsie-jedlo-ako-my-hovorkyna-zoo-bratislava-zuzana-dzurdzikova/audio.mp3" },
394    { id: "ep1632", name: "Bola na konzultácii vo Viedni a pozvali ju na stáž do New Yor…", sub: "Podcast · Talkshow so Šarkanom", show: "Talkshow so Šarkanom", img: "/assets/img/show-nedelna-talkshow.webp", tone: "peri", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/talkshow-so-sarkanom/bola-na-konzultacii-vo-viedni-a-pozvali-ju-na-staz-do-new-yorku-architektka-lenka-petrakova/audio.mp3" },
395    { id: "ep1668", name: "175 NÓRI PREPISJÚ HISTÓRIU, NEYMAR SA LÚČIL S REPRE", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/175-nori-prepisju-historiu-neymar-sa-lucil-s-repre/audio.mp3" },
396    { id: "ep1665", name: "174 MS VO FUTBALE S REKORDAMI A PLAYOFF", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/174-ms-vo-futbale-s-rekordami-a-playoff/audio.mp3" },
397    { id: "ep1662", name: "173 RONALDO A MESSI SÚ AKO ANDROID A iPHONE", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/173-ronaldo-a-messi-su-ako-android-a-iphone/audio.mp3" },
398    { id: "ep1659", name: "172 ŠTYRI ROKY SOM NEVIDEL FUTBAL feat. Števo Eisele", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/172-styri-roky-som-nevidel-futbal-feat-stevo-eisele/audio.mp3" },
399    { id: "ep1655", name: "171 NA MS VO FUTBALE SI NIKOHO NEVIEM PREDSTAVIŤ AKO VÍŤAZA", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/171-na-ms-vo-futbale-si-nikoho-neviem-predstavit-ako-vitaza/audio.mp3" },
400    { id: "ep1652", name: "170 GENERÁCIA VICEMAJSTROV SVETA", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/170-generacia-vicemajstrov-sveta/audio.mp3" },
401    { id: "ep1649", name: "169 VEĽKÁ TIPOVAČKA NA VÍŤAZOV A CYKLISTA NA \"SEX\"", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/169-velka-tipovacka-na-vitazov-a-cyklista-na-sex/audio.mp3" },
402    { id: "ep1643", name: "168 ARSENAL TO DAL, AJ MONTREAL TO DAL", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/168-arsenal-to-dal-aj-montreal-to-dal/audio.mp3" },
403    { id: "ep1640", name: "167 KIMI VEDEL \"VYPIC\" feat. Števo Eisele", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/167-kimi-vedel-vypic-feat-stevo-eisele/audio.mp3" },
404    { id: "ep1635", name: "166 STRIEBRO JE OBROVSKÝ ÚSPECH, AJ KEĎ BOĽAVÝ", sub: "Podcast · FUNúšik", show: "FUNúšik", img: "/assets/img/pod-funusik.png", tone: "mint", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/funusik/166-striebro-je-obrovsky-uspech-aj-ked-bolavy/audio.mp3" },
405    { id: "ep1666", name: "festival season", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/festival-season/audio.mp3" },
406    { id: "ep1656", name: "podľa Michaela Jacksona", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/podla-michaela-jacksona/audio.mp3" },
407    { id: "ep1647", name: "cafe del fun", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/cafe-del-fun/audio.mp3" },
408    { id: "ep1636", name: "afrobeat", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/afrobeat/audio.mp3" },
409    { id: "ep1619", name: "leto podľa nás", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/leto-podla-nas/audio.mp3" },
410    { id: "ep1611", name: "podľa Harryho Stylesa", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/podla-harryho-stylesa/audio.mp3" },
411    { id: "ep1604", name: "5 pesničiek 5 krajín", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/5-pesniciek-5-krajin/audio.mp3" },
412    { id: "ep1593", name: "hudobné udalosti", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/hudobne-udalosti/audio.mp3" },
413    { id: "ep1583", name: "podľa Bruna Marsa", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/podla-bruna-marsa/audio.mp3" },
414    { id: "ep1579", name: "Silvester 2025", sub: "Podcast · PODĽA MŇA", show: "PODĽA MŇA", img: "/assets/img/pod-podla-mna.png", tone: "butter", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/podla-mna/silvester-2025/audio.mp3" },
415    { id: "ep1534", name: "KERKA NA NEPODSTATNOM MIESTE. V SOBRANCIACH?", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/kerka-na-nepodstatnom-mieste-v-sobranciach/audio.mp3" },
416    { id: "ep1508", name: "MIKROZVUK TÝŽDŇA UCHOP ZO SPODU", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/mikrozvuk-tyzdna-uchop-zo-spodu/audio.mp3" },
417    { id: "ep1507", name: "VIEME AKO DRUHÍ", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/vieme-ako-druhi/audio.mp3" },
418    { id: "ep1506", name: "JE TU AJ INÝ HAJZEL OKREM VÁS DVOCH?", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/je-tu-aj-iny-hajzel-okrem-vas-dvoch/audio.mp3" },
419    { id: "ep1505", name: "ZOMRIEŤ S BUNDÁŠ KEŇÝROM V RUKE", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/zomriet-s-bundas-kenyrom-v-ruke/audio.mp3" },
420    { id: "ep1504", name: "DÁM TO PSOVI? ZJEŠ TO TY? feat. Celeste Buckingham", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/dam-to-psovi-zjes-to-ty-feat-celeste-buckingham/audio.mp3" },
421    { id: "ep1503", name: "BOH MESTSKEJ HROMADNEJ DOPRAVY EMHÁDES feat. Jozef Vajda", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/boh-mestskej-hromadnej-dopravy-emhades-feat-jozef-vajda/audio.mp3" },
422    { id: "ep1502", name: "TOTO JE EXKLUZÍVNA RANNÁ ŠOU Z NÁRODNEJ KEKSIKÁRNE", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/toto-je-exkluzivna-ranna-sou-z-narodnej-keksikarne/audio.mp3" },
423    { id: "ep1501", name: "DOŠIEL BENZÍN. TAK HO PRIVíTAJ.", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/dosiel-benzin-tak-ho-privitaj/audio.mp3" },
424    { id: "ep1500", name: "MY NIE SME NORMÁLNI, ALE TOTO AJ MŇA ZASKOČILO", sub: "Podcast · Ranná šou s Juniorom a Marcelom", show: "Ranná šou s Juniorom a Marcelom", img: "/assets/img/show-ranna-sou.webp", tone: "blush", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/ranna-sou-s-juniorom-a-marcelom/my-nie-sme-normalni-ale-toto-aj-mna-zaskocilo/audio.mp3" },
425    { id: "ep1536", name: "Urobiť poctu Erikovi a Barbare bol môj sen, vďaka nim som ove…", sub: "MIRO JAROŠ · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/urobit-poctu-erikovi-a-barbare-bol-moj-sen-vdaka-nim-som-ovela-viac-snival-miro-jaros/audio.mp3" },
426    { id: "ep1431", name: "Cesta nie je byť sám proti všetkým", sub: "MOMO & ERIC TRESOR · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/cesta-nie-je-byt-sam-proti-vsetkym-momo-eric-tresor/audio.mp3" },
427    { id: "ep1330", name: "FIS ŠPECIÁL", sub: "Vypočuj si nový album IMT Smile Exoticana, s členmi skupiny IMT Smile · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/fis-special-vypocuj-si-novy-album-imt-smile-exoticana-s-clenmi-skupiny-imt-smile/audio.mp3" },
428    { id: "ep1028", name: "\"Keď chceš niečo vytvoriť, musíš si sadnúť na zadok.\"", sub: "MARTIN HARICH · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/ked-chces-nieco-vytvorit-musis-si-sadnut-na-zadok-martin-harich/audio.mp3" },
429    { id: "ep754", name: "\"Kali nám dal energiu, keď sme to najviac potrebovali,\" Ivan…", sub: "FIS špeciál · Fu
429n in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/kali-nam-dal-energiu-ked-sme-to-najviac-potrebovali-ivan-tasler-a-kali-fis-special/audio.mp3" },
430    { id: "ep716", name: "Vojtik: “Ľudí ako som ja si veľa ľudí nevypočuje. Som rád, že…", sub: "Podcast · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/vojtik-ludi-ako-som-ja-si-vela-ludi-nevypocuje-som-rad-ze-objavili-detviansky-sen/audio.mp3" },
431    { id: "ep686", name: "FIS", sub: "Popstar proti svojej vôli | Zuzana Smatanová vo FUN live unplugged · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/fis-popstar-proti-svojej-voli-zuzana-smatanova-vo-fun-live-unplugged/audio.mp3" },
432    { id: "ep664", name: "\"Dusil som sa pri gitarovom sóle,\" Peter Bič Project", sub: "FUN LIVE UNPLUGGED · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/dusil-som-sa-pri-gitarovom-sole-peter-bic-project-fun-live-unplugged/audio.mp3" },
433    { id: "ep638", name: "FIS ŠPECIÁL", sub: "Vypočuj si nový album Celeste Buckingham so Celeste Buckigham · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/fis-special-vypocuj-si-novy-album-celeste-buckingham-so-celeste-buckigham/audio.mp3" },
434    { id: "ep571", name: "FIS ŠPECIÁL", sub: "Hity, ktoré preslávili skupinu Hex · Fun in Slovakia", show: "Fun in Slovakia", img: "/assets/img/pod-fun-in-slovakia.jpg", tone: "tang", url: "https://op3.dev/e//podcast.sepia.sk/episode/jasomfunradio/fun-in-slovakia/fis-special-hity-ktore-preslavili-skupinu-hex/audio.mp3" },
435    { id: "ep688", name: "Strýco Filip v #lvllama: „Týždeň som nejedol mäso. Bolo to st…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/stryco-filip-v-lvllama-tyzden-som-nejedol-maso-bolo-to-strasne-cely-som-sa-trasol/audio.mp3" },
436    { id: "ep680", name: "„Môj otec hral vo Fontáne pre Zuzanu,“ herec a študent medicí…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/moj-otec-hral-vo-fontane-pre-zuzanu-herec-a-student-mediciny-victor-ibara-v-lvllama/audio.mp3" },
437    { id: "ep677", name: "\"Odkedy nemám vlasy, viacej hrám!\" Herec Daniel Fischer v #lv…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/odkedy-nemam-vlasy-viacej-hram-herec-daniel-fischer-v-lvllama/audio.mp3" },
438    { id: "ep675", name: "Najsledovanejší Slovák na TikToku, Valden, v #lvllama: „S pri…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/najsledovanejsi-slovak-na-tiktoku-valden-v-lvllama-s-priatelkou-sarou-sa-vo-videach-plieskame-po-zadku/audio.mp3" },
439    { id: "ep604", name: "Rytmus v špeciálnej #lvllama: „Vianoce mám rád odkedy som spo…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/rytmus-v-specialnej-lvllama-vianoce-mam-rad-odkedy-som-spoznal-jasminu-dovtedy-som-bol-sebec-tradicna-cast-tvojej-oblubenej-sou/audio.mp3" },
440    { id: "ep603", name: "Koľko hodín spávajú raperi? Aj to prezradil Pil C v novej #lv…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/kolko-hodin-spavaju-raperi-aj-to-prezradil-pil-c-v-novej-lvllama/audio.mp3" },
441    { id: "ep599", name: "Števo Eisele v #lvllama: „Elektrické formule? Tak to nie. Hra…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/stevo-eisele-v-lvllama-elektricke-formule-tak-to-nie-hrackarske-haraburdy/audio.mp3" },
442    { id: "ep594", name: "„Mám kontakt aj na Dana Bilzeriana!“ Krásna Kika Ďurišová v #…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/mam-kontakt-aj-na-dana-bilzeriana-krasna-kika-durisova-v-lvllama/audio.mp3" },
443    { id: "ep589", name: "Bojovník UFC Martin Buday v #lvllama: „Cítim sa jak dôchodca…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/bojovnik-ufc-martin-buday-v-lvllama-citim
443-sa-jak-dochodca-krize-ma-bola-uz-teraz/audio.mp3" },
444    { id: "ep539", name: "„Svoj rómsky pôvod som vedela využiť vo svoj prospech.“ Spevá…", sub: "Podcast · Level Lama", show: "Level Lama", img: "/assets/img/pod-level-lama.jpg", tone: "peri", url: "https://podcast.sepia.sk/episode/jasomfunradio/level-lama/svoj-romsky-povod-som-vedela-vyuzit-vo-svoj-prospech-spevacka-zea-hostkou-v-lvllama/audio.mp3" },
445    { id: "ep1078", name: "MICHAELA TLAPOVÁ", sub: "Školský projekt ju priviedol k Instagramu, kde spravuje najväčší účet o Bratislave · Slováci sú prima", show: "Slováci sú prima", img: "/assets/img/funradio-logo.svg", tone: "mint", url: "https://podcast.sepia.sk/episode/jasomfunradio/slovaci-su-prima/michaela-tlapova-skolsky-projekt-ju-priviedol-k-instagramu-kde-spravuje-najvacsi-ucet-o-bratislave/audio.mp3" },
446    { id: "ep997", name: "DENIS KOVÁČ", sub: "\"Čítať motivačné knihy a nič z toho nerobiť je strata času.\" · Slováci sú prima", show: "Slováci sú prima", img: "/assets/img/funradio-logo.svg", tone: "mint", url: "https://podcast.sepia.sk/episode/jasomfunradio/slovaci-su-prima/denis-kovac-citat-motivacne-knihy-a-nic-z-toho-nerobit-je-strata-casu/audio.mp3" },
447    { id: "ep947", name: "SLOVÁCI SÚ PRIMA", sub: "Mirka cez úspešný profil Viete po slovensky popularizuje slovenčinu · Slováci sú prima", show: "Slováci sú prima", img: "/assets/img/funradio-logo.svg", tone: "mint", url: "https://podcast.sepia.sk/episode/jasomfunradio/slovaci-su-prima/slovaci-su-prima-mirka-cez-uspesny-profil-viete-po-slovensky-popularizuje-slovencinu/audio.mp3" },
448    { id: "ep948", name: "SLOVÁCI SÚ PRIMA", sub: "Daj Marekovi päť slov, napíše ti poviedku DO STO SLOV · Slováci sú prima", show: "Slováci sú prima", img: "/assets/img/funradio-logo.svg", tone: "mint", url: "https://podcast.sepia.sk/episode/jasomfunradio/slovaci-su-prima/slovaci-su-prima-daj-marekovi-pat-slov-napise-ti-poviedku-do-sto-slov/audio.mp3" },
449    { id: "ep949", name: "SLOVÁCI SÚ PRIMA", sub: "Jakub Nagy vyhral spolu s tímom súťaž americkej armády · Slováci sú prima", show: "Slováci sú prima", img: "/assets/img/funradio-logo.svg", tone: "mint", url: "https://podcast.sepia.sk/episode/jasomfunradio/slovaci-su-prima/slovaci-su-prima-jakub-nagy-vyhral-spolu-s-timom-sutaz-americkej-armady/audio.mp3" },
450    { id: "ep1398", name: "4 \"Keď som počul Keď sme sami, neveril som, že ste to vy,\" fe…", sub: "Podcast · VŠETKO, ČO MÁM RÁD #HEX35", show: "VŠETKO, ČO MÁM RÁD #HEX35", img: "/assets/img/funradio-logo.svg", tone: "butter", url: "https://podcast.sepia.sk/episode/jasomfunradio/vsetko-co-mam-rad-hex35/4-ked-som-pocul-ked-sme-sami-neveril-som-ze-ste-to-vy-feat-lukas-latinak/audio.mp3" },
451    { id: "ep1397", name: "3 \"Na koncerty so sebou nosíte obrovský aparát spomienok,\" fe…", sub: "Podcast · VŠETKO, ČO MÁM RÁD #HEX35", show: "VŠETKO, ČO MÁM RÁD #HEX35", img: "/assets/img/funradio-logo.svg", tone: "butter", url: "https://podcast.sepia.sk/episode/jasomfunradio/vsetko-co-mam-rad-hex35/3-na-koncerty-so-sebou-nosite-obrovsky-aparat-spomienok-feat-tina-a-emma-drobna/audio.mp3" },
452    { id: "ep1377", name: "2 \"Človek na vás aj ide zabudnúť a potom vás zahrajú na hokej…", sub: "Podcast · VŠETKO, ČO MÁM RÁD #HEX35", show: "VŠETKO, ČO MÁM RÁD #HEX35", img: "/assets/img/funradio-logo.svg", tone: "butter", url: "https://podcast.sepia.sk/episode/jasomfunradio/vsetko-co-mam-rad-hex35/2-clovek-na-vas-aj-ide-zabudnut-a-potom-vas-zahraju-na-hokeji-feat-jano-gordulic/audio.mp3" },
453    { id: "ep1376", name: "1 \"Pre mňa ste najstaršia mladá kapela,\" feat. Junior a Marcel", sub: "Podcast · VŠETKO, ČO MÁM RÁD #HEX35", show: "VŠETKO, ČO MÁM RÁD #HEX35", img: "/assets/img/funradio-logo.svg", tone: "butter", url: "https://podcast.sepia.sk/episode/jasomfunradio/vsetko-co-mam-rad-hex35/1-pre-mna-ste-najstarsia-mlada-kapela-feat-junior-a-marcel/audio.mp3" },
454  ];
455  const TRACKS = [...STATIONS, ...EPISODES];
456  const isStation = t => STATIONS.some(s => s.id === t.id);
457
458  const audio = new Audio();
459  /* ---------- hlasitosť (posuvník vo veľkom prehrávači) ----------
460     Poslucháčka hlásila, že hlasitosť na webe nešla ovládať: doteraz tu
461     nebol žiadny ovládač. Hodnota sa pamätá. Na iPhone hlasitosť stránka
462     ovládať nemôže (systémové obmedzenie), tam je posuvník sc
462hovaný v CSS. */
463  const vol = document.querySelector('.player-vol-range');
464  if (vol) {
465    let ulozena = 1;
466    try { ulozena = Math.min(1, Math.max(0, parseFloat(localStorage.getItem('rg-hlasitost') || '1'))); } catch (e) {}
467    if (isFinite(ulozena)) { audio.volume = ulozena; vol.value = String(ulozena); }
468    vol.addEventListener('input', () => {
469      const v = Math.min(1, Math.max(0, parseFloat(vol.value) || 0));
470      audio.volume = v;
471      audio.muted = v === 0;
472      try { localStorage.setItem('rg-hlasitost', String(v)); } catch (e) {}
473    });
474  }
475
476  /* softnav needs to know whether sound is running, so it can swap <main> instead of
477     doing a full page load (which would tear this element down mid-track) */
478  window.__frAudio = audio;
479
480  /* Naraz smie hrať len jedna karta prehliadača. Poslucháč mal bežne
481     pustený stream v jednej karte a podcast v druhej — hralo oboje cez
482     seba a refresh nepomohol, lebo zvuk šiel z inej karty. Každé
483     spustenie sa ohlási ostatným kartám a tie sa stíšia. */
484  let kanalKariet = null;
485  try { kanalKariet = new BroadcastChannel('rg-prehravac'); } catch (e) {}
486  const tatoKarta = Math.random().toString(36).slice(2);
487  if (kanalKariet) {
488    kanalKariet.onmessage = (e) => {
489      if (e && e.data && e.data.hra && e.data.kto !== tatoKarta && !audio.paused) pause();
490    };
491    audio.addEventListener('play', () => {
492      try { kanalKariet.postMessage({ hra: true, kto: tatoKarta }); } catch (e) {}
493    });
494  }
495
496  audio.preload = 'none';
497  let current = STATIONS[0];
498  let playing = false;
499  let loading = false;
500  let listenMs = 0;
501  let lastTick = 0;
502
503  /* dock */
504  const dock = $('.dock');
505  const dockBtn = $('.dock-btn');
506  const dockTitle = $('.dock-meta strong');
507  const dockSub = $('.dock-meta span');
508  const dockArtImg = $('.dock-art img');
509  const dockArtOrb = $('.dock-art .orb');
510
511  /* fullscreen prehrávač */
512  const player = $('.player');
513  const playerTitle = $('.player-title');
514  const playerAvatarImg = $('.player-avatar img');
515  const playerCard = $('.player-card');
516  const playerArtImg = $('.player-art-img');
517  const playerArtOrb = $('.player-art-orb');
518  const playerPlay = $('.player-play');
519  const playerState = $('.player-state');
520  const playerClock = $('.player-clock');
521  const playerProgress = $('.player-progress');
522  const playerBar = $('.player-progress .bar');
523  const playerStations = $('.player-stations');
524  const stationsToggle = $('.player-stations-toggle');
525
526  function setArt(imgEl, orbEl, track) {
527    if (!imgEl || !orbEl) return;
528    if (track.img) {
529      imgEl.src = track.img;
530      imgEl.alt = isStation(track) ? (track.show || track.name) : track.name;
531      imgEl.hidden = false;
532      orbEl.hidden = true;
533    } else {
534      imgEl.hidden = true;
535      orbEl.hidden = false;
536      orbEl.className = orbEl.className.replace(/orb--\S+/, 'orb--' + (track.orb || 'magenta'));
537    }
538  }
539
540  function paint() {
541    /* dock */
542    if (dockBtn) dockBtn.innerHTML = size(playing ? ICON_PAUSE : ICON_PLAY, 18);
543    if (dockTitle) dockTitle.textContent = current.name;
544    if (dockSub) dockSub.textContent = loading ? 'Načítavam prenos' : (playing ? (isStation(current) ? 'Naživo' : 'Prehráva sa') : current.sub);
545    dock?.classList.toggle('playing', playing);
546    setArt(dockArtImg, dockArtOrb, current);
547
548    /* fullscreen prehrávač */
549    if (playerTitle) playerTitle.textContent = isStation(current) ? current.show : current.name;
550    if (playerAvatarImg) playerAvatarImg.src = current.img || AVATAR_ZAKLAD;
551    if (playerCard) playerCard.className = 'player-card pastel-card tone-' + (current.tone || 'blush');
552    setArt(playerArtImg, playerArtOrb, current);
553    if (playerArtImg && current.img) playerArtImg.alt = (isStation(current) ? current.show : current.name);
554    if (playerPlay) {
555      $('.pp-ic', playerPlay).innerHTML = size(playing ? ICON_PAUSE : ICON_PLAY, 18);
556      $('.pp-label', playerPlay).textContent = playing ? 'Pauza' : 'Hrať';
557      playerPlay.classList.toggle('playing', playing);
558    }
559    if (playerState) playerState.textContent = isStation(current) ? 'NAŽIVO' : 'EPIZÓDA';
560    playerProgress?.classList.toggle('live', isStation(current));
561    $$('.player-stations button').forEach(b => b.classList.toggle('active', b.dataset.track === current.id));
562
563    /* staničné karty */
564    $$('.station-card').forEach(el => {
565      const active = playing && el.dataset.track === current.id;
566      el.classList.toggle('playing', active);
567      const ic = $('.st-disc', el);
568      if (ic) ic.innerHTML = size(active ? ICON_PAUSE : ICON_PLAY, 16);
569    });
570
571    /* play pilulky (deck, epizódy) */
572    $$('.play-pill').forEach(el => {
573      if (el === playerPlay) return;
574      const active = playing && el.dataset.track === current.id;
575      el.classList.toggle('playing', active);
576      const ic = $('.pp-ic', el);
577      if (ic) ic.innerHTML = size(active ? ICON_PAUSE : ICON_PLAY, 13);
578      const label = $('.pp-label', el);
579      if (label) label.textContent = active ? 'Pauza' : (el.dataset.label || 'Prehrať');
580    });
581
582    /* hero CTA */
583    $$('[data-play-live]').forEach(b => {
584      const live = playing && isStation(current);
585      b.innerHTML = size(live ? ICON_PAUSE : ICON_PLAY, 18) + '<span>' + (live ? 'Pozastaviť' : 'Pustiť naživo') + '</span>';
586    });
587  }
588
589  /* Epizód je vyše 3400, takže sa nedajú do skriptu. Stránka, ktorá epizódu
590     vykresľuje, jej údaje pripne priamo na tlačidlo a prehrávač si ich prevezme. */
591  function adopt(el) {
592    const id = el.dataset.track;
593    if (!id || !el.dataset.audio || TRACKS.some(x => x.id === id)) return;
594    TRACKS.push({
595      id,
596      name: el.dataset.title || 'Epizóda',
597      sub: el.dataset.sub || '',
598      show: el.dataset.show || '',
599      img: el.dataset.img || '/assets/img/funradio-logo.svg',
600      tone: el.dataset.tone || 'blush',
601      url: el.dataset.audio,
602    });
603  }
604
605  /* ── Odolnosť streamu na mobile (poslucháčka Vlny, Android, 16. 9. 2026) ──
606     Chrome na Androide stream pri výpadku siete, po hovore alebo po prepnutí
607     Wi-Fi na dáta zastaví a sám ho neobnoví. Kým poslucháč sám nestlačil
608     pauzu, prehrávač sa o obnovenie pokúša sám: pri chybe alebo zaseknutí
609     znova otvorí stream (s rastúcim odstupom), po prerušení systémom skúša
610     každých pár sekúnd, najviac 30 minút. Epizód podcastov sa to netýka. */
611  let chcemHrat = false;            /* úmysel poslucháča: hrať */
612  let pauzaOdPouzivatela = false;   /* pauzu stlačil poslucháč, nie systém */
613  let pokusov = 0;
614  let casovacObnovy = null;
615  let obnovaOd = 0;
616  const OBNOVA_MAX_MS = 30 * 60 * 1000;
617  function zrusObnovu() { if (casovacObnovy) { clearTimeout(casovacObnovy); casovacObnovy = null; } }
618  function naplanujObnovu(dovod) {
619    if (!chcemHrat || !isStation(current)) return;
620    if (!obnovaOd) obnovaOd = Date.now();
621    if (Date.now() - obnovaOd > OBNOVA_MAX_MS) { zrusObnovu(); return; }
622    zrusObnovu();
623    /* prerušenie systémom (hovor, vytiahnuté slúchadlá, iná aplikácia so zvukom):
624       žiadny slepý časovač, ten by kradol zvuk inej aplikácii alebo pustil rádio
625       z reproduktora; obnoví sa až pri návrate na stránku, po obnove siete alebo
626       tlačidlom Hrať v notifikácii */
627    if (dovod === 'prerusenie') return;
628    /* chyba spojenia: 2 s, 4 s, 8 s… po 30 s */
629    const odstup = Math.min(30000, 2000 * Math.pow(2, Math.min(pokusov, 4)));
630    pokusov++;
631    casovacObnovy = setTimeout(() => { casovacObnovy = null; obnovStream(dovod); }, odstup);
632  }
633  function obnovStream(dovod) {
634    if (!chcemHrat || !isStation(current)) return;
635    if (playing && !audio.paused && !audio.ended && audio.readyState >= 3 && dovod === 'zaseknutie') { pokusov = 0; return; }
636    if (dovod !== 'prerusenie') {
637      /* nové spojenie: to isté URL, load() zahodí zaseknutý buffer */
638      audio.src = current.url;
639      audio.load();
640    }
641    if (playing) { loading = true; paint(); }
642    audio.play().then(() => { loading = false; playing = true; obnovaOd = 0; paint(); })
643      .catch(() => { loading = false; playing = false; paint(); naplanujObnovu(dovod); });
644  }
645  /* po návrate na stránku (ukončený hovor, odomknutý telefón) sa nečaká na časovač */
646  document.addEventListener('visibilitychange', () => {
647    if (document.visibilityState === 'visible' && chcemHrat && isStation(current) && audio.paused && !pauzaOdPouzivatela) obnovStream('prerusenie');
648  });
649  window.addEventListener('online', () => { if (chcemHrat && isStation(current) && audio.paused && !pauzaOdPouzivatela) obnovStream('chyba'); });
650
651  /* Media Session: ovládanie zo zamknutej obrazovky a notifikácie; systém
652     potom berie stránku ako prehrávač a menej ju uspáva na pozadí. */
653  function nastavMediaSession() {
654    if (!('mediaSession' in navigator)) return;
655    try {
656      const obrazok = new URL(current.img || AVATAR_ZAKLAD, location.href).href;
657      navigator.mediaSession.metadata = new MediaMetadata({
658        title: isStation(current) ? (current.show || current.name) : current.name,
659        artist: isStation(current) ? current.name : (current.show || current.sub || ''),
660        artwork: [{ src: obrazok, sizes: '512x512' }],
661      });
662      navigator.mediaSession.playbackState = 'playing';
663    } catch (e) {}
664  }
665  if ('mediaSession' in navigator) {
666    try {
667      navigator.mediaSession.setActionHandler('play', () => { if (!playing) playTrack(current.id); });
668      navigator.mediaSession.setActionHandler('pause', () => pause());
669      navigator.mediaSession.setActionHandler('stop', () => pause());
670    } catch (e) {}
671    audio.addEventListener('pause', () => { try { navigator.mediaSession.playbackState = 'paused'; } catch (e) {} });
672  }
673
674  function playTrack(id) {
675    if (!id) return;   /* featured karta v link móde nemá track */
676    const t = TRACKS.find(x => x.id === id);
677    /* Neznáme id znamená rozbité tlačidlo — NIKDY nie prepnutie na stanicu.
678       Pôvodný fallback na STATIONS[0] spôsoboval, že pokazená karta epizódy
679       zapínala a vypínala ŽIVÝ STREAM (presne hlásená chyba z testovania). */
680    if (!t) { console.warn('Prehrávač: neznáma stopa', id); return; }
681    if (current.id === t.id && playing) { pause(); return; }
682    if (current.id !== t.id) { listenMs = 0; audio.src = t.url; }
683    else if (!audio.src) audio.src = t.url;
684    current = t;
685    loading = true;
686    lastTick = Date.now();
687    chcemHrat = true; pauzaOdPouzivatela = false; pokusov = 0; obnovaOd = 0; zrusObnovu();
688    audio.play().then(() => { loading = false; playing = true;
688 paint(); }).catch(() => {
689      loading = false; playing = false;
690      if (dockSub) dockSub.textContent = 'Prenos sa nepodarilo spustiť';
691      paint();
692    });
693    playing = true;
694    paint();
695  }
696  function pause() { pauzaOdPouzivatela = true; chcemHrat = false; zrusObnovu(); audio.pause(); playing = false; paint(); }
697  function toggle() { playing ? pause() : playTrack(current.id); }
698
699  audio.addEventListener('playing', () => { loading = false; playing = true; lastTick = Date.now(); pokusov = 0; obnovaOd = 0; zrusObnovu(); nastavMediaSession(); paint(); });
700  audio.addEventListener('pause', () => {
701    playing = false; paint();
702    /* pauza, ktorú nestlačil poslucháč (hovor, slúchadlá, iná aplikácia,
703       úsporný režim): stream sa obnoví sám, kým ho poslucháč nezastaví */
704    if (!pauzaOdPouzivatela && !loading && chcemHrat && isStation(current)) naplanujObnovu('prerusenie');
705  });
706  audio.addEventListener('ended', () => {
707    playing = false; paint();
708    /* živý stream nekončí; koniec znamená, že spojenie spadlo */
709    if (chcemHrat && isStation(current)) naplanujObnovu('koniec');
710  });
711  audio.addEventListener('error', () => { if (chcemHrat && isStation(current)) naplanujObnovu('chyba'); });
712  audio.addEventListener('stalled', () => { if (chcemHrat && isStation(current) && !audio.paused) naplanujObnovu('zaseknutie'); });
713  /* 'waiting' príde, keď sa buffer vyprázdni (mŕtve spojenie bez zmeny siete); pri úvodnom
714     bufferovaní po play() je loading true a nič sa neplánuje */
715  audio.addEventListener('waiting', () => { if (chcemHrat && isStation(current) && !audio.paused && !loading) naplanujObnovu('zaseknutie'); });
716  /* zvuk vložený v článku (<audio controls>) nesmie hrať cez stream:
717     keď sa spustí čokoľvek iné než náš prehrávač, stream sa zastaví */
718  document.addEventListener('play', (e) => {
719    if (e.target !== audio && playing) pause();
720  }, true);
721
722  dockBtn?.addEventListener('click', toggle);
723  playerPlay?.addEventListener('click', toggle);
724
725  /* čas počúvania a progres */
726  const fmtTime = ms => {
727    const s = Math.floor(ms / 1000);
728    return String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
729  };
730  setInterval(() => {
731    if (playing) {
732      const now = Date.now();
733      listenMs += now - lastTick;
734      lastTick = now;
735    }
736    if (!document.body.classList.contains('player-open')) return;
737    if (isStation(current)) {
738      if (playerClock) playerClock.textContent = fmtTime(listenMs);
739    } else {
740      if (playerClock) playerClock.textContent = fmtTime(audio.currentTime * 1000);
741      if (playerBar && audio.duration) playerBar.style.width = (audio.currentTime / audio.duration * 100) + '%';
742    }
743  }, 500);
744
745  /* ── posúvanie epizódy po lište (živý prenos sa posúvať nedá) ── */
746  if (playerProgress) {
747    playerProgress.setAttribute('role', 'slider');
748    playerProgress.setAttribute('tabindex', '0');
749    playerProgress.setAttribute('aria-label', 'Pozícia v epizóde');
750    const seekable = () => !isStation(current) && isFinite(audio.duration) && audio.duration > 0;
751    const seekTo = (clientX) => {
752      const r = playerProgress.getBoundingClientRect();
753      const p = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
754      audio.currentTime = p * audio.duration;
755      if (playerBar) playerBar.style.width = (p * 100) + '%';
756      playerProgress.setAttribute('aria-valuenow', Math.round(p * 100));
757    };
758    let scrubbing = false;
759    playerProgress.addEventListener('pointerdown', (e) => {
760      if (!seekable()) return;
761      scrubbing = true;
762      playerProgress.classList.add('scrubbing');
763      try { playerProgress.setPointerCapture(e.pointerId); } catch (_) {}
764      seekTo(e.clientX);
765    });
766    playerProgress.addEventListener('pointermove', (e) => { if (scrubbing) seekTo(e.clientX); });
767    const endScrub = (e) => {
768      if (!scrubbing) return;
769      scrubbing = false;
770      playerProgress.classList.remove('scrubbing');
771      try { playerProgress.releasePointerCapture(e.pointerId); } catch (_) {}
772    };
773    playerProgress.addEventListener('pointerup', endScrub);
774    playerProgress.addEventListener('pointercancel', endScrub);
775    playerProgress.addEventListener('keydown', (e) => {
776      if (!seekable()) return;
777      const step = e.shiftKey ? 60 : 15;
778      if (e.key === 'ArrowRight') { e.preventDefault(); audio.currentTime = Math.min(audio.duration, audio.currentTime + step); }
779      else if (e.key === 'ArrowLeft') { e.preventDefault(); audio.currentTime = Math.max(0, audio.currentTime - step); }
780    });
781  }
782
783  /* ── návrat k živému vysielaniu ──
784     Po pustení epizódy sa k rádiu nedalo vrátiť inak než cez tlačidlo v hlavičke —
785     zoznam staníc je schovaný za ikonou. Preto vlastné tlačidlo v prehrávači aj v docku. */
786  const LIVE_ID = STATIONS[0].id;
787  const LIVE_NAME = STATIONS[0].name;
788  /* Samotná bodka v krúžku vyzerala ako tlačidlo nahrávania. Vysielacia vlna
789     plus slovo "Naživo" povie rovno, čo sa stane po stlačení. */
790  const ICON_LIVE =
791    '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor"' +
792    ' stroke-width="2" stroke-linecap="round" aria-hidden="true">' +
793    '<circle cx="12" cy="12" r="2.4" fill="currentColor" stroke="none"/>' +
794    '<path d="M8.1 15.9a5.5 5.5 0 0 1 0-7.8M15.9 8.1a5.5 5.5 0 0 1 0 7.8"/>' +
795    '<path d="M5.3 18.7a9.5 9.5 0 0 1 0-13.4M18.7 5.3a9.5 9.5 0 0 1 0 13.4"/>' +
796    '</svg>';
797  function makeLiveBtn(cls, label) {
798    const b = document.createElement('button');
799    b.type = 'button';
800    b.className = cls;
801    b.innerHTML = ICON_LIVE + '<span class="bl-t">' + label + '</span>';
802    b.addEventListener('click', (e) => { e.stopPropagation(); playTrack(LIVE_ID); });
803    return b;
804  }
805  const backLive = makeLiveBtn('back-live', 'Späť na ' + LIVE_NAME + ' naživo');
806  $('.player-controls')?.appendChild(backLive);
807  const dockLive = makeLiveBtn('dock-live', 'Naživo');
808  dockLive.title = 'Prepnúť na živé vysielanie ' + LIVE_NAME;
809  dockLive.setAttribute('aria-label', 'Prepnúť na živé vysielanie ' + LIVE_NAME);
810  if (dock && dockBtn) dock.insertBefore(dockLive, dockBtn);
811
812  const syncLive = () => {
813    const onEpisode = !isStation(current);
814    backLive.hidden = !onEpisode;
815    dockLive.hidden = !onEpisode;
816  };
817  syncLive();
818  const _paint = paint;
819  paint = function () { _paint.apply(this, arguments); syncLive(); };
820
821  /* otvorenie a zatvorenie fullscreen prehrávača — GSAP "vyrastie z docku"
822     (clip-path reveal z obrysu docku do celej obrazovky), fallback CSS slide */
823  const playerParts = () => ['.player-top', '.player-card', '.player-controls', '.player-stations']
824    .map(sel => player && player.querySelector(sel)).filter(Boolean);
825  const dockClip = () => {
826    const d = dock ? dock.getBoundingClientRect() : null;
827    if (!d) return 'inset(78% 30% 4% 30% round 34px)';
828    const vw = innerWidth, vh = innerHeight;
829    return `inset(${(d.top / vh * 100).toFixed(1)}% ${((vw - d.right) / vw * 100).toFixed(1)}% ${((vh - d.bottom) / vh * 100).toFixed(1)}% ${(d.left / vw * 100).toFixed(1)}% round 34px)`;
830  };
831  let playerTl = null;
832  const playerOpen = () => {
833    document.body.classList.add('player-open');
834    paint();
835    if (window.gsap && !reduced && player) {
836      if (playerTl) playerTl.kill();
837      player.classList.add('player--anim');
838      playerTl = gsap.timeline({
839        onComplete() {
840          player.classList.remove('player--anim');
841          gsap.set(player, { clearProps: 'clipPath,visibility,opacity' });
842        },
843      })
844        .fromTo(player,
845          { clipPath: dockClip(), visibility: 'visible', autoAlpha: 1 },
846          { clipPath: 'inset(0% 0% 0% 0% round 0px)', duration: .62, ease: 'power4.inOut' })
847        .fromTo(playerParts(),
848          { y: 26, autoAlpha: 0 },
849          { y: 0, autoAlpha: 1, duration: .5, ease: 'power3.out', stagger: .07, clearProps: 'transform,opacity,visibility' },
850          '-=0.22');
851    }
852  };
853  const playerClose = () => {
854    if (window.gsap && !reduced && player && document.body.classList.contains('player-open')) {
855      if (playerTl) playerTl.kill();
856      player.classList.add('player--anim');
857      playerTl = gsap.timeline({
858        onComplete() {
859          document.body.classList.remove('player-open');
860          player.classList.remove('player--anim');
861          gsap.set(player, { clearProps: 'clipPath,visibility,opacity' });
862        },
863      })
864        .to(player, { clipPath: dockClip(), duration: .4, ease: 'power3.in' });
865    } else {
866      document.body.classList.remove('player-open');
867    }
868  };
869  $('.dock-expand')?.addEventListener('click', playerOpen);
870  document.querySelectorAll('.js-open-player').forEach(el => el.addEventListener('click', playerOpen));
871  $('.dock-meta')?.addEventListener('click', playerOpen);
872  $('.dock-art')?.addEventListener('click', playerOpen);
873  $('.player-close')?.addEventListener('click', playerClose);
874  addEventListener('keydown', e => {
875    if (e.key === 'Escape') { playerClose(); menuClose(); }
876  });
877
878  /* staničné chipy v prehrávači */
879  if (playerStations) {
880    STATIONS.forEach(s => {
881      const b = document.createElement('button');
882      b.dataset.track = s.id;
883      b.textContent = s.name;
884      b.addEventListener('click', () => playTrack(s.id));
885      playerStations.appendChild(b);
886    });
887  }
888  stationsToggle?.addEventListener('click', () => {
889    const open = playerStations.classList.toggle('open');
890    stationsToggle.setAttribute('aria-expanded', String(open));
891  });
892
893  /* všetky prvky s data-track spúšťajú prehrávanie */
894  document.addEventListener('click', e => {
895    const el = e.target.closest('[data-track]');
896    if (!el || el.closest('.player-stations')) return;
897    e.preventDefault(); adopt(el); playTrack(el.dataset.track);
898  });
899  $$('[data-play-live]').forEach(el => el.addEventListener('click', e => { e.preventDefault(); playTrack('fun'); }));
900
901  /* Karty epizód na úvodnej stránke: statické HTML nieslo zástupné id
902     (pod1, pod2, pod3), ktoré prehrávač nepozná — klik potom nerobil nič,
903     prípadne prepínal živý stream. Tu sa každá takáto karta napojí na
904     skutočnú epizódu: najprv podľa titulku, inak na najnovšiu voľnú. */
905  (() => {
906    const karty = document.querySelectorAll('.pod-ep .pe-play');
907    if (!karty.length || !EPISODES.length) return;
908    const norm = (t) => String(t || '').toLowerCase().replace(/[…\.]+$/, '').replace(/\s+/g, ' ').trim().slice(0, 30);
909    const pouzite = new Set();
910    karty.forEach((btn) => {
911      const id = btn.dataset.track;
912      if (id && TRACKS.some((t) => t.id === id)) { pouzite.add(id); return; }
913      const titulok = norm(btn.closest('.pod-ep')?.querySelector('.pe-title')?.textContent);
914      const ep = titulok ? EPISODES.find((e) => norm(e.name).startsWith(titulok) || titulok.startsWith(norm(e.name))) : null;
915      /* Bez istej zhody kartu radšej nechaj tak. Pôvodný fallback bral epizódy
916         v globálnom poradí podľa dátumu a karte inej relácie podsunul najnovšiu
917         Adelu a Sajfu (presne nahlásená chyba: FUNúšik hral Lýdiu Eckhardt). */
918      if (!ep || pouzite.has(ep.id)) return;
919      pouzite.add(ep.id);
920      btn.dataset.track = ep.id;
921    });
922  })();
923
924  /* ---------- živá pesnička zo Sepie (plní v2/live.js) ----------
925     text nahradí názov v hlavičke prehrávača, cover (keď existuje)
926     generický vizuál; null všetko vráti na základ. Cover sa najprv
927     potichu prednačíta, aby nikdy nebliklo rozbité <img>. */
928  /* platí pre každú stanicu (hlavné vysielanie aj internetové kanály),
929     preto základný text aj žiadaný cover drží každá zvlášť */
930  const zakladShow = {};
931  const coverZiadany = {};
932  STATIONS.forEach(s => { zakladShow[s.id] = s.show; coverZiadany[s.id] = ''; });
933  window.__zivaSkladba = (id, text, cover) => {
934    const s = STATIONS.find(x => x.id === id);
935    if (!s) return;
936    let zmena = false;
937    const novyShow = text || zakladShow[id];
938    if (s.show !== novyShow) { s.show = novyShow; zmena = true; }
939    const chcem = (text && cover) ? cover : '';
940    if (chcem !== coverZiadany[id]) {
941      coverZiadany[id] = chcem;
942      /* cover starej pesničky dole HNEĎ (orb sa ukáže), nový nabehne po
943         onload — inak by pri pomalom preloade svietil obrázok starej */
944      if (s.img) { s.img = null; zmena = true; }
945      if (chcem) {
946        const im = new Image();
947        im.onload = () => {
948          if (coverZiadany[id] === chcem && s.img !== chcem) { s.img = chcem; if (current === s) paint(); }
949        };
950        im.onerror = () => {
951          /* reset, aby ďalší 30 s poll preload zopakoval — JPG môže na
952             server doraziť o pár sekúnd neskôr než push */
953          if (coverZiadany[id] === chcem) coverZiadany[id] = '';
954        };
955        im.src = chcem;
956      }
957    }
958    if (zmena && current === s) { paint(); if (playing) nastavMediaSession(); }
959  };
960
961  paint();
962})();
963
964/* ============================================================
965   PODCASTY · filter cez avatarov + prepínanie featured karty
966   ============================================================ */
967(() => {
968  const chips = document.querySelectorAll('.pf-chip');
969  const eps = document.querySelectorAll('.pod-ep');
970  const feat = document.querySelector('.pod-feat');
971  if (!chips.length || !feat) return;
972
973  const featImg = feat.querySelector('.pf-img');
974  const featShow = feat.querySelector('.pf-chip-show');
975  const featHost = feat.querySelector('.pf-host');
976  const featTitle = feat.querySelector('.pf-title');
977  const featPlay = feat.querySelector('.pf-play');
978
979  /* featured obsah pre každú reláciu (all = Adela a Sajfa ep. 1) */
980  const FEAT = {
981    "all": { img: "/assets/img/show-adela-a-sajfa.webp", pos: 'center', show: "Adela a Sajfa", host: "herečka a speváčka Nela Pocisková · Adela a Sajfa", title: "Naučila som sa, že život sa nemusí prežiť v tlaku, ale dá sa…", track: "ep1667" },
982    "adela-a-sajfa": { img: "/assets/img/show-adela-a-sajfa.webp", pos: 'center', show: "Adela a Sajfa", host: "herečka a speváčka Nela Pocisková · Adela a Sajfa", title: "Naučila som sa, že život sa nemusí prežiť v tlaku, ale dá sa…", track: "ep1667" },
983    "sajfa-sou": { img: "/assets/img/show-sajfa-sou.webp", pos: 'center', show: "Sajfa šou", host: "Sajfa šou", title: "ĎUĎOVI RODIČIA VYSLALI SIGNÁL feat. Hex", track: "ep1585" },
984    "talkshow-so-sarkanom": { img: "/assets/img/show-nedelna-talkshow.webp", pos: 'center', show: "Talkshow so Šarkanom", host: "Talkshow so Šarkanom", title: "Môj prvý bikepark? Lopata a igelit na teniskách – staviteľ tr…", track: "ep1663" },
985    "funusik": { img: "/assets/img/pod-funusik.png", pos: 'center', show: "FUNúšik", host: "FUNúšik", title: "175 NÓRI PREPISJÚ HISTÓRIU, NEYMAR SA LÚČIL S REPRE", track: "ep1668" },
986    "podla-mna": { img: "/assets/img/pod-podla-mna.png", pos: 'center', show: "PODĽA MŇA", host: "PODĽA MŇA", title: "festival season", track: "ep1666" },
987    "ranna-sou-s-juniorom-a-marcelom": { img: "/assets/img/show-ranna-sou.webp", pos: 'center', show: "Ranná šou s Juniorom a Marcelom", host: "Ranná šou s Juniorom a Marcelom", title: "KERKA NA NEPODSTATNOM MIESTE. V SOBRANCIACH?", track: "ep1534" },
988    "fun-in-slovakia": { img: "/assets/img/pod-fun-in-slovakia.jpg", pos: 'center', show: "Fun in Slovakia", host: "MIRO JAROŠ · Fun in Slovakia", title: "Urobiť poctu Erikovi a Barbare bol môj sen, vďaka nim som ove…", track: "ep1536" },
989    "level-lama": { img: "/assets/img/pod-level-lama.jpg", pos: 'center', show: "Level Lama", host: "Level Lama", title: "Strýco Filip v #lvllama: „Týždeň som nejedol mäso. Bolo to st…", track: "ep688" },
990    "slovaci-su-prima": { img: "/assets/img/funradio-logo.svg", pos: 'center', show: "Slováci sú prima", host: "Školský projekt ju priviedol k Instagramu, kde spravuje najväčší účet o Bratislave · Slováci sú prima", title: "MICHAELA TLAPOVÁ", track: "ep1078" },
991    "vsetko-co-mam-rad-hex35": { img: "/assets/img/funradio-logo.svg", pos: 'center', show: "VŠETKO, ČO MÁM RÁD #HEX35", host: "VŠETKO, ČO MÁM RÁD #HEX35", title: "4 \"Keď som počul Keď sme sami, neveril som, že ste to vy,\" fe…", track: "ep1398" },
992  };
993
994  function setFeat(key) {
995    const f = FEAT[key] || FEAT.all;
996    featImg.src = f.img;
997    featImg.style.objectPosition = f.pos;
998    if (featShow) featShow.textContent = f.show;
999    if (featHost) featHost.textContent = f.host;
1000    if (featTitle) featTitle.textContent = f.title;
1001    if (featPlay) {
1002      if (f.track) {
1003        featPlay.dataset.track = f.track;
1004        featPlay.removeAttribute('href');
1005        featPlay.querySelector('.pe-ic').nextSibling.textContent = '';
1006        featPlay.lastChild.textContent = 'Pustiť epizódu';
1007      } else {
1008        delete featPlay.dataset.track;
1009        featPlay.lastChild.textContent = 'Vypočuť na webe';
1010      }
1011    }
1012  }
1013
1014  chips.forEach(chip => chip.addEventListener('click', () => {
1015    const key = chip.dataset.filter;
1016    chips.forEach(c => {
1017      c.classList.toggle('is-active', c === chip);
1018      c.setAttribute('aria-selected', c === chip ? 'true' : 'false');
1019    });
1020    eps.forEach(ep => { ep.hidden = key !== 'all' && ep.dataset.show !== key; });
1021    setFeat(key);
1022  }));
1023
1024  /* klik na featured play: ak nemá track, otvor odkaz */
1025  if (featPlay) featPlay.addEventListener('click', () => {
1026    if (!featPlay.dataset.track) {
1027      const f = FEAT[document.querySelector('.pf-chip.is-active')?.dataset.filter || 'all'];
1028      if (f && f.href) window.open(f.href, '_blank', 'noopener');
1029    }
1030  });
1031})();

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.