diff --git a/src/css/program.css b/src/css/program.css index 2c19fb8..b49b206 100644 --- a/src/css/program.css +++ b/src/css/program.css @@ -17,8 +17,19 @@ font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 8px; line-height: 1.2; } + .pm-subtitle { + font-size: 13px; color: var(--text); margin-bottom: 10px; + font-style: italic; + } .pm-time { - font-size: 12px; color: var(--accent); margin-bottom: 14px; + font-size: 12px; color: var(--accent); margin-bottom: 10px; + } + .pm-meta { + font-size: 12px; color: var(--text-muted); margin-bottom: 10px; + } + .pm-genres { + font-size: 12px; color: var(--text); margin-bottom: 12px; + font-weight: 600; } .pm-desc { font-size: 13px; color: var(--text-muted); line-height: 1.6; diff --git a/src/js/program.js b/src/js/program.js index aa2dbe0..c3659ed 100644 --- a/src/js/program.js +++ b/src/js/program.js @@ -4,12 +4,20 @@ function openProgramModal(ch, p) { const isLive = p.start <= now && p.stop > now; const dur = Math.round((p.stop - p.start) / 60000); const ep = fmtEpisode(p.season, p.episode); + const genres = formatCategories(p.categories); + const meta = [p.airDate, p.rating].filter(Boolean).join(' · '); document.getElementById('pm-title').textContent = p.title; document.getElementById('pm-channel').textContent = ch.name; + document.getElementById('pm-subtitle').textContent = p.subtitle || ''; + document.getElementById('pm-subtitle').style.display = p.subtitle ? 'block' : 'none'; document.getElementById('pm-time').textContent = `${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${dur} ${L.duration_min}` + (ep ? ` · ${ep}` : ''); + document.getElementById('pm-meta').textContent = meta; + document.getElementById('pm-meta').style.display = meta ? 'block' : 'none'; + document.getElementById('pm-genres').textContent = genres; + document.getElementById('pm-genres').style.display = genres ? 'block' : 'none'; document.getElementById('pm-desc').textContent = p.desc || ''; document.getElementById('pm-desc').style.display = p.desc ? 'block' : 'none'; diff --git a/src/js/search.js b/src/js/search.js index ff89e2d..6ea8cbd 100644 --- a/src/js/search.js +++ b/src/js/search.js @@ -49,6 +49,7 @@ function applySearch(q) { if (p.stop < today || p.start > tomorrow) return false; if (p.title.toLowerCase().includes(q)) return true; if (p.desc && p.desc.toLowerCase().includes(q)) return true; + if ((p.categories || []).some(cat => cat.toLowerCase().includes(q))) return true; return false; }); }); @@ -110,7 +111,10 @@ function renderFiltered(filteredChannels, q) { block.className = 'program-block'; // Highlight programs that match the current search query. - const matchesProg = p.title.toLowerCase().includes(q) || (p.desc && p.desc.toLowerCase().includes(q)); + const matchesProg = + p.title.toLowerCase().includes(q) || + (p.desc && p.desc.toLowerCase().includes(q)) || + (p.categories || []).some(cat => cat.toLowerCase().includes(q)); if (matchesProg) block.classList.add('search-match'); if (p.stop < now) block.classList.add('is-past'); @@ -168,7 +172,10 @@ function renderMobileFiltered(filteredChannels, q) { const isLive = p.start <= now && p.stop > now; const isPast = p.stop < now; const dur = Math.round((p.stop - p.start) / 60000); - const matchesProg = p.title.toLowerCase().includes(q) || (p.desc && p.desc.toLowerCase().includes(q)); + const matchesProg = + p.title.toLowerCase().includes(q) || + (p.desc && p.desc.toLowerCase().includes(q)) || + (p.categories || []).some(cat => cat.toLowerCase().includes(q)); const item = document.createElement('div'); item.className = 'mobile-program' + (isLive ? ' is-live' : '') + (isPast ? ' is-past' : '') + (matchesProg ? ' search-match' : ''); diff --git a/src/js/tooltip.js b/src/js/tooltip.js index 87ebd72..0b8ad55 100644 --- a/src/js/tooltip.js +++ b/src/js/tooltip.js @@ -8,8 +8,13 @@ function getNowPlaying(chId) { // ── TOOLTIP ─────────────────────────────────────────────────────────────────── function showTooltip(e, p) { const ep = fmtEpisode(p.season, p.episode); - document.getElementById('tt-title').textContent = p.title + (ep ? ' ' + ep : ''); - document.getElementById('tt-time').textContent = `${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${Math.round((p.stop-p.start)/60000)} min`; + const genres = formatCategories(p.categories); + const title = p.title + (p.subtitle ? ` - ${p.subtitle}` : '') + (ep ? ' ' + ep : ''); + const meta = [p.airDate, p.rating, genres].filter(Boolean).join(' · '); + document.getElementById('tt-title').textContent = title; + document.getElementById('tt-time').textContent = + `${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${Math.round((p.stop-p.start)/60000)} min` + + (meta ? ` · ${meta}` : ''); document.getElementById('tt-desc').textContent = p.desc || '—'; document.getElementById('tooltip').classList.add('visible'); moveTooltip(e); @@ -23,5 +28,3 @@ function moveTooltip(e) { function hideTooltip() { document.getElementById('tooltip').classList.remove('visible'); } - - diff --git a/src/js/utils.js b/src/js/utils.js index 4de08da..7717d1b 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -106,6 +106,34 @@ function fmtEpisode(season, episode) { return null; } +function parseCategories(p) { + const seen = new Set(); + const categories = []; + p.querySelectorAll('category').forEach(cat => { + const value = (cat.textContent || '').trim(); + const key = value.toLowerCase(); + if (!value || seen.has(key)) return; + seen.add(key); + categories.push(value); + }); + return categories; +} + +function formatCategories(categories) { + return Array.isArray(categories) && categories.length ? categories.join(' · ') : ''; +} + +function parseAirDate(value) { + if (!value) return null; + const match = String(value).trim().match(/^(\d{4})/); + return match ? match[1] : null; +} + +function parseRatingValue(p) { + const rating = p.querySelector('rating > value')?.textContent || ''; + return rating.trim() || null; +} + function parseAndRender(doc) { channels = []; programs = {}; doc.querySelectorAll('channel').forEach(ch => { @@ -124,9 +152,13 @@ function parseAndRender(doc) { const start = parseXMLTVDate(p.getAttribute('start')); const stop = parseXMLTVDate(p.getAttribute('stop')); const title = p.querySelector('title')?.textContent || ''; + const subtitle = p.querySelector('sub-title')?.textContent || ''; const desc = p.querySelector('desc')?.textContent || ''; + const categories = parseCategories(p); + const airDate = parseAirDate(p.querySelector('date')?.textContent || ''); + const rating = parseRatingValue(p); const epInfo = parseEpisode(p); - if (start && stop) programs[chId].push({ start, stop, title, desc, ...epInfo }); + if (start && stop) programs[chId].push({ start, stop, title, subtitle, desc, categories, airDate, rating, ...epInfo }); }); Object.keys(programs).forEach(id => programs[id].sort((a,b) => a.start - b.start)); diff --git a/src/tpl/modals.php b/src/tpl/modals.php index 2e242c7..85b5f95 100644 --- a/src/tpl/modals.php +++ b/src/tpl/modals.php @@ -4,7 +4,10 @@
+ + +