    if(!ts) return "";
    const d = new Date(ts * 1000);
    return String(d.getHours()).padStart(2,'0') + ":" + String(d.getMinutes()).padStart(2,'0');
}

function updateTopFromRow(row, alsoActive){
    if(!row) return;

    const title = document.getElementById('channelTitle');
    const nowBox = document.querySelector('.epgProgramNow');
    const nextBox = document.querySelector('.epgProgramNext');
    const timeNow = document.querySelector('.epgTimeSimple');
    const timeNext = document.querySelector('.epgNextTime');

    const name = row.getAttribute('data-name') || 'Canal';
    const nowTitle = row.getAttribute('data-now') || 'Programación en Actualización';
    const nextTitle = row.getAttribute('data-next') || 'Programación en Actualización';

    const st = parseInt(row.getAttribute('data-start') || '0', 10);
    const en = parseInt(row.getAttribute('data-end') || '0', 10);
    const ns = parseInt(row.getAttribute('data-next-start') || '0', 10);
    const ne = parseInt(row.getAttribute('data-next-end') || '0', 10);

    if(title) title.textContent = name;
    if(nowBox) nowBox.textContent = nowTitle;
    if(nextBox) nextBox.textContent = nextTitle;
    if(timeNow) timeNow.textContent = (st && en) ? (pzHora(st) + " - " + pzHora(en)) : "";
    if(timeNext) timeNext.textContent = (ns && ne) ? (pzHora(ns) + " - " + pzHora(ne)) : "";

    topStartTsLive = st;
    topEndTsLive = en;
    topCurrentRow = row;

    updateTopEpgProgress();
}


async function refreshTopEpgFromServer(row){
    if(!row || topRefreshBusy) return;

    const sid = row.getAttribute('data-stream-id') || '';
    if(!sid) return;

    const nowMs = Date.now();
    if(nowMs - topLastRefresh < 10000) return;

    topRefreshBusy = true;
    topLastRefresh = nowMs;

    try{
        const url = 'tv_clean.php?ajax_epg=1&stream_id=' + encodeURIComponent(sid) + '&_=' + Date.now();
        const r = await fetch(url, {cache:'no-store'});
        const j = await r.json();

        row.setAttribute('data-now', j.now || 'Programacion en Actualizacion');
        row.setAttribute('data-next', j.next || 'Programacion en Actualizacion');
        row.setAttribute('data-start', j.start || 0);
        row.setAttribute('data-end', j.end || 0);
        row.setAttribute('data-next-start', j.next_start || 0);
        row.setAttribute('data-next-end', j.next_end || 0);

        updateTopFromRow(row, false);
    }catch(e){}
    topRefreshBusy = false;
}

function updateTopEpgProgress(){
    const fill = document.getElementById('epgBarFillLive');
    const remain = document.getElementById('epgRemainLive');

    if(!fill || !remain || !topStartTsLive || !topEndTsLive || topEndTsLive <= topStartTsLive){
        if(fill) fill.style.width = '0%';
        if(remain) remain.textContent = '0 min';
        return;
    }

    const now = Math.floor(Date.now() / 1000);
    const total = topEndTsLive - topStartTsLive;
    const elapsed = Math.max(0, Math.min(total, now - topStartTsLive));
    const progress = Math.max(0, Math.min(100, Math.round((elapsed / total) * 100)));
    const mins = Math.max(0, Math.ceil((topEndTsLive - now) / 60));

    fill.style.width = progress + '%';
    remain.textContent = mins + ' min';

    if(mins <= 0){
