Kossuth / widget.html
corsokossuth's picture
Upload 3 files
2b9ae1c verified
Raw
History Blame Contribute Delete
15.1 kB
<!DOCTYPE html>
<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% !important; 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>