From 86690c37534704eaf34b015517a96109e10319c2 Mon Sep 17 00:00:00 2001 From: Marco Mooren Date: Mon, 11 May 2026 02:18:14 +0200 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- server/routes/stations.js | 6 +- web/docs/index.html | 2 +- web/docs/style.css | 278 ++++-- web/main.js | 12 +- web/style.css | 1760 ++++++++++++++++++++++++++----------- 5 files changed, 1443 insertions(+), 615 deletions(-) diff --git a/server/routes/stations.js b/server/routes/stations.js index 6acc4cd..08c4ebe 100644 --- a/server/routes/stations.js +++ b/server/routes/stations.js @@ -46,9 +46,9 @@ router.post('/:id/vote', requireUser, (req, res) => { const id = Number(req.params.id); if (!getStation(id)) return res.status(404).json({ error: 'not found' }); const raw = req.body?.value; - const value = raw === 1 || raw === '1' || raw === 'up' ? 1 - : raw === -1 || raw === '-1' || raw === 'down' ? -1 - : raw === 0 || raw === '0' || raw === null || raw === 'clear' ? 0 + const value = raw === 1 || raw === '1' || raw === 'up' ? 1 + : raw === -1 || raw === '-1' || raw === 'down' ? -1 + : raw === 0 || raw === '0' || raw === null || raw === 'clear' ? 0 : NaN; if (Number.isNaN(value)) return res.status(400).json({ error: 'value must be 1, -1 or 0' }); res.json(castVote(req.user.id, id, value)); diff --git a/web/docs/index.html b/web/docs/index.html index ddc9904..4f305a3 100644 --- a/web/docs/index.html +++ b/web/docs/index.html @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/web/docs/style.css b/web/docs/style.css index bde60c8..b9effa1 100644 --- a/web/docs/style.css +++ b/web/docs/style.css @@ -16,48 +16,93 @@ color-scheme: dark; } -* { box-sizing: border-box; } -html, body { margin: 0; padding: 0; background: var(--bg-0); color: var(--fg); } -a { color: var(--accent-2); text-decoration: none; } -a:hover { text-decoration: underline; } +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: var(--bg-0); + color: var(--fg); +} + +a { + color: var(--accent-2); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} .docs-header { - position: sticky; top: 0; z-index: 10; + position: sticky; + top: 0; + z-index: 10; background: rgba(7, 8, 11, 0.92); border-bottom: 1px solid var(--line); backdrop-filter: blur(8px); } + .docs-header-inner { - max-width: 980px; margin: 0 auto; + max-width: 980px; + margin: 0 auto; padding: 14px 20px; - display: flex; align-items: center; gap: 16px; + display: flex; + align-items: center; + gap: 16px; } + .docs-header h1 { - margin: 0; font-size: 18px; letter-spacing: -0.01em; + margin: 0; + font-size: 18px; + letter-spacing: -0.01em; } + .docs-header .back { - color: var(--muted); font-size: 13px; - padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; + color: var(--muted); + font-size: 13px; + padding: 6px 10px; + border: 1px solid var(--line); + border-radius: 8px; } -.docs-header .back:hover { color: var(--fg); background: var(--bg-2); text-decoration: none; } + +.docs-header .back:hover { + color: var(--fg); + background: var(--bg-2); + text-decoration: none; +} + .docs-header .base { margin-left: auto; font-family: ui-monospace, "SF Mono", Menlo, monospace; - font-size: 12px; color: var(--muted); - padding: 4px 10px; background: var(--bg-2); - border: 1px solid var(--line); border-radius: 8px; + font-size: 12px; + color: var(--muted); + padding: 4px 10px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: 8px; } #app { - max-width: 980px; margin: 0 auto; + max-width: 980px; + margin: 0 auto; padding: 24px 20px 80px; } + h2.group { margin: 32px 0 12px; - font-size: 13px; text-transform: uppercase; letter-spacing: 0.1em; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.1em; color: var(--muted); } -h2.group:first-child { margin-top: 0; } + +h2.group:first-child { + margin-top: 0; +} .ep { background: var(--bg-1); @@ -66,106 +111,197 @@ h2.group:first-child { margin-top: 0; } padding: 16px; margin-bottom: 14px; } + .ep-head { - display: flex; align-items: center; gap: 10px; + display: flex; + align-items: center; + gap: 10px; flex-wrap: wrap; } + .ep-path { font-family: ui-monospace, "SF Mono", Menlo, monospace; - font-size: 13px; color: var(--fg); + font-size: 13px; + color: var(--fg); background: var(--bg-2); - padding: 4px 10px; border-radius: 6px; + padding: 4px 10px; + border-radius: 6px; border: 1px solid var(--line); overflow-wrap: anywhere; } + .ep-sum { - margin: 10px 0 0; color: var(--muted); - font-size: 14px; line-height: 1.5; + margin: 10px 0 0; + color: var(--muted); + font-size: 14px; + line-height: 1.5; } .m { display: inline-block; - font-size: 11px; font-weight: 800; letter-spacing: 0.06em; - padding: 4px 8px; border-radius: 6px; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.06em; + padding: 4px 8px; + border-radius: 6px; color: #07080b; } -.m-get { background: var(--good); } -.m-post { background: var(--accent); } -.m-put { background: #d9b14a; } -.m-delete { background: var(--bad); color: #fff; } -.m-info { background: var(--info); } + +.m-get { + background: var(--good); +} + +.m-post { + background: var(--accent); +} + +.m-put { + background: #d9b14a; +} + +.m-delete { + background: var(--bad); + color: #fff; +} + +.m-info { + background: var(--info); +} .params { - width: 100%; border-collapse: collapse; + width: 100%; + border-collapse: collapse; margin-top: 14px; font-size: 13px; } + .params th { - text-align: left; font-weight: 600; color: var(--muted); - text-transform: uppercase; font-size: 11px; letter-spacing: 0.06em; - padding: 8px 10px; border-bottom: 1px solid var(--line); -} -.params td { - padding: 8px 10px; border-bottom: 1px solid var(--line); - color: var(--fg); -} -.params td:first-child { width: 160px; } -.params code { - font-family: ui-monospace, "SF Mono", Menlo, monospace; - font-size: 12px; color: var(--accent-2); - background: var(--bg-2); - padding: 2px 6px; border-radius: 4px; + text-align: left; + font-weight: 600; + color: var(--muted); + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.06em; + padding: 8px 10px; + border-bottom: 1px solid var(--line); +} + +.params td { + padding: 8px 10px; + border-bottom: 1px solid var(--line); + color: var(--fg); +} + +.params td:first-child { + width: 160px; +} + +.params code { + font-family: ui-monospace, "SF Mono", Menlo, monospace; + font-size: 12px; + color: var(--accent-2); + background: var(--bg-2); + padding: 2px 6px; + border-radius: 4px; +} + +.examples { + margin-top: 14px; } -.examples { margin-top: 14px; } .examples-h { - font-size: 11px; color: var(--muted); - text-transform: uppercase; letter-spacing: 0.06em; + font-size: 11px; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.06em; margin-bottom: 6px; } + .examples pre { - margin: 0 0 8px; padding: 10px 12px; - background: var(--bg-0); border: 1px solid var(--line); + margin: 0 0 8px; + padding: 10px 12px; + background: var(--bg-0); + border: 1px solid var(--line); border-radius: 8px; font-family: ui-monospace, "SF Mono", Menlo, monospace; - font-size: 12.5px; color: var(--accent-2); + font-size: 12.5px; + color: var(--accent-2); overflow-x: auto; } -.try { margin-top: 14px; } +.try { + margin-top: 14px; +} + .try-row { - display: flex; gap: 8px; align-items: center; + display: flex; + gap: 8px; + align-items: center; margin-bottom: 8px; } + .try-q { flex: 1; - background: var(--bg-2); color: var(--fg); - border: 1px solid var(--line); border-radius: 8px; - padding: 8px 12px; font-size: 13px; + background: var(--bg-2); + color: var(--fg); + border: 1px solid var(--line); + border-radius: 8px; + padding: 8px 12px; + font-size: 13px; font-family: ui-monospace, "SF Mono", Menlo, monospace; outline: none; } -.try-q:focus { border-color: var(--accent); } + +.try-q:focus { + border-color: var(--accent); +} + .try-btn { - background: var(--accent); color: #1a0a00; - border: 0; border-radius: 8px; - padding: 8px 16px; font-size: 13px; font-weight: 700; + background: var(--accent); + color: #1a0a00; + border: 0; + border-radius: 8px; + padding: 8px 16px; + font-size: 13px; + font-weight: 700; cursor: pointer; font-family: inherit; } -.try-btn:hover { background: #ff8a55; } -.try-btn:disabled { opacity: 0.6; cursor: default; } -.try-open { - color: var(--muted); font-size: 12px; - padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; + +.try-btn:hover { + background: #ff8a55; } -.try-open:hover { color: var(--fg); background: var(--bg-2); text-decoration: none; } + +.try-btn:disabled { + opacity: 0.6; + cursor: default; +} + +.try-open { + color: var(--muted); + font-size: 12px; + padding: 6px 10px; + border: 1px solid var(--line); + border-radius: 8px; +} + +.try-open:hover { + color: var(--fg); + background: var(--bg-2); + text-decoration: none; +} + .try-out { - margin: 0; padding: 12px; - background: var(--bg-0); border: 1px solid var(--line); + margin: 0; + padding: 12px; + background: var(--bg-0); + border: 1px solid var(--line); border-radius: 8px; font-family: ui-monospace, "SF Mono", Menlo, monospace; - font-size: 12px; color: var(--fg); - max-height: 320px; overflow: auto; - white-space: pre-wrap; word-break: break-word; -} + font-size: 12px; + color: var(--fg); + max-height: 320px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; +} \ No newline at end of file diff --git a/web/main.js b/web/main.js index 3278a70..8420635 100644 --- a/web/main.js +++ b/web/main.js @@ -122,14 +122,14 @@ function render() { title: 'Upvote', onClick: () => votePlayer(1) }, el('span', { class: 'vote-icon' }, '▲'), - el('span', { class: 'vote-count' }, String(v?.up ?? 0))), + el('span', { class: 'vote-count' }, String(v?.up ?? 0))), el('button', { class: `vote down ${v?.myVote === -1 ? 'on' : ''}`, disabled: !p.stationId, title: 'Downvote', onClick: () => votePlayer(-1) }, el('span', { class: 'vote-icon' }, '▼'), - el('span', { class: 'vote-count' }, String(v?.down ?? 0))) + el('span', { class: 'vote-count' }, String(v?.down ?? 0))) ), el('button', { class: `btn-play ${p.loading ? 'loading' : ''}`, @@ -172,11 +172,11 @@ function render() { title: 'Sort browse list', onChange: (e) => { state.sort = e.target.value; savedGridScroll = 0; refreshStations().then(render); } }, - el('option', { value: 'hot', selected: state.sort === 'hot' }, '🔥 Hot (smart)'), - el('option', { value: 'top', selected: state.sort === 'top' }, '▲ Top voted'), - el('option', { value: 'plays', selected: state.sort === 'plays' }, '▶ Most played'), + el('option', { value: 'hot', selected: state.sort === 'hot' }, '🔥 Hot (smart)'), + el('option', { value: 'top', selected: state.sort === 'top' }, '▲ Top voted'), + el('option', { value: 'plays', selected: state.sort === 'plays' }, '▶ Most played'), el('option', { value: 'controversial', selected: state.sort === 'controversial' }, '⚡ Controversial'), - el('option', { value: 'name', selected: state.sort === 'name' }, 'A → Z') + el('option', { value: 'name', selected: state.sort === 'name' }, 'A → Z') ) : null, el('input', { diff --git a/web/style.css b/web/style.css index 0ed43f1..d73f164 100644 --- a/web/style.css +++ b/web/style.css @@ -1,691 +1,1323 @@ :root { - --bg-0: #07080b; - --bg-1: #0e1116; - --bg-2: #161a22; - --bg-3: #1f242e; - --line: #262b36; - --fg: #e9ecf2; - --muted: #8a90a0; - --muted-2: #5d6373; - --accent: #ff7a3d; - --accent-2: #ffb37a; - --accent-glow: rgba(255, 122, 61, 0.35); - --good: #4ec9a6; - --bad: #ec6a6a; - --radius-sm: 10px; - --radius: 14px; - --radius-lg: 20px; - --pad: 16px; - --shadow-sm: 0 1px 2px rgba(0,0,0,0.4); - --shadow: 0 8px 24px rgba(0,0,0,0.45); - --shadow-lg: 0 18px 40px rgba(0,0,0,0.55); - font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; - font-feature-settings: "ss01", "cv11"; - color-scheme: dark; + --bg-0: #07080b; + --bg-1: #0e1116; + --bg-2: #161a22; + --bg-3: #1f242e; + --line: #262b36; + --fg: #e9ecf2; + --muted: #8a90a0; + --muted-2: #5d6373; + --accent: #ff7a3d; + --accent-2: #ffb37a; + --accent-glow: rgba(255, 122, 61, 0.35); + --good: #4ec9a6; + --bad: #ec6a6a; + --radius-sm: 10px; + --radius: 14px; + --radius-lg: 20px; + --pad: 16px; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.55); + font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + font-feature-settings: "ss01", "cv11"; + color-scheme: dark; } -* { box-sizing: border-box; } -html, body { - margin: 0; padding: 0; - background: radial-gradient(1200px 600px at 30% -10%, rgba(255,122,61,0.08), transparent 60%), - radial-gradient(900px 500px at 100% 110%, rgba(78, 201, 166, 0.06), transparent 60%), - var(--bg-0); - color: var(--fg); +* { + box-sizing: border-box; } + +html, body { - -webkit-tap-highlight-color: transparent; - touch-action: manipulation; - user-select: none; - overflow: hidden; + margin: 0; + padding: 0; + background: radial-gradient(1200px 600px at 30% -10%, rgba(255, 122, 61, 0.08), transparent 60%), + radial-gradient(900px 500px at 100% 110%, rgba(78, 201, 166, 0.06), transparent 60%), + var(--bg-0); + color: var(--fg); } + +body { + -webkit-tap-highlight-color: transparent; + touch-action: manipulation; + user-select: none; + overflow: hidden; +} + button { - font: inherit; color: inherit; - background: none; border: 0; - cursor: pointer; - padding: 0; + font: inherit; + color: inherit; + background: none; + border: 0; + cursor: pointer; + padding: 0; +} + +input, +select, +textarea { + font: inherit; + color: inherit; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--bg-3); + border-radius: 8px; +} + +::-webkit-scrollbar-thumb:hover { + background: #2c323e; } -input, select, textarea { font: inherit; color: inherit; } -::-webkit-scrollbar { width: 8px; height: 8px; } -::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 8px; } -::-webkit-scrollbar-thumb:hover { background: #2c323e; } /* === Kiosk shell 1080 x 660 === */ .kiosk #app { - width: 1080px; - height: 660px; - margin: 0 auto; - display: grid; - grid-template-rows: 92px 1fr; - gap: 12px; - padding: 12px; + width: 1080px; + height: 660px; + margin: 0 auto; + display: grid; + grid-template-rows: 92px 1fr; + gap: 12px; + padding: 12px; } /* === Now-playing bar === */ .now { - display: grid; - grid-template-columns: 1fr auto; - gap: 16px; - padding: 10px 16px; - background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.01)), - var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--radius); - align-items: center; - box-shadow: var(--shadow-sm); - position: relative; - overflow: hidden; + display: grid; + grid-template-columns: 1fr auto; + gap: 16px; + padding: 10px 16px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)), + var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--radius); + align-items: center; + box-shadow: var(--shadow-sm); + position: relative; + overflow: hidden; } + .now::before { - content: ""; position: absolute; inset: 0; - background: radial-gradient(400px 120px at 0% 0%, var(--accent-glow), transparent 70%); - opacity: 0.5; pointer-events: none; + content: ""; + position: absolute; + inset: 0; + background: radial-gradient(400px 120px at 0% 0%, var(--accent-glow), transparent 70%); + opacity: 0.5; + pointer-events: none; } -.now > * { position: relative; } + +.now>* { + position: relative; +} + @keyframes pulse { - 0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); } - 50% { box-shadow: 0 0 0 6px transparent; } + + 0%, + 100% { + box-shadow: 0 0 0 0 var(--accent-glow); + } + + 50% { + box-shadow: 0 0 0 6px transparent; + } +} + +.now .meta { + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; } -.now .meta { min-width: 0; display: flex; flex-direction: column; gap: 4px; } .now .meta .name { - font-size: 19px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.15; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -.now .meta .sub { - color: var(--muted); font-size: 12px; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - display: flex; align-items: center; gap: 6px; -} -.now .meta .tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px; } -.tag { - font-size: 11px; font-weight: 500; - padding: 2px 8px; border-radius: 999px; - background: rgba(255,179,122,0.10); color: var(--accent-2); - border: 1px solid rgba(255,179,122,0.18); + font-size: 19px; + font-weight: 700; + letter-spacing: -0.01em; + line-height: 1.15; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.now .controls { display: flex; gap: 10px; align-items: center; } -.btn-play, .btn-stop { - width: 46px; height: 46px; - border-radius: 50%; - display: flex; align-items: center; justify-content: center; - font-size: 18px; - transition: transform 80ms ease, background 120ms ease, box-shadow 120ms ease; +.now .meta .sub { + color: var(--muted); + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + gap: 6px; } -.btn-play { - background: var(--accent); color: #1a0a00; font-weight: 900; - box-shadow: 0 6px 20px var(--accent-glow); + +.now .meta .tags { + display: flex; + gap: 5px; + flex-wrap: wrap; + margin-top: 2px; } -.btn-play:hover { background: #ff8a55; } -.btn-play:active { transform: scale(0.94); } -.btn-play.loading { opacity: 0.65; } + +.tag { + font-size: 11px; + font-weight: 500; + padding: 2px 8px; + border-radius: 999px; + background: rgba(255, 179, 122, 0.10); + color: var(--accent-2); + border: 1px solid rgba(255, 179, 122, 0.18); +} + +.now .controls { + display: flex; + gap: 10px; + align-items: center; +} + +.btn-play, .btn-stop { - background: var(--bg-2); color: var(--muted); - border: 1px solid var(--line); + width: 46px; + height: 46px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: transform 80ms ease, background 120ms ease, box-shadow 120ms ease; +} + +.btn-play { + background: var(--accent); + color: #1a0a00; + font-weight: 900; + box-shadow: 0 6px 20px var(--accent-glow); +} + +.btn-play:hover { + background: #ff8a55; +} + +.btn-play:active { + transform: scale(0.94); +} + +.btn-play.loading { + opacity: 0.65; +} + +.btn-stop { + background: var(--bg-2); + color: var(--muted); + border: 1px solid var(--line); +} + +.btn-stop:not(:disabled):hover { + background: var(--bg-3); + color: var(--fg); +} + +.btn-stop:disabled { + opacity: 0.35; + cursor: default; } -.btn-stop:not(:disabled):hover { background: var(--bg-3); color: var(--fg); } -.btn-stop:disabled { opacity: 0.35; cursor: default; } .vol { - width: 170px; display: flex; align-items: center; gap: 8px; - padding: 6px 10px; background: var(--bg-2); - border: 1px solid var(--line); border-radius: 999px; + width: 170px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: 999px; } -.vol .vol-icon { font-size: 13px; } -.vol input[type=range] { flex: 1; height: 18px; accent-color: var(--accent); } + +.vol .vol-icon { + font-size: 13px; +} + +.vol input[type=range] { + flex: 1; + height: 18px; + accent-color: var(--accent); +} + .vol .val { - width: 28px; text-align: right; color: var(--muted); - font-variant-numeric: tabular-nums; font-size: 11px; + width: 28px; + text-align: right; + color: var(--muted); + font-variant-numeric: tabular-nums; + font-size: 11px; } /* === Library shell === */ .lib { - background: var(--bg-1); - border: 1px solid var(--line); - border-radius: var(--radius); - padding: 10px 10px 6px; - display: flex; flex-direction: column; - min-height: 0; gap: 8px; - box-shadow: var(--shadow-sm); + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 10px 10px 6px; + display: flex; + flex-direction: column; + min-height: 0; + gap: 8px; + box-shadow: var(--shadow-sm); +} + +.header { + display: flex; + align-items: center; + gap: 8px; +} + +.tabs { + display: flex; + gap: 4px; + flex: 1; + min-width: 0; } -.header { display: flex; align-items: center; gap: 8px; } -.tabs { display: flex; gap: 4px; flex: 1; min-width: 0; } .tab { - padding: 9px 14px; border-radius: 10px; - background: transparent; color: var(--muted); - font-size: 13px; font-weight: 600; min-height: 38px; - border: 1px solid transparent; - transition: background 120ms ease, color 120ms ease, border-color 120ms ease; -} -.tab:hover { color: var(--fg); background: var(--bg-2); } -.tab.active { - background: linear-gradient(180deg, rgba(255,122,61,0.18), rgba(255,122,61,0.08)); - color: var(--accent-2); - border-color: rgba(255,122,61,0.30); + padding: 9px 14px; + border-radius: 10px; + background: transparent; + color: var(--muted); + font-size: 13px; + font-weight: 600; + min-height: 38px; + border: 1px solid transparent; + transition: background 120ms ease, color 120ms ease, border-color 120ms ease; } -.header-tools { display: flex; gap: 6px; align-items: center; } -.search { - width: 220px; - padding: 8px 12px; height: 36px; - background: var(--bg-2); color: var(--fg); - border: 1px solid var(--line); border-radius: 999px; - font-size: 13px; - outline: none; - transition: border-color 120ms ease, box-shadow 120ms ease; +.tab:hover { + color: var(--fg); + background: var(--bg-2); +} + +.tab.active { + background: linear-gradient(180deg, rgba(255, 122, 61, 0.18), rgba(255, 122, 61, 0.08)); + color: var(--accent-2); + border-color: rgba(255, 122, 61, 0.30); +} + +.header-tools { + display: flex; + gap: 6px; + align-items: center; +} + +.search { + width: 220px; + padding: 8px 12px; + height: 36px; + background: var(--bg-2); + color: var(--fg); + border: 1px solid var(--line); + border-radius: 999px; + font-size: 13px; + outline: none; + transition: border-color 120ms ease, box-shadow 120ms ease; +} + +.search::placeholder { + color: var(--muted-2); +} + +.search:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); } -.search::placeholder { color: var(--muted-2); } -.search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } .btn-add { - width: 36px; height: 36px; border-radius: 50%; - background: var(--accent); color: #1a0a00; - font-size: 22px; font-weight: 800; line-height: 1; - box-shadow: 0 4px 12px var(--accent-glow); - transition: transform 80ms ease, background 120ms ease; + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--accent); + color: #1a0a00; + font-size: 22px; + font-weight: 800; + line-height: 1; + box-shadow: 0 4px 12px var(--accent-glow); + transition: transform 80ms ease, background 120ms ease; +} + +.btn-add:hover { + background: #ff8a55; +} + +.btn-add:active { + transform: scale(0.94); } -.btn-add:hover { background: #ff8a55; } -.btn-add:active { transform: scale(0.94); } .btn-random { - display: flex; align-items: center; gap: 6px; - height: 36px; padding: 0 12px; - border-radius: 999px; - background: var(--bg-2); color: var(--fg); - border: 1px solid var(--line); - font-size: 13px; font-weight: 600; - transition: background 120ms, border-color 120ms, transform 80ms; + display: flex; + align-items: center; + gap: 6px; + height: 36px; + padding: 0 12px; + border-radius: 999px; + background: var(--bg-2); + color: var(--fg); + border: 1px solid var(--line); + font-size: 13px; + font-weight: 600; + transition: background 120ms, border-color 120ms, transform 80ms; } -.btn-random:hover { background: var(--bg-3); border-color: rgba(255,122,61,0.4); } -.btn-random:active { transform: scale(0.96); } -.btn-random .rand-icon { font-size: 15px; line-height: 1; } + +.btn-random:hover { + background: var(--bg-3); + border-color: rgba(255, 122, 61, 0.4); +} + +.btn-random:active { + transform: scale(0.96); +} + +.btn-random .rand-icon { + font-size: 15px; + line-height: 1; +} + .btn-random .rand-mode { - font-size: 11px; padding: 2px 6px; border-radius: 999px; - background: rgba(255,122,61,0.18); color: var(--accent-2); - border: 1px solid rgba(255,122,61,0.30); - letter-spacing: 0.02em; + font-size: 11px; + padding: 2px 6px; + border-radius: 999px; + background: rgba(255, 122, 61, 0.18); + color: var(--accent-2); + border: 1px solid rgba(255, 122, 61, 0.30); + letter-spacing: 0.02em; } .btn-docs { - display: flex; align-items: center; justify-content: center; - height: 36px; padding: 0 12px; - border-radius: 999px; - background: var(--bg-2); color: var(--muted); - border: 1px solid var(--line); - font-size: 12px; font-weight: 700; letter-spacing: 0.06em; - text-decoration: none; - transition: background 120ms, color 120ms, border-color 120ms; + display: flex; + align-items: center; + justify-content: center; + height: 36px; + padding: 0 12px; + border-radius: 999px; + background: var(--bg-2); + color: var(--muted); + border: 1px solid var(--line); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.06em; + text-decoration: none; + transition: background 120ms, color 120ms, border-color 120ms; +} + +.btn-docs:hover { + background: var(--bg-3); + color: var(--fg); + border-color: rgba(78, 201, 166, 0.4); } -.btn-docs:hover { background: var(--bg-3); color: var(--fg); border-color: rgba(78,201,166,0.4); } .chips { - display: flex; flex-wrap: wrap; gap: 5px; - max-height: 64px; overflow-y: auto; - padding: 2px; + display: flex; + flex-wrap: wrap; + gap: 5px; + max-height: 64px; + overflow-y: auto; + padding: 2px; } + .chip { - padding: 4px 10px; border-radius: 999px; - background: var(--bg-2); color: var(--muted); - border: 1px solid var(--line); - font-size: 11px; font-weight: 600; min-height: 26px; - transition: background 120ms, color 120ms, border-color 120ms; + padding: 4px 10px; + border-radius: 999px; + background: var(--bg-2); + color: var(--muted); + border: 1px solid var(--line); + font-size: 11px; + font-weight: 600; + min-height: 26px; + transition: background 120ms, color 120ms, border-color 120ms; } -.chip:hover { color: var(--fg); } + +.chip:hover { + color: var(--fg); +} + .chip.active { - background: rgba(255,122,61,0.18); - color: var(--accent-2); - border-color: rgba(255,122,61,0.4); + background: rgba(255, 122, 61, 0.18); + color: var(--accent-2); + border-color: rgba(255, 122, 61, 0.4); } .grid { - flex: 1; min-height: 0; overflow-y: auto; - display: flex; flex-direction: column; - gap: 4px; - padding: 2px 4px 6px 2px; + flex: 1; + min-height: 0; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 4px; + padding: 2px 4px 6px 2px; } + .card { - display: grid; - grid-template-columns: 44px 1fr auto auto; - align-items: center; gap: 12px; - padding: 6px 10px 6px 6px; - background: var(--bg-2); - border: 1px solid transparent; - border-radius: 10px; - min-height: 56px; - text-align: left; - cursor: pointer; - transition: background 100ms ease, border-color 100ms ease, transform 80ms ease; - position: relative; + display: grid; + grid-template-columns: 44px 1fr auto auto; + align-items: center; + gap: 12px; + padding: 6px 10px 6px 6px; + background: var(--bg-2); + border: 1px solid transparent; + border-radius: 10px; + min-height: 56px; + text-align: left; + cursor: pointer; + transition: background 100ms ease, border-color 100ms ease, transform 80ms ease; + position: relative; } -.card:hover { background: var(--bg-3); } -.card:active { transform: scale(0.995); } + +.card:hover { + background: var(--bg-3); +} + +.card:active { + transform: scale(0.995); +} + .card.playing { - background: linear-gradient(90deg, rgba(255,122,61,0.14), var(--bg-2) 60%); - border-color: rgba(255,122,61,0.35); + background: linear-gradient(90deg, rgba(255, 122, 61, 0.14), var(--bg-2) 60%); + border-color: rgba(255, 122, 61, 0.35); } + .card.playing::before { - content: ""; position: absolute; left: 0; top: 8px; bottom: 8px; - width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); + content: ""; + position: absolute; + left: 0; + top: 8px; + bottom: 8px; + width: 3px; + border-radius: 0 3px 3px 0; + background: var(--accent); } + .card .art { - width: 44px; height: 44px; border-radius: 8px; - background: var(--bg-3) center/cover no-repeat; - display: flex; align-items: center; justify-content: center; - font-size: 16px; color: var(--muted-2); - flex-shrink: 0; - border: 1px solid var(--line); - overflow: hidden; + width: 44px; + height: 44px; + border-radius: 8px; + background: var(--bg-3) center/cover no-repeat; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + color: var(--muted-2); + flex-shrink: 0; + border: 1px solid var(--line); + overflow: hidden; } + .card .art .art-img { - width: 100%; height: 100%; object-fit: cover; display: block; + width: 100%; + height: 100%; + object-fit: cover; + display: block; } -.card .card-body { min-width: 0; } + +.card .card-body { + min-width: 0; +} + .card .n { - font-weight: 600; font-size: 14px; line-height: 1.2; - letter-spacing: -0.005em; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-weight: 600; + font-size: 14px; + line-height: 1.2; + letter-spacing: -0.005em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } + .card .g { - font-size: 11.5px; color: var(--muted); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - margin-top: 2px; + font-size: 11.5px; + color: var(--muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-top: 2px; } -.card .fav, .card .more { - width: 32px; height: 32px; - border-radius: 8px; - display: flex; align-items: center; justify-content: center; - font-size: 16px; color: var(--muted); - transition: background 100ms, color 100ms; + +.card .fav, +.card .more { + width: 32px; + height: 32px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + color: var(--muted); + transition: background 100ms, color 100ms; +} + +.card .fav:hover, +.card .more:hover { + background: rgba(255, 255, 255, 0.06); + color: var(--fg); +} + +.card .fav.on { + color: var(--accent); +} + +.card .more { + font-weight: 700; + letter-spacing: 1px; } -.card .fav:hover, .card .more:hover { background: rgba(255,255,255,0.06); color: var(--fg); } -.card .fav.on { color: var(--accent); } -.card .more { font-weight: 700; letter-spacing: 1px; } .empty { - color: var(--muted); padding: 32px 16px; text-align: center; - font-size: 13px; + color: var(--muted); + padding: 32px 16px; + text-align: center; + font-size: 13px; } /* === Login overlay === */ .login { - position: fixed; inset: 0; - background: radial-gradient(800px 500px at 50% 0%, rgba(255,122,61,0.10), transparent 60%), - rgba(7,8,11,0.97); - display: flex; align-items: center; justify-content: center; - z-index: 50; + position: fixed; + inset: 0; + background: radial-gradient(800px 500px at 50% 0%, rgba(255, 122, 61, 0.10), transparent 60%), + rgba(7, 8, 11, 0.97); + display: flex; + align-items: center; + justify-content: center; + z-index: 50; } + .login form { - background: var(--bg-1); border: 1px solid var(--line); - padding: 32px; border-radius: var(--radius-lg); - display: flex; flex-direction: column; gap: 14px; - width: 380px; - box-shadow: var(--shadow-lg); + background: var(--bg-1); + border: 1px solid var(--line); + padding: 32px; + border-radius: var(--radius-lg); + display: flex; + flex-direction: column; + gap: 14px; + width: 380px; + box-shadow: var(--shadow-lg); } -.login h1 { margin: 0 0 8px; font-size: 24px; letter-spacing: -0.01em; } + +.login h1 { + margin: 0 0 8px; + font-size: 24px; + letter-spacing: -0.01em; +} + .login input { - background: var(--bg-2); border: 1px solid var(--line); color: var(--fg); - padding: 13px 14px; border-radius: var(--radius-sm); - font-size: 15px; outline: none; - transition: border-color 120ms, box-shadow 120ms; + background: var(--bg-2); + border: 1px solid var(--line); + color: var(--fg); + padding: 13px 14px; + border-radius: var(--radius-sm); + font-size: 15px; + outline: none; + transition: border-color 120ms, box-shadow 120ms; } -.login input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } + +.login input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + .login button { - background: var(--accent); color: #1a0a00; font-weight: 700; - padding: 13px; border-radius: var(--radius-sm); font-size: 15px; - box-shadow: 0 6px 18px var(--accent-glow); - transition: background 120ms; + background: var(--accent); + color: #1a0a00; + font-weight: 700; + padding: 13px; + border-radius: var(--radius-sm); + font-size: 15px; + box-shadow: 0 6px 18px var(--accent-glow); + transition: background 120ms; +} + +.login button:hover { + background: #ff8a55; +} + +.login .err { + color: var(--bad); + font-size: 13px; + min-height: 18px; } -.login button:hover { background: #ff8a55; } -.login .err { color: var(--bad); font-size: 13px; min-height: 18px; } /* === Add-station dialog === */ dialog.add-station { - border: 1px solid var(--line); - background: var(--bg-1); color: var(--fg); - border-radius: var(--radius-lg); - padding: 0; max-width: 520px; width: 90%; - box-shadow: var(--shadow-lg); + border: 1px solid var(--line); + background: var(--bg-1); + color: var(--fg); + border-radius: var(--radius-lg); + padding: 0; + max-width: 520px; + width: 90%; + box-shadow: var(--shadow-lg); } -dialog.add-station::backdrop { background: rgba(7,8,11,0.65); backdrop-filter: blur(4px); } + +dialog.add-station::backdrop { + background: rgba(7, 8, 11, 0.65); + backdrop-filter: blur(4px); +} + dialog.add-station form { - padding: 24px; display: flex; flex-direction: column; gap: 12px; + padding: 24px; + display: flex; + flex-direction: column; + gap: 12px; } -dialog.add-station h2 { margin: 0 0 4px; font-size: 18px; letter-spacing: -0.01em; } + +dialog.add-station h2 { + margin: 0 0 4px; + font-size: 18px; + letter-spacing: -0.01em; +} + dialog.add-station label { - display: flex; flex-direction: column; gap: 4px; - font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; + display: flex; + flex-direction: column; + gap: 4px; + font-size: 11px; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.06em; } + dialog.add-station input, dialog.add-station select { - background: var(--bg-2); border: 1px solid var(--line); color: var(--fg); - padding: 9px 11px; border-radius: var(--radius-sm); - font-size: 14px; outline: none; - transition: border-color 120ms, box-shadow 120ms; + background: var(--bg-2); + border: 1px solid var(--line); + color: var(--fg); + padding: 9px 11px; + border-radius: var(--radius-sm); + font-size: 14px; + outline: none; + transition: border-color 120ms, box-shadow 120ms; } + dialog.add-station input:focus, dialog.add-station select:focus { - border-color: var(--accent); - box-shadow: 0 0 0 3px var(--accent-glow); + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); } -dialog.add-station .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } -dialog.add-station .err { color: var(--bad); font-size: 12px; min-height: 14px; } -dialog.add-station .actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; } + +dialog.add-station .row2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +dialog.add-station .err { + color: var(--bad); + font-size: 12px; + min-height: 14px; +} + +dialog.add-station .actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 4px; +} + .btn-primary { - background: var(--accent); color: #1a0a00; font-weight: 700; - padding: 9px 16px; border-radius: var(--radius-sm); font-size: 14px; - transition: background 120ms; + background: var(--accent); + color: #1a0a00; + font-weight: 700; + padding: 9px 16px; + border-radius: var(--radius-sm); + font-size: 14px; + transition: background 120ms; } -.btn-primary:hover { background: #ff8a55; } + +.btn-primary:hover { + background: #ff8a55; +} + .btn-ghost { - background: transparent; color: var(--muted); - padding: 9px 16px; border-radius: var(--radius-sm); font-size: 14px; - border: 1px solid var(--line); - transition: color 120ms, background 120ms; + background: transparent; + color: var(--muted); + padding: 9px 16px; + border-radius: var(--radius-sm); + font-size: 14px; + border: 1px solid var(--line); + transition: color 120ms, background 120ms; +} + +.btn-ghost:hover { + color: var(--fg); + background: var(--bg-2); } -.btn-ghost:hover { color: var(--fg); background: var(--bg-2); } /* === Context menu (API endpoints) === */ .ctx-menu { - position: fixed; - z-index: 100; - min-width: 360px; max-width: 460px; - background: var(--bg-1); color: var(--fg); - border: 1px solid var(--line); - border-radius: var(--radius); - box-shadow: var(--shadow-lg); - padding: 10px; - display: flex; flex-direction: column; gap: 2px; - animation: ctxIn 100ms ease-out; + position: fixed; + z-index: 100; + min-width: 360px; + max-width: 460px; + background: var(--bg-1); + color: var(--fg); + border: 1px solid var(--line); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); + padding: 10px; + display: flex; + flex-direction: column; + gap: 2px; + animation: ctxIn 100ms ease-out; } -@keyframes ctxIn { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } } + +@keyframes ctxIn { + from { + opacity: 0; + transform: translateY(-4px) scale(0.98); + } + + to { + opacity: 1; + transform: none; + } +} + .ctx-title { - font-weight: 700; font-size: 13px; padding: 4px 8px 0; - letter-spacing: -0.005em; + font-weight: 700; + font-size: 13px; + padding: 4px 8px 0; + letter-spacing: -0.005em; } + .ctx-sub { - font-size: 10.5px; color: var(--muted-2); - padding: 0 8px 8px; border-bottom: 1px solid var(--line); - font-family: ui-monospace, "SF Mono", Menlo, monospace; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: 10.5px; + color: var(--muted-2); + padding: 0 8px 8px; + border-bottom: 1px solid var(--line); + font-family: ui-monospace, "SF Mono", Menlo, monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } + .ctx-row { - display: grid; grid-template-columns: 1fr auto auto; - gap: 6px; align-items: center; - padding: 6px 8px; - border-radius: 8px; + display: grid; + grid-template-columns: 1fr auto auto; + gap: 6px; + align-items: center; + padding: 6px 8px; + border-radius: 8px; } -.ctx-row:hover { background: var(--bg-2); } -.ctx-row-text { min-width: 0; } -.ctx-label { font-size: 12px; color: var(--fg); font-weight: 500; } + +.ctx-row:hover { + background: var(--bg-2); +} + +.ctx-row-text { + min-width: 0; +} + +.ctx-label { + font-size: 12px; + color: var(--fg); + font-weight: 500; +} + .ctx-url { - font-size: 11px; color: var(--muted); - font-family: ui-monospace, "SF Mono", Menlo, monospace; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: 11px; + color: var(--muted); + font-family: ui-monospace, "SF Mono", Menlo, monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } + .ctx-btn { - width: 28px; height: 28px; border-radius: 6px; - background: var(--bg-2); color: var(--muted); - font-size: 13px; - display: flex; align-items: center; justify-content: center; - transition: background 100ms, color 100ms; - border: 1px solid var(--line); + width: 28px; + height: 28px; + border-radius: 6px; + background: var(--bg-2); + color: var(--muted); + font-size: 13px; + display: flex; + align-items: center; + justify-content: center; + transition: background 100ms, color 100ms; + border: 1px solid var(--line); } -.ctx-btn:hover { background: var(--bg-3); color: var(--fg); } -.ctx-empty { padding: 8px; color: var(--muted); font-size: 12px; } + +.ctx-btn:hover { + background: var(--bg-3); + color: var(--fg); +} + +.ctx-empty { + padding: 8px; + color: var(--muted); + font-size: 12px; +} + .ctx-danger { - margin-top: 4px; - padding: 7px 10px; border-radius: 8px; - background: transparent; color: var(--bad); - border: 1px solid rgba(236,106,106,0.25); - font-size: 12px; font-weight: 600; - text-align: left; + margin-top: 4px; + padding: 7px 10px; + border-radius: 8px; + background: transparent; + color: var(--bad); + border: 1px solid rgba(236, 106, 106, 0.25); + font-size: 12px; + font-weight: 600; + text-align: left; +} + +.ctx-danger:hover { + background: rgba(236, 106, 106, 0.10); } -.ctx-danger:hover { background: rgba(236,106,106,0.10); } /* === Toast === */ .toast { - position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); - background: var(--bg-1); padding: 10px 18px; border-radius: 999px; - font-size: 13px; color: var(--fg); z-index: 200; - border: 1px solid var(--line); - box-shadow: var(--shadow); - animation: toastIn 180ms ease-out; + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: var(--bg-1); + padding: 10px 18px; + border-radius: 999px; + font-size: 13px; + color: var(--fg); + z-index: 200; + border: 1px solid var(--line); + box-shadow: var(--shadow); + animation: toastIn 180ms ease-out; +} + +@keyframes toastIn { + from { + opacity: 0; + transform: translate(-50%, 8px); + } + + to { + opacity: 1; + transform: translate(-50%, 0); + } } -@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } } /* ============================================================ MINIMAL HIGH-CONTRAST THEME OVERRIDE Flat surfaces, sharp 90deg corners, monochrome + single accent. ============================================================ */ :root { - --bg-0: #000000; - --bg-1: #0a0a0a; - --bg-2: #141414; - --bg-3: #1f1f1f; - --line: #2e2e2e; - --fg: #ffffff; - --muted: #a0a0a0; - --muted-2: #6a6a6a; - --accent: #ff5b00; - --accent-2: #ff5b00; - --accent-glow: transparent; - --good: #00d27a; - --bad: #ff3030; - --radius-sm: 0; - --radius: 0; - --radius-lg: 0; - --shadow-sm: none; - --shadow: none; - --shadow-lg: none; + --bg-0: #000000; + --bg-1: #0a0a0a; + --bg-2: #141414; + --bg-3: #1f1f1f; + --line: #2e2e2e; + --fg: #ffffff; + --muted: #a0a0a0; + --muted-2: #6a6a6a; + --accent: #ff5b00; + --accent-2: #ff5b00; + --accent-glow: transparent; + --good: #00d27a; + --bad: #ff3030; + --radius-sm: 0; + --radius: 0; + --radius-lg: 0; + --shadow-sm: none; + --shadow: none; + --shadow-lg: none; } -html, body { - background: var(--bg-0) !important; +html, +body { + background: var(--bg-0) !important; } -*, *::before, *::after { border-radius: 0 !important; } -button, input, select, textarea, dialog { border-radius: 0 !important; } +*, +*::before, +*::after { + border-radius: 0 !important; +} -::-webkit-scrollbar-thumb { background: var(--bg-3) !important; } -::-webkit-scrollbar-thumb:hover { background: var(--line) !important; } +button, +input, +select, +textarea, +dialog { + border-radius: 0 !important; +} + +::-webkit-scrollbar-thumb { + background: var(--bg-3) !important; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--line) !important; +} /* Flatten decorative gradients/glows */ .now { - background: var(--bg-1) !important; - box-shadow: none !important; - border-color: var(--line) !important; -} -.now::before { display: none !important; } -@keyframes pulse { - 0%, 100% { border-color: var(--accent); } - 50% { border-color: var(--line); } + background: var(--bg-1) !important; + box-shadow: none !important; + border-color: var(--line) !important; } -.now .meta .name { text-transform: uppercase; letter-spacing: 0.01em; font-weight: 800; } +.now::before { + display: none !important; +} + +@keyframes pulse { + + 0%, + 100% { + border-color: var(--accent); + } + + 50% { + border-color: var(--line); + } +} + +.now .meta .name { + text-transform: uppercase; + letter-spacing: 0.01em; + font-weight: 800; +} .tag { - background: var(--bg-2) !important; - color: var(--fg) !important; - border-color: var(--line) !important; - text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; + background: var(--bg-2) !important; + color: var(--fg) !important; + border-color: var(--line) !important; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 700; } -.btn-play, .btn-stop, .btn-add { - box-shadow: none !important; - border: 1px solid var(--line); - transition: background 80ms linear, color 80ms linear, transform 60ms linear !important; +.btn-play, +.btn-stop, +.btn-add { + box-shadow: none !important; + border: 1px solid var(--line); + transition: background 80ms linear, color 80ms linear, transform 60ms linear !important; } -.btn-play, .btn-add { - background: var(--accent) !important; - color: #000 !important; - border-color: var(--accent) !important; + +.btn-play, +.btn-add { + background: var(--accent) !important; + color: #000 !important; + border-color: var(--accent) !important; } -.btn-play:hover, .btn-add:hover { - background: #fff !important; - color: #000 !important; - border-color: #fff !important; + +.btn-play:hover, +.btn-add:hover { + background: #fff !important; + color: #000 !important; + border-color: #fff !important; } -.btn-stop { background: var(--bg-2) !important; color: var(--fg) !important; } + +.btn-stop { + background: var(--bg-2) !important; + color: var(--fg) !important; +} + .btn-stop:not(:disabled):hover { - background: #fff !important; color: #000 !important; border-color: #fff !important; + background: #fff !important; + color: #000 !important; + border-color: #fff !important; } .vol { - background: var(--bg-2) !important; - border-color: var(--line) !important; + background: var(--bg-2) !important; + border-color: var(--line) !important; } .lib { - background: var(--bg-1) !important; - box-shadow: none !important; - border-color: var(--line) !important; + background: var(--bg-1) !important; + box-shadow: none !important; + border-color: var(--line) !important; } /* Tabs: connected high-contrast segmented control */ -.tabs { gap: 0 !important; } -.tab { - border: 1px solid var(--line) !important; - margin-right: -1px !important; - background: transparent !important; - text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; font-size: 12px; +.tabs { + gap: 0 !important; } -.tab:hover { background: var(--bg-2) !important; color: var(--fg) !important; } + +.tab { + border: 1px solid var(--line) !important; + margin-right: -1px !important; + background: transparent !important; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 700; + font-size: 12px; +} + +.tab:hover { + background: var(--bg-2) !important; + color: var(--fg) !important; +} + .tab.active { - background: var(--accent) !important; - color: #000 !important; - border-color: var(--accent) !important; - position: relative; z-index: 1; + background: var(--accent) !important; + color: #000 !important; + border-color: var(--accent) !important; + position: relative; + z-index: 1; } .search { - background: var(--bg-2) !important; - border-color: var(--line) !important; + background: var(--bg-2) !important; + border-color: var(--line) !important; +} + +.search:focus { + border-color: var(--accent) !important; + box-shadow: none !important; } -.search:focus { border-color: var(--accent) !important; box-shadow: none !important; } .chip { - background: var(--bg-2) !important; - border-color: var(--line) !important; - text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700; + background: var(--bg-2) !important; + border-color: var(--line) !important; + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 700; } -.chip:hover { color: var(--fg) !important; border-color: var(--fg) !important; } + +.chip:hover { + color: var(--fg) !important; + border-color: var(--fg) !important; +} + .chip.active { - background: var(--accent) !important; - color: #000 !important; - border-color: var(--accent) !important; + background: var(--accent) !important; + color: #000 !important; + border-color: var(--accent) !important; } /* Card list: tight, sharp, single-pixel grid */ -.grid { gap: 0 !important; } -.card { - background: var(--bg-1) !important; - border: 1px solid var(--line) !important; - margin-bottom: -1px; - transition: background 60ms linear, border-color 60ms linear !important; -} -.card:hover { - background: var(--bg-2) !important; - border-color: var(--muted-2) !important; - z-index: 1; -} -.card:active { transform: none !important; } -.card.playing { - background: var(--bg-2) !important; - border-color: var(--accent) !important; - z-index: 2; -} -.card.playing::before { - left: 0 !important; top: 0 !important; bottom: 0 !important; - width: 4px !important; background: var(--accent) !important; -} -.card .art { box-shadow: none !important; } -.card .n { font-weight: 700; } -.card .g { text-transform: uppercase; letter-spacing: 0.03em; } -.card .fav:hover, .card .more:hover { - background: var(--bg-3) !important; color: var(--fg) !important; - border: 1px solid var(--line) !important; +.grid { + gap: 0 !important; } -.empty { text-transform: uppercase; letter-spacing: 0.06em; } +.card { + background: var(--bg-1) !important; + border: 1px solid var(--line) !important; + margin-bottom: -1px; + transition: background 60ms linear, border-color 60ms linear !important; +} + +.card:hover { + background: var(--bg-2) !important; + border-color: var(--muted-2) !important; + z-index: 1; +} + +.card:active { + transform: none !important; +} + +.card.playing { + background: var(--bg-2) !important; + border-color: var(--accent) !important; + z-index: 2; +} + +.card.playing::before { + left: 0 !important; + top: 0 !important; + bottom: 0 !important; + width: 4px !important; + background: var(--accent) !important; +} + +.card .art { + box-shadow: none !important; +} + +.card .n { + font-weight: 700; +} + +.card .g { + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.card .fav:hover, +.card .more:hover { + background: var(--bg-3) !important; + color: var(--fg) !important; + border: 1px solid var(--line) !important; +} + +.empty { + text-transform: uppercase; + letter-spacing: 0.06em; +} /* Login */ -.login { background: #000 !important; } +.login { + background: #000 !important; +} + .login form { - border: 1px solid #fff !important; - box-shadow: none !important; + border: 1px solid #fff !important; + box-shadow: none !important; } -.login h1 { text-transform: uppercase; letter-spacing: 0.04em; font-weight: 900; } -.login input { background: var(--bg-2) !important; border-color: var(--line) !important; } -.login input:focus { border-color: var(--accent) !important; box-shadow: none !important; } + +.login h1 { + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 900; +} + +.login input { + background: var(--bg-2) !important; + border-color: var(--line) !important; +} + +.login input:focus { + border-color: var(--accent) !important; + box-shadow: none !important; +} + .login button { - background: var(--accent) !important; - color: #000 !important; - border: 1px solid var(--accent) !important; - box-shadow: none !important; - text-transform: uppercase; letter-spacing: 0.08em; font-weight: 900; + background: var(--accent) !important; + color: #000 !important; + border: 1px solid var(--accent) !important; + box-shadow: none !important; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 900; +} + +.login button:hover { + background: #fff !important; + color: #000 !important; + border-color: #fff !important; } -.login button:hover { background: #fff !important; color: #000 !important; border-color: #fff !important; } /* Dialogs */ dialog.add-station { - border: 1px solid #fff !important; - box-shadow: none !important; + border: 1px solid #fff !important; + box-shadow: none !important; } + dialog.add-station::backdrop { - background: rgba(0,0,0,0.85) !important; - backdrop-filter: none !important; + background: rgba(0, 0, 0, 0.85) !important; + backdrop-filter: none !important; } -dialog.add-station h2 { text-transform: uppercase; letter-spacing: 0.04em; font-weight: 900; } -dialog.add-station label { letter-spacing: 0.08em; font-weight: 700; } + +dialog.add-station h2 { + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 900; +} + +dialog.add-station label { + letter-spacing: 0.08em; + font-weight: 700; +} + dialog.add-station input, -dialog.add-station select { background: var(--bg-2) !important; border-color: var(--line) !important; } +dialog.add-station select { + background: var(--bg-2) !important; + border-color: var(--line) !important; +} + dialog.add-station input:focus, -dialog.add-station select:focus { border-color: var(--accent) !important; box-shadow: none !important; } +dialog.add-station select:focus { + border-color: var(--accent) !important; + box-shadow: none !important; +} .btn-primary { - background: var(--accent) !important; - color: #000 !important; - border: 1px solid var(--accent) !important; - text-transform: uppercase; letter-spacing: 0.06em; font-weight: 900; + background: var(--accent) !important; + color: #000 !important; + border: 1px solid var(--accent) !important; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 900; } -.btn-primary:hover { background: #fff !important; color: #000 !important; border-color: #fff !important; } + +.btn-primary:hover { + background: #fff !important; + color: #000 !important; + border-color: #fff !important; +} + .btn-ghost { - background: transparent !important; border: 1px solid var(--line) !important; - text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; + background: transparent !important; + border: 1px solid var(--line) !important; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 700; +} + +.btn-ghost:hover { + color: var(--fg) !important; + background: var(--bg-2) !important; + border-color: var(--fg) !important; } -.btn-ghost:hover { color: var(--fg) !important; background: var(--bg-2) !important; border-color: var(--fg) !important; } /* Context menu */ .ctx-menu { - border: 1px solid #fff !important; - box-shadow: none !important; + border: 1px solid #fff !important; + box-shadow: none !important; } -.ctx-title { text-transform: uppercase; letter-spacing: 0.06em; font-weight: 900; font-size: 12px; } -.ctx-row { border: 1px solid transparent; } -.ctx-row:hover { background: var(--bg-2) !important; border-color: var(--line); } + +.ctx-title { + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 900; + font-size: 12px; +} + +.ctx-row { + border: 1px solid transparent; +} + +.ctx-row:hover { + background: var(--bg-2) !important; + border-color: var(--line); +} + .ctx-btn { - background: var(--bg-2) !important; - border: 1px solid var(--line) !important; - color: var(--muted); + background: var(--bg-2) !important; + border: 1px solid var(--line) !important; + color: var(--muted); } -.ctx-btn:hover { background: #fff !important; color: #000 !important; border-color: #fff !important; } + +.ctx-btn:hover { + background: #fff !important; + color: #000 !important; + border-color: #fff !important; +} + .ctx-danger { - background: transparent !important; - border: 1px solid var(--bad) !important; - color: var(--bad) !important; - text-transform: uppercase; letter-spacing: 0.06em; font-weight: 800; + background: transparent !important; + border: 1px solid var(--bad) !important; + color: var(--bad) !important; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 800; +} + +.ctx-danger:hover { + background: var(--bad) !important; + color: #000 !important; } -.ctx-danger:hover { background: var(--bad) !important; color: #000 !important; } /* Toast */ .toast { - background: #fff !important; - color: #000 !important; - border: 1px solid #fff !important; - box-shadow: none !important; - text-transform: uppercase; letter-spacing: 0.06em; font-weight: 800; font-size: 12px; + background: #fff !important; + color: #000 !important; + border: 1px solid #fff !important; + box-shadow: none !important; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 800; + font-size: 12px; } /* ============================================================ @@ -694,76 +1326,136 @@ dialog.add-station select:focus { border-color: var(--accent) !important; box-sh ============================================================ */ .vote-group { - display: flex; - gap: 0; - margin-right: 6px; + display: flex; + gap: 0; + margin-right: 6px; } + .vote { - display: flex; align-items: center; gap: 6px; - height: 46px; min-width: 64px; - padding: 0 12px; - background: var(--bg-2) !important; - color: var(--fg); - border: 1px solid var(--line) !important; - font-weight: 800; font-size: 14px; - font-variant-numeric: tabular-nums; - transition: background 80ms linear, color 80ms linear, border-color 80ms linear !important; + display: flex; + align-items: center; + gap: 6px; + height: 46px; + min-width: 64px; + padding: 0 12px; + background: var(--bg-2) !important; + color: var(--fg); + border: 1px solid var(--line) !important; + font-weight: 800; + font-size: 14px; + font-variant-numeric: tabular-nums; + transition: background 80ms linear, color 80ms linear, border-color 80ms linear !important; } -.vote + .vote { margin-left: -1px; } -.vote:disabled { opacity: 0.35; cursor: not-allowed; } -.vote .vote-icon { font-size: 17px; line-height: 1; } -.vote .vote-count { font-size: 13px; letter-spacing: 0.02em; } + +.vote+.vote { + margin-left: -1px; +} + +.vote:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.vote .vote-icon { + font-size: 17px; + line-height: 1; +} + +.vote .vote-count { + font-size: 13px; + letter-spacing: 0.02em; +} + .vote.up:not(:disabled):hover { - background: var(--good) !important; color: #000 !important; border-color: var(--good) !important; + background: var(--good) !important; + color: #000 !important; + border-color: var(--good) !important; } + .vote.down:not(:disabled):hover { - background: var(--bad) !important; color: #000 !important; border-color: var(--bad) !important; + background: var(--bad) !important; + color: #000 !important; + border-color: var(--bad) !important; } + .vote.up.on { - background: var(--good) !important; color: #000 !important; border-color: var(--good) !important; + background: var(--good) !important; + color: #000 !important; + border-color: var(--good) !important; } + .vote.down.on { - background: var(--bad) !important; color: #000 !important; border-color: var(--bad) !important; + background: var(--bad) !important; + color: #000 !important; + border-color: var(--bad) !important; } /* Sort dropdown next to search */ .sort { - height: 36px; - padding: 0 28px 0 12px; - background: var(--bg-2) !important; - color: var(--fg); - border: 1px solid var(--line) !important; - font-size: 12px; font-weight: 700; - text-transform: uppercase; letter-spacing: 0.05em; - appearance: none; - background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), - linear-gradient(135deg, var(--muted) 50%, transparent 50%); - background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%; - background-size: 5px 5px, 5px 5px; - background-repeat: no-repeat; - outline: none; - cursor: pointer; + height: 36px; + padding: 0 28px 0 12px; + background: var(--bg-2) !important; + color: var(--fg); + border: 1px solid var(--line) !important; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + appearance: none; + background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), + linear-gradient(135deg, var(--muted) 50%, transparent 50%); + background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%; + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; + outline: none; + cursor: pointer; +} + +.sort:focus, +.sort:hover { + border-color: var(--accent) !important; +} + +.sort option { + background: var(--bg-1); + color: var(--fg); } -.sort:focus, .sort:hover { border-color: var(--accent) !important; } -.sort option { background: var(--bg-1); color: var(--fg); } /* Grid card: add a column for the score badge. */ .card { - grid-template-columns: 44px 1fr auto auto auto !important; + grid-template-columns: 44px 1fr auto auto auto !important; } -.score-badge { - display: inline-flex; align-items: center; justify-content: center; - min-width: 36px; height: 26px; - padding: 0 8px; - background: var(--bg-2); - border: 1px solid var(--line); - color: var(--muted); - font-weight: 800; font-size: 12px; - font-variant-numeric: tabular-nums; - letter-spacing: 0.02em; -} -.score-badge.pos { color: var(--good); border-color: rgba(0,210,122,0.35); } -.score-badge.neg { color: var(--bad); border-color: rgba(255,48,48,0.35); } -.score-badge.neu { color: var(--muted-2); } -.card.playing .score-badge { border-color: var(--accent); } +.score-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + height: 26px; + padding: 0 8px; + background: var(--bg-2); + border: 1px solid var(--line); + color: var(--muted); + font-weight: 800; + font-size: 12px; + font-variant-numeric: tabular-nums; + letter-spacing: 0.02em; +} + +.score-badge.pos { + color: var(--good); + border-color: rgba(0, 210, 122, 0.35); +} + +.score-badge.neg { + color: var(--bad); + border-color: rgba(255, 48, 48, 0.35); +} + +.score-badge.neu { + color: var(--muted-2); +} + +.card.playing .score-badge { + border-color: var(--accent); +} \ No newline at end of file