From d4d780618932147373fd0f9c92c2822bf3c26410 Mon Sep 17 00:00:00 2001 From: zwitschi Date: Tue, 4 Aug 2026 20:51:22 +0200 Subject: [PATCH] feat: implement dark mode theme and refactor viewer layout for improved responsiveness --- CHANGELOG.md | 3 + frontend/index.html | 82 +++++------- frontend/src/main.ts | 30 ++--- frontend/src/style.css | 274 ++++++++++++++++++++++++++++------------- 4 files changed, 238 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2210d9d..072411c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - Updated chat runtime image to include ffmpeg/ffprobe for media duration probing on upload. - Rebuilt viewer, admin, and admin-login pages around custom semantic class structures with accessibility-focused landmarks, tab semantics, and keyboard-focus behavior. - Replaced legacy and Tailwind-based stylesheet rules with a custom design-token CSS system covering responsive layouts, forms, tables, and reduced-motion behavior. +- Switched the frontend theme to dark-mode-first token defaults with a light-scheme override path for users who prefer light appearance. +- Refactored the viewer page to a single header plus unified content layout with responsive video/chat split, mobile stacking, and chat panel toggle behavior. - Code-split `hls.js` via lazy loading in the viewer bootstrap to shrink the main frontend bundle and isolate stream playback logic into a dedicated chunk. ### Fixed @@ -30,6 +32,7 @@ - Removed Tailwind and Alpine runtime/build dependencies from the frontend package configuration and eliminated Tailwind directives/warnings during production build. - Enforced 2GB upload ceiling plus server-side media file type/size validation for admin uploads. - Increased nginx `client_max_body_size` to allow resumable admin upload chunks and prevent `413 Content Too Large` responses. +- Removed legacy now-playing viewer UI elements that conflicted with the streamlined single-header layout. ## [1.0.3] - 2026-08-03 diff --git a/frontend/index.html b/frontend/index.html index 6933799..eb4fffa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -10,63 +10,45 @@
-
-
-
- -
-

Linear Void

-

24/7 Synchronized Channel • Live

-
+
+
+ +
+

Linear Void

+

24/7 Synchronized Channel • Live

-
- - Loading schedule... -
-
+
+ +
+
-
- +
- -
diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 9c22920..16b777e 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,12 +1,13 @@ import "./style.css"; -type HlsClass = typeof import("hls.js")["default"]; +type HlsClass = (typeof import("hls.js"))["default"]; declare global { interface Window { switchRoom: (room: string) => void; sendMessage: () => void; togglePrivateDM: () => void; + toggleChatPanel: () => void; } } @@ -14,6 +15,7 @@ let hls: InstanceType | null = null; let ws: WebSocket | null = null; let currentRoom = "main"; let username = "viewer" + Math.floor(Math.random() * 1000); +let chatVisible = true; function resolveWebSocketBaseUrl() { const configuredUrl = import.meta.env.VITE_PUBLIC_WS_URL?.trim(); @@ -134,25 +136,22 @@ async function initPlayerAndChat() { connectWebSocket("main"); - setInterval(() => { - const titles = [ - "Midnight Jazz Lounge", - "Synthwave Nights", - "Classic Cinema", - "Ambient Void", - ]; - const title = titles[Math.floor(Math.random() * titles.length)]; - document.getElementById("now-playing")!.textContent = title; - const overlay = document.getElementById("now-playing-overlay")!; - overlay.classList.remove("hidden"); - document.getElementById("overlay-title")!.textContent = title; - }, 15000); - console.log( "Linear Void Frontend initialized - HLS Player + Chat Sidebar ready", ); } +function toggleChatPanel() { + const shell = document.querySelector(".viewer-shell"); + const toggle = document.getElementById("chat-toggle"); + if (!shell || !toggle) return; + + chatVisible = !chatVisible; + shell.classList.toggle("chat-collapsed", !chatVisible); + toggle.textContent = chatVisible ? "Hide Chat" : "Show Chat"; + toggle.setAttribute("aria-expanded", String(chatVisible)); +} + function sendMessage() { const input = document.getElementById("message-input") as HTMLInputElement; const text = input.value.trim(); @@ -190,6 +189,7 @@ function togglePrivateDM() { window.switchRoom = switchRoom; window.sendMessage = sendMessage; window.togglePrivateDM = togglePrivateDM; +window.toggleChatPanel = toggleChatPanel; document.addEventListener("DOMContentLoaded", () => { void initPlayerAndChat(); diff --git a/frontend/src/style.css b/frontend/src/style.css index de1a2cb..3939d31 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1,29 +1,48 @@ :root { + color-scheme: dark; + --font-body: "Atkinson Hyperlegible", "Segoe UI", "Helvetica Neue", sans-serif; --font-display: "Space Grotesk", "Segoe UI", sans-serif; --font-mono: "IBM Plex Mono", Consolas, monospace; - --bg-0: #f4f6ef; - --bg-1: #e3e8db; - --ink-0: #101b18; - --ink-1: #2d3a36; - --ink-soft: #5a6963; + --bg-0: #0d1417; + --bg-1: #172227; + --bg-2: #0a1012; + --ink-0: #edf6f3; + --ink-1: #cadbd6; + --ink-soft: #9cb2ab; - --panel: #fffffd; - --panel-2: #f7fbf2; - --line: #c6d1c5; + --panel: #17252b; + --panel-2: #1f3138; + --line: #30464d; + --line-soft: #3d5962; - --brand: #005f73; - --brand-strong: #00464f; - --brand-contrast: #f9fffd; - --ok: #2f7a43; - --warn: #b76a00; - --danger: #a7283f; + --brand: #2ec4b6; + --brand-strong: #20a79a; + --brand-contrast: #061412; + --ok: #5add94; + --warn: #f2b560; + --danger: #ff7d8f; + --danger-soft: #3a1f27; - --focus: #0a9396; - --shadow-1: 0 14px 30px rgba(31, 54, 47, 0.12); - --shadow-2: 0 22px 44px rgba(20, 39, 35, 0.2); + --focus: #78f2e0; + --shadow-1: 0 14px 30px rgba(0, 0, 0, 0.34); + --shadow-2: 0 22px 44px rgba(0, 0, 0, 0.45); + + --bg-grad-a: #132129; + --bg-grad-b: var(--bg-0); + --bg-grad-c: var(--bg-2); + + --live-dot: #ff6b6b; + --overlay-bg: rgba(4, 14, 18, 0.84); + + --input-bg: #131f24; + --input-border: var(--line-soft); + --placeholder: #90a8a1; + + --badge-start: #35c8bb; + --badge-end: #1f8d85; --radius-1: 0.6rem; --radius-2: 1rem; @@ -40,6 +59,50 @@ --fast: 160ms ease; } +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + + --bg-0: #f4f6ef; + --bg-1: #e3e8db; + --bg-2: #eff3ea; + --ink-0: #101b18; + --ink-1: #2d3a36; + --ink-soft: #5a6963; + + --panel: #fffffd; + --panel-2: #f7fbf2; + --line: #c6d1c5; + --line-soft: #d8e2d8; + + --brand: #005f73; + --brand-strong: #00464f; + --brand-contrast: #f9fffd; + --ok: #2f7a43; + --warn: #b76a00; + --danger: #a7283f; + --danger-soft: #fff3f6; + + --focus: #0a9396; + --shadow-1: 0 14px 30px rgba(31, 54, 47, 0.12); + --shadow-2: 0 22px 44px rgba(20, 39, 35, 0.2); + + --bg-grad-a: #dce9d6; + --bg-grad-b: var(--bg-0); + --bg-grad-c: #eff3ea; + + --live-dot: #cb271a; + --overlay-bg: rgba(0, 16, 19, 0.84); + + --input-bg: #fff; + --input-border: #c2d2c1; + --placeholder: #74817b; + + --badge-start: #0a9396; + --badge-end: #005f73; + } +} + * { box-sizing: border-box; } @@ -48,6 +111,7 @@ html, body { margin: 0; padding: 0; + height: 100%; min-height: 100%; } @@ -56,9 +120,9 @@ body { color: var(--ink-0); background: radial-gradient( circle at 20% 10%, - #dce9d6 0%, - var(--bg-0) 50%, - #eff3ea 100% + var(--bg-grad-a) 0%, + var(--bg-grad-b) 50%, + var(--bg-grad-c) 100% ); line-height: 1.45; } @@ -172,13 +236,13 @@ select { } .btn-danger { - background: #fff3f6; + background: var(--danger-soft); color: var(--danger); - border-color: #f3c0ca; + border-color: color-mix(in srgb, var(--danger) 45%, transparent); } .btn-danger:hover { - background: #ffe8ee; + background: color-mix(in srgb, var(--danger-soft) 70%, var(--danger)); } .btn-block { @@ -198,13 +262,13 @@ select { width: 100%; border: 1px solid var(--line); border-radius: var(--radius-1); - background: #fff; + background: var(--input-bg); color: var(--ink-0); padding: 0.65rem 0.8rem; } .text-input::placeholder { - color: #74817b; + color: var(--placeholder); } .text-area { @@ -239,7 +303,7 @@ select { .data-table td { text-align: left; padding: 0.72rem 0.65rem; - border-bottom: 1px solid #dfe7de; + border-bottom: 1px solid var(--line-soft); vertical-align: top; } @@ -279,34 +343,44 @@ select { .lv-body { min-height: 100vh; - padding: var(--space-5); + width: 100vw; + height: 100vh; + padding: 0; + overflow: hidden; } .viewer-shell { - width: min(1250px, 100%); - margin: 0 auto; + width: 100vw; + height: 100vh; + margin: 0; display: grid; - grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); - background: linear-gradient(170deg, #f7fbf2 0%, #ebf2e7 40%, #f8fbf5 100%); + grid-template-rows: auto minmax(0, 1fr); + background: linear-gradient( + 170deg, + color-mix(in srgb, var(--panel-2) 72%, var(--panel)) 0%, + var(--panel) 45%, + color-mix(in srgb, var(--panel) 75%, var(--bg-1)) 100% + ); border: 1px solid var(--line); - border-radius: var(--radius-3); + border-radius: 0; box-shadow: var(--shadow-2); overflow: hidden; } -.viewer-main { +.viewer-content { display: grid; - grid-template-rows: auto 1fr auto; - min-height: 76vh; + grid-template-columns: minmax(0, 1fr) minmax(300px, 420px); + min-height: 0; + height: 100%; } -.viewer-header, -.viewer-footer { +.viewer-header { padding: var(--space-5); display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); + border-bottom: 1px solid var(--line); } .viewer-brand-group { @@ -324,8 +398,12 @@ select { font-weight: 800; font-size: 0.95rem; letter-spacing: 0.05em; - color: #fff; - background: linear-gradient(140deg, #0a9396 0%, #005f73 100%); + color: var(--brand-contrast); + background: linear-gradient( + 140deg, + var(--badge-start) 0%, + var(--badge-end) 100% + ); } .brand-text, @@ -347,29 +425,33 @@ select { align-items: center; gap: var(--space-2); border-radius: 999px; - background: #fff; - border: 1px solid #c9d6c8; + background: var(--panel); + border: 1px solid var(--line); color: var(--ink-1); padding: 0.35rem 0.8rem; } .live-dot, .chat-live-marker { - color: #cb271a; + color: var(--live-dot); font-weight: 800; } .player-stage { - position: relative; - padding: 0 var(--space-5) var(--space-5); + min-height: 0; + height: 100%; + padding: var(--space-4); + display: grid; } .player { width: 100%; + height: 100%; border-radius: 1.1rem; - border: 1px solid #bfd0be; + border: 1px solid var(--line-soft); background: #000; box-shadow: var(--shadow-1); + object-fit: contain; } .now-playing-overlay { @@ -382,7 +464,7 @@ select { display: flex; align-items: center; gap: var(--space-2); - background: rgba(0, 16, 19, 0.84); + background: var(--overlay-bg); color: #f0fffb; padding: 0.44rem 0.72rem; border-radius: 999px; @@ -402,23 +484,16 @@ select { font-size: 0.86rem; } -.room-links { - display: flex; - flex-wrap: wrap; - gap: var(--space-3); -} - -.room-links a { - text-decoration: none; - font-weight: 650; +.chat-toggle-btn { + white-space: nowrap; } .chat-sidebar { - background: #f0f6ee; + background: color-mix(in srgb, var(--panel) 82%, var(--bg-1)); border-left: 1px solid var(--line); display: grid; grid-template-rows: auto 1fr auto; - min-height: 76vh; + min-height: 0; } .chat-header, @@ -430,7 +505,7 @@ select { display: flex; align-items: center; justify-content: space-between; - border-bottom: 1px solid #d4dfd3; + border-bottom: 1px solid var(--line-soft); gap: var(--space-3); } @@ -474,20 +549,20 @@ select { } .message-other { - background: #ffffff; - border: 1px solid #d3e0d2; + background: var(--panel); + border: 1px solid var(--line-soft); } .message-self { - background: #005f73; - color: #fff; + background: var(--brand); + color: var(--brand-contrast); } .message-system { font-size: 0.83rem; - color: #1d4441; - background: #d9efea; - border: 1px solid #b5ddd6; + color: var(--ink-1); + background: color-mix(in srgb, var(--brand) 22%, var(--panel-2)); + border: 1px solid color-mix(in srgb, var(--brand) 45%, var(--line)); border-radius: 999px; padding: 0.24rem 0.68rem; animation: messageIn var(--fast); @@ -524,16 +599,28 @@ select { .input { width: 100%; - border: 1px solid #c2d2c1; + border: 1px solid var(--input-border); border-radius: var(--radius-1); padding: 0.65rem 0.8rem; - background: #fff; + background: var(--input-bg); color: var(--ink-0); } .admin-body { min-height: 100vh; padding: var(--space-4); + background: + radial-gradient( + circle at 82% 12%, + rgba(20, 48, 54, 0.5) 0%, + transparent 44% + ), + radial-gradient( + circle at 16% 85%, + rgba(8, 28, 34, 0.55) 0%, + transparent 40% + ), + var(--bg-0); } .admin-shell { @@ -544,13 +631,13 @@ select { border-radius: var(--radius-3); overflow: hidden; border: 1px solid var(--line); - background: #f7fbf3; + background: color-mix(in srgb, var(--panel) 88%, var(--bg-1)); box-shadow: var(--shadow-2); min-height: calc(100vh - 2 * var(--space-4)); } .admin-sidebar { - background: #16423d; + background: #102a2f; color: #ecfff6; display: grid; grid-template-rows: auto auto 1fr; @@ -594,9 +681,9 @@ select { } .tab-trigger.is-active { - background: #0a9396; - color: #fff; - border-color: #89d1c8; + background: var(--brand); + color: var(--brand-contrast); + border-color: color-mix(in srgb, var(--brand) 55%, #fff); } .admin-main { @@ -604,6 +691,14 @@ select { overflow: auto; } +.viewer-shell.chat-collapsed .viewer-content { + grid-template-columns: 1fr; +} + +.viewer-shell.chat-collapsed .chat-sidebar { + display: none; +} + .tab-panel { display: grid; gap: var(--space-4); @@ -622,9 +717,9 @@ select { } .panel-card { - border: 1px solid #d8e2d8; + border: 1px solid var(--line-soft); border-radius: var(--radius-2); - background: #fff; + background: var(--panel); padding: var(--space-4); box-shadow: var(--shadow-1); } @@ -636,10 +731,10 @@ select { } .upload-dropzone { - border: 2px dashed #97b8aa; + border: 2px dashed color-mix(in srgb, var(--brand) 55%, var(--line)); border-radius: var(--radius-2); padding: var(--space-5); - background: #effaf5; + background: color-mix(in srgb, var(--brand) 12%, var(--panel-2)); display: grid; justify-items: start; gap: var(--space-2); @@ -647,7 +742,7 @@ select { .upload-dropzone.is-dragover { border-color: var(--brand); - background: #e6f6ef; + background: color-mix(in srgb, var(--brand) 20%, var(--panel-2)); } .upload-progress { @@ -668,9 +763,9 @@ select { } .stat-card { - border: 1px solid #dce7db; + border: 1px solid var(--line-soft); border-radius: var(--radius-2); - background: #fff; + background: var(--panel); padding: var(--space-4); box-shadow: var(--shadow-1); } @@ -704,7 +799,7 @@ select { } .service-row { - border: 1px solid #dbe5db; + border: 1px solid var(--line-soft); border-radius: var(--radius-1); padding: 0.75rem; display: flex; @@ -748,9 +843,9 @@ select { } .auth-card { - border: 1px solid #cad8c9; + border: 1px solid var(--line); border-radius: var(--radius-3); - background: #fff; + background: var(--panel); box-shadow: var(--shadow-2); padding: var(--space-6); display: grid; @@ -781,11 +876,15 @@ select { } @media (max-width: 1080px) { - .viewer-shell, .admin-shell { grid-template-columns: 1fr; } + .viewer-content { + grid-template-columns: 1fr; + grid-template-rows: minmax(320px, 52vh) minmax(0, 1fr); + } + .chat-sidebar { border-left: 0; border-top: 1px solid var(--line); @@ -821,16 +920,15 @@ select { } .viewer-header, - .viewer-footer, .player-stage, .chat-header, .chat-input-area { padding: var(--space-3); } - .viewer-footer { + .viewer-header { + align-items: flex-start; flex-direction: column; - align-items: start; } .admin-nav { @@ -848,6 +946,10 @@ select { grid-template-columns: 1fr; } + .viewer-shell.chat-collapsed .viewer-content { + grid-template-rows: 1fr; + } + .auth-card { padding: var(--space-5); }