Spaces:
Running
Running
| <html lang="it"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> | |
| <title>Corso Kossuth β in onda</title> | |
| <meta name="description" content="Mini-player di Corso Kossuth: ascolta la radio in onda 24/7."> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.10/hls.min.js"></script> | |
| <style> | |
| :root{ | |
| --bg:#121212; --card-a:#1b1b1b; --card-b:#141414; | |
| --surface:#242424; --border:rgba(255,255,255,.10); --border2:rgba(255,255,255,.18); | |
| --red:#9B2626; --red-h:#b52d2d; --red-soft:rgba(155,38,38,.16); | |
| --cream:#f6f1e4; --text:#f0f0f0; --text2:rgba(240,240,240,.66); --text3:rgba(240,240,240,.40); | |
| --sans:'Outfit',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; | |
| --mono:'DM Mono','Courier New',monospace; | |
| } | |
| *{box-sizing:border-box;} | |
| html,body{margin:0;padding:0;background:transparent;} | |
| body{ | |
| font-family:var(--sans); color:var(--text); | |
| /* L'iframe ospita un solo player: niente scroll, nessun margine. */ | |
| overflow:hidden; | |
| } | |
| .kw{ | |
| display:flex; align-items:stretch; gap:0; | |
| max-width:560px; min-width:240px; margin:0 auto; | |
| background:linear-gradient(135deg,var(--card-a),var(--card-b)); | |
| border:1px solid var(--border); border-radius:16px; overflow:hidden; | |
| position:relative; | |
| } | |
| /* Striscia rossa di brand sul bordo sinistro */ | |
| .kw::before{ | |
| content:""; position:absolute; left:0; top:0; bottom:0; width:3px; | |
| background:var(--red); z-index:2; | |
| } | |
| /* ββ Copertina βββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .kw-art{ | |
| position:relative; flex:0 0 auto; | |
| width:92px; aspect-ratio:1; background:#0e0e0e; overflow:hidden; | |
| } | |
| .kw-art img{ width:100%; height:100%; object-fit:cover; display:none; } | |
| .kw-art.has-img img{ display:block; } | |
| /* Placeholder K-brand (mostrato finchΓ© non c'Γ¨ copertina) */ | |
| .kw-art .kw-ph{ | |
| position:absolute; inset:0; display:flex; align-items:center; justify-content:center; | |
| background:var(--red); | |
| } | |
| .kw-art.has-img .kw-ph{ display:none; } | |
| .kw-ph svg{ width:58%; height:58%; } | |
| /* ββ Info ββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .kw-info{ | |
| flex:1 1 auto; min-width:0; | |
| padding:11px 12px 11px 14px; | |
| display:flex; flex-direction:column; justify-content:center; gap:2px; | |
| } | |
| .kw-eyebrow{ display:flex; align-items:center; gap:6px; margin-bottom:1px; } | |
| .kw-live{ | |
| display:inline-flex; align-items:center; gap:5px; | |
| font-family:var(--mono); font-size:9.5px; font-weight:500; letter-spacing:.12em; | |
| text-transform:uppercase; color:#e26a6a; | |
| } | |
| .kw-dot{ width:6px; height:6px; border-radius:50%; background:#e05050; flex:0 0 auto; } | |
| .kw-live.on .kw-dot{ animation:kwpulse 1.8s ease-in-out infinite; } | |
| @keyframes kwpulse{ 0%,100%{opacity:1; box-shadow:0 0 0 0 rgba(224,80,80,.5);} 50%{opacity:.55; box-shadow:0 0 0 5px rgba(224,80,80,0);} } | |
| .kw-title{ | |
| font-size:15px; font-weight:600; line-height:1.25; color:var(--text); | |
| white-space:nowrap; overflow:hidden; text-overflow:ellipsis; | |
| } | |
| .kw-artist{ | |
| font-size:12.5px; font-weight:400; line-height:1.3; color:var(--text2); | |
| white-space:nowrap; overflow:hidden; text-overflow:ellipsis; | |
| } | |
| .kw-sub{ display:flex; align-items:center; gap:8px; margin-top:4px; min-width:0; } | |
| .kw-lic{ | |
| font-family:var(--mono); font-size:9px; font-weight:500; letter-spacing:.05em; | |
| color:var(--text3); border:1px solid var(--border); border-radius:999px; | |
| padding:1px 7px; white-space:nowrap; flex:0 0 auto; | |
| } | |
| .kw-open{ | |
| font-family:var(--mono); font-size:9px; letter-spacing:.05em; color:var(--text3); | |
| text-decoration:none; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; | |
| transition:color .15s; | |
| } | |
| .kw-open:hover{ color:var(--text); } | |
| /* ββ Play / pause ββββββββββββββββββββββββββββββββββββββββββ */ | |
| .kw-ctrl{ | |
| flex:0 0 auto; display:flex; align-items:center; padding:0 13px 0 6px; | |
| } | |
| .kw-btn{ | |
| width:46px; height:46px; border-radius:50%; flex:0 0 auto; | |
| background:var(--red); border:1px solid rgba(255,255,255,.10); color:var(--cream); | |
| display:flex; align-items:center; justify-content:center; cursor:pointer; | |
| transition:background .15s, transform .1s; padding:0; | |
| } | |
| .kw-btn:hover{ background:var(--red-h); } | |
| .kw-btn:active{ transform:scale(.94); } | |
| .kw-btn:focus-visible{ outline:2px solid #e26a6a; outline-offset:2px; } | |
| .kw-btn svg{ width:20px; height:20px; } | |
| .kw-btn .kw-ic-play{ display:block; } | |
| .kw-btn .kw-ic-pause, .kw-btn .kw-ic-load{ display:none; } | |
| .kw.is-playing .kw-btn .kw-ic-play{ display:none; } | |
| .kw.is-playing .kw-btn .kw-ic-pause{ display:block; } | |
| .kw.is-loading .kw-btn .kw-ic-play, | |
| .kw.is-loading .kw-btn .kw-ic-pause{ display:none; } | |
| .kw.is-loading .kw-btn .kw-ic-load{ display:block; animation:kwspin .8s linear infinite; } | |
| @keyframes kwspin{ to{ transform:rotate(360deg); } } | |
| /* ββ Barra avanzamento (sottile, in basso) βββββββββββββββββ */ | |
| .kw-prog{ position:absolute; left:0; right:0; bottom:0; height:2px; background:rgba(255,255,255,.06); z-index:2; } | |
| .kw-prog-fill{ height:100%; width:0; background:var(--red); transition:width .25s linear; } | |
| .kw.is-live .kw-prog-fill{ width:100% ; background:linear-gradient(90deg,transparent,var(--red),transparent); background-size:200% 100%; animation:kwlive 2.2s linear infinite; transition:none; } | |
| @keyframes kwlive{ from{background-position:200% 0;} to{background-position:-200% 0;} } | |
| /* ββ Stato/errore (rimpiazza titolo quando serve) βββββββββ */ | |
| .kw-status{ font-size:12px; color:var(--text3); font-family:var(--mono); } | |
| /* ββ Responsivo: iframe stretto β impila copertina sopra ββ */ | |
| @media (max-width:300px){ | |
| .kw{ flex-direction:column; } | |
| .kw-art{ width:100%; aspect-ratio:16/7; } | |
| .kw-ctrl{ position:absolute; top:8px; right:8px; padding:0; } | |
| .kw-btn{ width:40px; height:40px; box-shadow:0 2px 10px rgba(0,0,0,.4); } | |
| .kw-info{ padding:11px 14px 13px; } | |
| } | |
| @media (prefers-reduced-motion:reduce){ | |
| .kw-live.on .kw-dot, .kw.is-loading .kw-ic-load, .kw.is-live .kw-prog-fill{ animation:none; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="kw" id="kw"> | |
| <div class="kw-art" id="kw-art"> | |
| <div class="kw-ph" aria-hidden="true"> | |
| <!-- Marchio K di Corso Kossuth --> | |
| <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"> | |
| <defs><mask id="km"><rect width="200" height="200" fill="#fff"/> | |
| <polygon points="-4,148 204,42 204,62 -4,168" fill="#000"/></mask></defs> | |
| <path d="M34,22 H68 V92 L168,22 V62 L112,100 L168,138 V178 L68,108 V178 H34 Z" | |
| fill="#f6f1e4" mask="url(#km)"/> | |
| </svg> | |
| </div> | |
| <img id="kw-img" alt=""> | |
| </div> | |
| <div class="kw-info"> | |
| <div class="kw-eyebrow"> | |
| <span class="kw-live" id="kw-live"><span class="kw-dot"></span>In onda</span> | |
| </div> | |
| <div class="kw-title" id="kw-title">Corso Kossuth</div> | |
| <div class="kw-artist" id="kw-artist">Premi play per ascoltare</div> | |
| <div class="kw-sub"> | |
| <span class="kw-lic" id="kw-lic" style="display:none"></span> | |
| <a class="kw-open" id="kw-open" href="#" target="_blank" rel="noopener">Apri la radio β</a> | |
| </div> | |
| </div> | |
| <div class="kw-ctrl"> | |
| <button class="kw-btn" id="kw-btn" aria-label="Ascolta la radio" aria-pressed="false"> | |
| <svg class="kw-ic-play" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 5.5v13a1 1 0 0 0 1.5.87l11-6.5a1 1 0 0 0 0-1.74l-11-6.5A1 1 0 0 0 7 5.5z"/></svg> | |
| <svg class="kw-ic-pause" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg> | |
| <svg class="kw-ic-load" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9" opacity=".9"/></svg> | |
| </button> | |
| </div> | |
| <div class="kw-prog" aria-hidden="true"><div class="kw-prog-fill" id="kw-prog"></div></div> | |
| </div> | |
| <audio id="kw-audio" preload="none" crossorigin="anonymous"></audio> | |
| <script> | |
| (function(){ | |
| "use strict"; | |
| // Origine dei dati. Di norma il widget Γ¨ servito DALLO STESSO Space, quindi i | |
| // percorsi relativi (/hls, /stream, /metadata, /artwork) puntano giΓ al posto | |
| // giusto. Per incorporarlo da un dominio diverso servendo questo file altrove, | |
| // passa l'origine con ?o=https://tuo-space.hf.space | |
| var P = new URLSearchParams(location.search); | |
| var BASE = (P.get('o') || '').replace(/\/+$/,''); // '' = relativo (consigliato) | |
| var HLS_URL = BASE + '/hls/stream.m3u8'; | |
| var STREAM_URL = BASE + '/stream'; | |
| var META_URL = BASE + '/metadata'; | |
| var ART_URL = BASE + '/artwork'; | |
| var SITE_URL = (BASE || location.origin) + '/'; | |
| var STREAM_LATENCY_S = 16; // allineato al sito principale (latenza HLS) | |
| var kw = document.getElementById('kw'); | |
| var audio = document.getElementById('kw-audio'); | |
| var btn = document.getElementById('kw-btn'); | |
| var elArtW = document.getElementById('kw-art'); | |
| var elImg = document.getElementById('kw-img'); | |
| var elTit = document.getElementById('kw-title'); | |
| var elArt = document.getElementById('kw-artist'); | |
| var elLic = document.getElementById('kw-lic'); | |
| var elLive = document.getElementById('kw-live'); | |
| var elProg = document.getElementById('kw-prog'); | |
| document.getElementById('kw-open').href = SITE_URL; | |
| var hls = null, started = false, lastKey = '', artKey = ''; | |
| // Timer brano | |
| var prgStart = 0, prgDur = 0, clkSrv = 0, clkPerf = 0, rafId = 0; | |
| // ββ Stream ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function setLoading(on){ kw.classList.toggle('is-loading', on); } | |
| function setPlaying(on){ | |
| kw.classList.toggle('is-playing', on); | |
| btn.setAttribute('aria-pressed', on ? 'true' : 'false'); | |
| btn.setAttribute('aria-label', on ? 'Metti in pausa' : 'Ascolta la radio'); | |
| } | |
| function startStream(){ | |
| setLoading(true); | |
| if (typeof Hls !== 'undefined' && Hls.isSupported()){ | |
| hls = new Hls({ liveSyncDurationCount:4, enableWorker:true }); | |
| hls.loadSource(HLS_URL); | |
| hls.attachMedia(audio); | |
| hls.on(Hls.Events.MANIFEST_PARSED, function(){ | |
| audio.play().catch(fallback); | |
| }); | |
| hls.on(Hls.Events.ERROR, function(_, d){ | |
| if (!d.fatal) return; | |
| if (d.type === Hls.ErrorTypes.NETWORK_ERROR){ try{ hls.startLoad(); return; }catch(e){} } | |
| else if (d.type === Hls.ErrorTypes.MEDIA_ERROR){ try{ hls.recoverMediaError(); return; }catch(e){} } | |
| fallback(); | |
| }); | |
| } else if (audio.canPlayType('application/vnd.apple.mpegurl')){ | |
| audio.src = HLS_URL; audio.play().catch(fallback); | |
| } else { | |
| fallback(); | |
| } | |
| } | |
| function fallback(){ | |
| if (hls){ try{ hls.destroy(); }catch(e){} hls = null; } | |
| audio.src = STREAM_URL; audio.load(); | |
| audio.play().catch(function(){ | |
| setLoading(false); setPlaying(false); | |
| elArt.textContent = 'Stream non raggiungibile β riprova'; | |
| }); | |
| } | |
| function toggle(){ | |
| if (!started){ | |
| started = true; | |
| startStream(); | |
| return; | |
| } | |
| if (audio.paused){ setLoading(true); audio.play().catch(fallback); } | |
| else { audio.pause(); } | |
| } | |
| btn.addEventListener('click', toggle); | |
| audio.addEventListener('playing', function(){ setLoading(false); setPlaying(true); }); | |
| audio.addEventListener('pause', function(){ setLoading(false); setPlaying(false); }); | |
| audio.addEventListener('waiting',function(){ setLoading(true); }); | |
| audio.addEventListener('error', function(){ if (started) fallback(); }); | |
| // ββ Metadati (titolo / artista / licenza / copertina) βββββββ | |
| function applyMeta(d){ | |
| var title = (d.title || '').trim(); | |
| var artist = (d.artist || '').trim(); | |
| var key = artist + '|' + title; | |
| // Timer: riferimento orario del server + ancoraggio al cambio brano | |
| if (typeof d.srv_now === 'number'){ clkSrv = d.srv_now; clkPerf = performance.now(); } | |
| if (key !== lastKey){ | |
| lastKey = key; | |
| if (title) elTit.textContent = title; | |
| if (artist) elArt.textContent = artist; else if (title) elArt.textContent = ''; | |
| if (!title && !artist){ elTit.textContent = 'Corso Kossuth'; elArt.textContent = 'In onda 24/7'; } | |
| prgStart = (typeof d.ts === 'number') ? d.ts : 0; | |
| prgDur = (typeof d.duration === 'number' && d.duration > 0) ? d.duration : 0; | |
| refreshArtwork(); | |
| } else { | |
| // stesso brano: aggiorna durata se Γ¨ arrivata dopo | |
| if (!prgDur && typeof d.duration === 'number' && d.duration > 0) prgDur = d.duration; | |
| } | |
| // Licenza | |
| var lic = (d.license || '').trim(); | |
| if (lic){ elLic.textContent = lic; elLic.style.display = ''; } | |
| else { elLic.style.display = 'none'; } | |
| elLive.classList.toggle('on', !audio.paused && started); | |
| } | |
| function refreshArtwork(){ | |
| var url = ART_URL + '?t=' + Date.now(); | |
| if (url === artKey) return; artKey = url; | |
| var probe = new Image(); | |
| probe.onload = function(){ elImg.src = url; elArtW.classList.add('has-img'); }; | |
| probe.onerror = function(){ elImg.removeAttribute('src'); elArtW.classList.remove('has-img'); }; | |
| probe.src = url; | |
| } | |
| function pollMeta(){ | |
| fetch(META_URL + '?t=' + Date.now(), { cache:'no-store' }) | |
| .then(function(r){ return r.ok ? r.json() : null; }) | |
| .then(function(d){ if (d) applyMeta(d); }) | |
| .catch(function(){}) | |
| .finally(function(){ setTimeout(pollMeta, 5000); }); | |
| } | |
| // ββ Barra avanzamento βββββββββββββββββββββββββββββββββββββββ | |
| function tick(){ | |
| rafId = requestAnimationFrame(tick); | |
| if (!prgStart || !clkSrv){ kw.classList.remove('is-live'); elProg.style.width = '0'; return; } | |
| var srvNow = clkSrv + (performance.now() - clkPerf)/1000; | |
| var elapsed = (srvNow - prgStart) - STREAM_LATENCY_S; | |
| if (elapsed < 0) elapsed = 0; | |
| if (prgDur > 0){ | |
| kw.classList.remove('is-live'); | |
| var pct = Math.max(0, Math.min(100, (elapsed / prgDur) * 100)); | |
| elProg.style.width = pct + '%'; | |
| } else { | |
| // Durata sconosciuta (es. diretta): barra "live" animata | |
| kw.classList.add('is-live'); | |
| } | |
| } | |
| // Avvio | |
| pollMeta(); | |
| tick(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |