= htmlspecialchars($programme['title']) ?>
+ + += htmlspecialchars($programme['description']) ?>
+ +diff --git a/app/cache/.gitkeep b/app/cache/.gitkeep old mode 100644 new mode 100755 diff --git a/app/config/channels.json b/app/config/channels.json index ec0bc15..31157af 100644 --- a/app/config/channels.json +++ b/app/config/channels.json @@ -2,7 +2,7 @@ "orf1.at": { "uuid": "58a965b21f48319d1a4051476667f5e2", "name": "ORF 1", - "icon": "\/logos\/orf1.png", + "icon": "\/logos\/orf1.png.svg", "country": "", "language": "", "enabled": true, @@ -13,7 +13,7 @@ "orf2.at": { "uuid": "4c9dba34a821b5b25c8e1e2b65abc4df", "name": "ORF 2", - "icon": "\/logos\/orf2.png", + "icon": "\/logos\/orf2.png.svg", "country": "", "language": "", "enabled": true, diff --git a/app/public/assets/css/main.css b/app/public/assets/css/main.css index e9f1343..f591490 100644 --- a/app/public/assets/css/main.css +++ b/app/public/assets/css/main.css @@ -401,3 +401,464 @@ button { .favorite-star { color: #facc15; } + +.page-heading { + margin-bottom: 22px; +} + +.page-heading h2 { + margin: 0; + font-size: 1.8rem; +} + +.page-heading p { + color: var(--muted); + margin: 6px 0 0; +} + +.day-toolbar { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: center; + margin-bottom: 22px; + flex-wrap: wrap; +} + +.day-nav { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.button-link { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--panel-2); + color: var(--text); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 14px; + text-decoration: none; + font-weight: 800; +} + +.button-link:hover { + background: var(--accent-soft); +} + +.day-filter { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.day-filter input, +.day-filter select { + background: #020617; + color: var(--text); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 12px; +} + +.day-list { + display: grid; + gap: 20px; +} + +.day-channel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 18px; + overflow: hidden; +} + +.day-channel-header { + background: var(--panel-2); + padding: 15px 18px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.channel-number { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + height: 26px; + margin-right: 10px; + border-radius: 999px; + background: var(--panel-3); + color: #dbeafe; + font-size: 0.85rem; + font-weight: 800; +} + +.day-programme { + display: grid; + grid-template-columns: 92px 1fr; + gap: 16px; + padding: 16px 18px; + border-top: 1px solid var(--border); +} + +.day-programme.is-current { + background: rgba(34, 197, 94, 0.12); + border-left: 5px solid var(--success); +} + +.day-programme.is-past { + opacity: 0.55; +} + +.day-time { + color: var(--muted); + font-weight: 800; + display: flex; + flex-direction: column; + gap: 4px; +} + +.day-programme-body h3 { + margin: 0; + font-size: 1.08rem; +} + +.day-programme-body p { + color: #cbd5e1; + margin: 10px 0 0; + line-height: 1.45; +} + +@media (max-width: 700px) { + .day-programme { + grid-template-columns: 1fr; + gap: 8px; + } + + .day-time { + flex-direction: row; + } + + .day-time span:first-child::after { + content: "–"; + margin-left: 8px; + } +} + +.timeline-wrap { + --minute-width: 2px; + margin-bottom: 28px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 18px; + background: var(--panel); +} + +.timeline { + min-width: calc(180px + 1440 * var(--minute-width)); +} + +.timeline-header, +.timeline-row { + display: grid; + grid-template-columns: 180px calc(1440 * var(--minute-width)); +} + +.timeline-header { + position: sticky; + top: 0; + z-index: 5; + background: var(--panel-2); + border-bottom: 1px solid var(--border); +} + +.timeline-channel-head, +.timeline-channel { + position: sticky; + left: 0; + z-index: 4; + background: var(--panel-2); + border-right: 1px solid var(--border); +} + +.timeline-channel-head { + padding: 12px 14px; + font-weight: 800; + color: var(--muted); +} + +.timeline-hours { + position: relative; + height: 46px; + background: + repeating-linear-gradient( + to right, + transparent 0, + transparent calc(60 * var(--minute-width) - 1px), + rgba(148, 163, 184, 0.25) calc(60 * var(--minute-width)), + rgba(148, 163, 184, 0.25) calc(60 * var(--minute-width)) + ); +} + +.timeline-hour { + position: absolute; + top: 12px; + transform: translateX(6px); + color: var(--muted); + font-size: 0.8rem; + font-weight: 800; +} + +.timeline-row { + min-height: 72px; + border-bottom: 1px solid var(--border); +} + +.timeline-channel { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 14px; + font-weight: 800; +} + +.timeline-programmes { + position: relative; + min-height: 72px; + background: + repeating-linear-gradient( + to right, + transparent 0, + transparent calc(60 * var(--minute-width) - 1px), + rgba(148, 163, 184, 0.12) calc(60 * var(--minute-width)), + rgba(148, 163, 184, 0.12) calc(60 * var(--minute-width)) + ); +} + +.timeline-programme { + position: absolute; + top: 9px; + left: calc(var(--start) * var(--minute-width)); + width: calc(var(--duration) * var(--minute-width)); + height: 54px; + overflow: hidden; + border-radius: 12px; + padding: 8px 10px; + background: var(--panel-3); + border: 1px solid rgba(148, 163, 184, 0.25); + color: var(--text); +} + +.timeline-programme strong { + display: block; + font-size: 0.85rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.timeline-programme span { + display: block; + margin-top: 3px; + color: var(--muted); + font-size: 0.75rem; +} + +.timeline-programme.is-current { + background: rgba(34, 197, 94, 0.22); + border-color: var(--success); +} + +.timeline-programme.is-past { + opacity: 0.55; +} + +.timeline-now-line { + position: absolute; + top: 0; + bottom: 0; + width: 2px; + background: #ef4444; + z-index: 100; + pointer-events: none; +} + +.timeline-now-line::before { + content: ""; + position: absolute; + left: -5px; + top: -5px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #ef4444; +} + + +.channel-logo { + width: 34px; + height: 24px; + object-fit: contain; + flex: 0 0 auto; + background: rgba(255, 255, 255, 0.08); + border-radius: 6px; + padding: 2px; +} + +.timeline-channel-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.timeline-programme { + cursor: pointer; + transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease; +} + +.timeline-programme:hover { + transform: scale(1.015); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35); + z-index: 20; +} + +.timeline-channel { + justify-content: flex-start; +} + +.timeline-channel .channel-number { + margin-right: 0; +} + +.programme-modal[hidden] { + display: none; +} + +.programme-modal { + position: fixed; + inset: 0; + z-index: 999; +} + +.programme-modal-backdrop { + position: absolute; + inset: 0; + background: rgba(2, 6, 23, 0.78); + backdrop-filter: blur(3px); +} + +.programme-modal-card { + position: relative; + width: min(620px, calc(100vw - 32px)); + margin: 10vh auto; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 22px; + padding: 28px; + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55); +} + +.programme-modal-close { + position: absolute; + top: 14px; + right: 14px; + width: 38px; + height: 38px; + padding: 0; + border-radius: 999px; + font-size: 1.5rem; + line-height: 1; +} + +.programme-modal-time { + color: var(--muted); + font-weight: 800; + margin-bottom: 8px; +} + +.programme-modal-card h2 { + margin: 0 46px 8px 0; + font-size: 1.7rem; +} + +.programme-modal-subtitle { + color: #cbd5e1; + font-size: 1.05rem; + margin-bottom: 12px; +} + +.programme-modal-category { + display: inline-block; + margin-bottom: 14px; + padding: 5px 10px; + border-radius: 999px; + background: var(--panel-3); + color: #dbeafe; + font-size: 0.85rem; + font-weight: 700; +} + +.programme-modal-category:empty, +.programme-modal-subtitle:empty { + display: none; +} + +.programme-modal-card p { + color: #dbe4ef; + line-height: 1.55; + margin: 0; +} + +.guide-search { + position: relative; + margin-bottom: 22px; +} + +.guide-search input { + width: 100%; + background: #020617; + color: var(--text); + border: 1px solid var(--border); + border-radius: 14px; + padding: 13px 16px; + font-size: 1rem; +} + +.guide-search-results { + position: absolute; + z-index: 200; + top: calc(100% + 8px); + left: 0; + right: 0; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 16px; + overflow: hidden; + box-shadow: 0 20px 50px rgba(0,0,0,.45); +} + +.guide-search-result { + padding: 13px 16px; + border-bottom: 1px solid var(--border); + cursor: pointer; +} + +.guide-search-result:hover { + background: var(--accent-soft); +} + +.guide-search-result strong { + display: block; +} + +.guide-search-result span { + display: block; + margin-top: 4px; + color: var(--muted); + font-size: .88rem; +} diff --git a/app/public/assets/js/app.js b/app/public/assets/js/app.js index b0ed84b..7b46f0c 100644 --- a/app/public/assets/js/app.js +++ b/app/public/assets/js/app.js @@ -1 +1,138 @@ +document.addEventListener("DOMContentLoaded", () => { + + const wrap = document.querySelector(".timeline-wrap"); + const line = document.querySelector(".timeline-now-line"); + + if (!wrap || !line) + return; + + wrap.scrollLeft = Math.max( + 0, + line.offsetLeft - wrap.clientWidth / 3 + ); + +}); + +document.addEventListener("DOMContentLoaded", () => { + const modal = document.getElementById("programmeModal"); + + if (!modal) { + return; + } + + const title = document.getElementById("modalTitle"); + const subtitle = document.getElementById("modalSubtitle"); + const description = document.getElementById("modalDescription"); + const category = document.getElementById("modalCategory"); + const time = document.getElementById("modalTime"); + + const openModal = (programmeElement) => { + const id = programmeElement.dataset.programmeId; + const programme = window.TVGUIDE_PROGRAMMES?.[id]; + + if (!programme) { + return; + } + + title.textContent = programme.title || ""; + subtitle.textContent = programme.subtitle || ""; + description.textContent = programme.description || "Keine Beschreibung vorhanden."; + category.textContent = programme.category || ""; + time.textContent = `${programme.channel || ""} · ${programme.start || ""} – ${programme.stop || ""}`; + + modal.hidden = false; + document.body.classList.add("modal-open"); + }; + + const closeModal = () => { + modal.hidden = true; + document.body.classList.remove("modal-open"); + }; + + document.querySelectorAll(".timeline-programme").forEach((programme) => { + programme.addEventListener("click", () => openModal(programme)); + }); + + modal.querySelectorAll("[data-modal-close]").forEach((element) => { + element.addEventListener("click", closeModal); + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && !modal.hidden) { + closeModal(); + } + }); +}); + +document.addEventListener("DOMContentLoaded", () => { + const search = document.getElementById("guideSearch"); + const results = document.getElementById("guideSearchResults"); + const programmes = window.TVGUIDE_PROGRAMMES || {}; + + if (!search || !results) { + return; + } + + const openProgrammeById = (id) => { + const element = document.querySelector(`[data-programme-id="${CSS.escape(id)}"]`); + if (element) { + element.click(); + } + }; + + search.addEventListener("input", () => { + const query = search.value.trim().toLowerCase(); + + if (query.length < 2) { + results.hidden = true; + results.innerHTML = ""; + return; + } + + const matches = Object.values(programmes) + .filter((programme) => { + return [ + programme.title, + programme.subtitle, + programme.description, + programme.category, + programme.channel + ].some((value) => (value || "").toLowerCase().includes(query)); + }) + .slice(0, 12); + + if (!matches.length) { + results.innerHTML = `
= htmlspecialchars($anzeigeDatum) ?>
+= htmlspecialchars($programme['description']) ?>
+ +