Files
radio-explorer/server/public/assets/master-B8Vyo4--.css
Marco Mooren 29423288ca feat: add multi-user support for favorites management and room clock synchronization
- Implemented a new API endpoint for retrieving and managing user favorites in /api/users.
- Added functionality for admins to edit the shared "main" user's favorites.
- Created a one-shot DB smoke test script for verifying multi-user kiosk migrations.
- Introduced a RoomClock class for synchronizing server time across clients using WebSocket.
2026-05-13 13:53:12 +02:00

2 lines
15 KiB
CSS

:root{--bg-0: #07080b;--bg-1: #0e1116;--bg-2: #161a22;--bg-3: #1f242e;--fg: #e9ecf2;--muted: #8a90a0;--muted-2: #5d6373;--line: #262b36;--accent: #ff7a3d;--accent-2: #ffb37a;--accent-glow: rgba(255, 122, 61, .35);--ok: #4ec9a6;--warn: #ffd166;--err: #ec6a6a}*{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;background:radial-gradient(1200px 600px at 30% -10%,rgba(255,122,61,.06),transparent 60%),var(--bg-0);color:var(--fg);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,system-ui,sans-serif;overflow:hidden}#app{height:100%;width:100%}.login{display:grid;place-items:center;height:100%}.login form{display:grid;gap:8px;padding:24px;background:var(--bg-1);border:1px solid var(--line);min-width:280px}.login input,.login button{padding:10px 12px;background:var(--bg-2);color:var(--fg);border:1px solid var(--line);font-size:14px}.login button{background:var(--accent);color:#1a0a00;font-weight:700;cursor:pointer;border-color:var(--accent)}.login .err{color:var(--err);font-size:12px;min-height:1em}.master{display:grid;grid-template-rows:56px auto 1fr;height:100%;gap:0}.master .topbar{display:flex;align-items:center;gap:10px;padding:0 18px;border-bottom:1px solid var(--line);background:var(--bg-1);font-size:13px}.master .topbar h1{margin:0;font-size:14px;font-weight:700;letter-spacing:.06em}.master .topbar .grow{flex:1}.master .topbar .pill{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;height:32px;background:var(--bg-2);border:1px solid var(--line);font-size:12px}.master .topbar select,.master .topbar input,.master .topbar button{background:var(--bg-2);color:var(--fg);border:1px solid var(--line);height:32px;padding:0 12px;font-size:12px}.master .topbar button{cursor:pointer}.master .topbar .peers{color:var(--muted);font-variant-numeric:tabular-nums}.master .topbar .status-on{color:var(--ok)}.master .topbar .status-off{color:var(--muted-2)}.master .stage{padding:12px 16px 8px;overflow:hidden;min-height:0}.master .np{display:grid;grid-template-columns:auto 1fr;gap:20px;align-items:stretch;background:linear-gradient(135deg,#ffffff08,#ffffff03),var(--bg-1);border:1px solid var(--line);padding:14px;position:relative;overflow:hidden}.master .np:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:radial-gradient(600px 220px at 0% 0%,var(--accent-glow),transparent 70%);opacity:.35;pointer-events:none}.master .np>*{position:relative}.master .np .art{height:clamp(180px,28vh,300px);aspect-ratio:1 / 1;width:auto;background:var(--bg-2);background-size:cover;background-position:center;box-shadow:0 24px 60px #0009;border:1px solid var(--line);position:relative;overflow:hidden;flex-shrink:0}.master .np .art .art-img{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;object-fit:cover;display:block}.master .np .art.empty:after{content:"♪";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;font-size:64px;color:var(--muted-2)}.master .np .meta{display:grid;gap:6px;align-content:center;min-width:0}.master .np .meta .tiny{color:var(--muted-2);text-transform:uppercase;letter-spacing:.14em;font-size:10px}.master .np .meta h2{margin:0;font-size:clamp(20px,2.4vw,34px);font-weight:800;line-height:1.05;letter-spacing:-.01em}.master .np .meta .genres{display:flex;flex-wrap:wrap;gap:6px}.master .np .meta .tag{padding:3px 10px;background:#ffb37a1a;color:var(--accent-2);border:1px solid rgba(255,179,122,.18);font-size:12px}.master .np .meta .stats{display:flex;gap:16px;color:var(--muted);font-size:13px}.master .np .meta .stats b{color:var(--fg)}.master .np .meta .np-spectrum{display:block;width:100%;height:72px;border-radius:8px;background:linear-gradient(180deg,#ffffff05,#00000040);border:1px solid rgba(255,255,255,.06)}.master .topbar .role-pill{background:#50dcff1a;border-color:#50dcff59;color:#b4ebff;letter-spacing:.02em}.master .np .transport{display:flex;align-items:center;gap:10px;margin-top:2px}.master .ctrl{width:38px;height:38px;display:grid;place-items:center;background:var(--bg-2);border:1px solid var(--line);color:var(--fg);font-size:15px;cursor:pointer;transition:background .12s,border-color .12s,transform 80ms}.master .ctrl:hover:not(:disabled){background:var(--bg-3);border-color:var(--accent)}.master .ctrl:active:not(:disabled){transform:scale(.96)}.master .ctrl:disabled{opacity:.35;cursor:default}.master .ctrl.primary{width:46px;height:46px;background:var(--accent);border-color:var(--accent);color:#1a0a00;font-size:18px;font-weight:900;box-shadow:0 6px 20px var(--accent-glow)}.master .ctrl.primary:hover:not(:disabled){background:#ff8a55;border-color:#ff8a55}.master .vol{display:flex;align-items:center;gap:10px;padding:6px 12px;background:var(--bg-2);border:1px solid var(--line);color:var(--muted);font-size:12px;flex:1;max-width:320px}.master .vol .vol-icon{font-size:14px}.master .vol input[type=range]{flex:1;accent-color:var(--accent)}.master .vol .val{width:36px;text-align:right;color:var(--muted);font-variant-numeric:tabular-nums}.master .np .peer-line{display:flex;flex-wrap:wrap;align-items:center;gap:8px;color:var(--muted);font-size:12px;margin-top:4px}.master .np .peer-line-label{color:var(--muted-2);text-transform:uppercase;letter-spacing:.1em;font-size:10px}.master .peer{display:inline-flex;align-items:center;gap:6px}.master .peer .role-tag{font-size:10px;padding:1px 6px;background:var(--bg-2);border:1px solid var(--line);text-transform:uppercase;letter-spacing:.08em}.master .peer.role-display .role-tag{color:var(--accent-2);border-color:#ff7a3d66}.master .stations-bar{padding:0 24px 20px;overflow:hidden;min-height:0}.master .err-banner{background:#ec6a6a1f;border:1px solid rgba(236,106,106,.4);color:var(--err);padding:4px 10px;font-size:12px;margin-left:12px}.master .np .meta .title-row{display:flex;align-items:center;gap:14px}.master .np .meta .title-row h2{margin:0;flex:1;min-width:0;word-break:break-word}.master .np .meta .fav-toggle{width:40px;height:40px;background:var(--bg-2);border:1px solid var(--line);color:var(--muted);font-size:20px;line-height:1;cursor:pointer;display:grid;place-items:center;flex-shrink:0;transition:background .12s,color .12s,border-color .12s}.master .np .meta .fav-toggle:hover{border-color:var(--accent);color:var(--accent-2)}.master .np .meta .fav-toggle.on{background:linear-gradient(180deg,#ff7a3d40,#ff7a3d1f);border-color:#ff7a3d80;color:var(--accent);text-shadow:0 0 12px var(--accent-glow)}.master .topbar .out-btn{cursor:pointer;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.master .topbar .out-btn.active{border-color:var(--accent);color:var(--accent-2)}.out-popover-wrap{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;display:grid;place-items:start center;padding-top:64px;z-index:50;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.out-popover{width:min(440px,92vw);max-height:70vh;overflow:auto;background:var(--bg-1);border:1px solid var(--line);padding:14px;box-shadow:0 24px 80px #00000080}.out-popover-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.out-popover-head h3{flex:1;margin:0;font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em}.out-popover .close{width:32px;height:32px;background:var(--bg-2);border:1px solid var(--line);color:var(--fg);font-size:18px;cursor:pointer}.out-popover .device-list{display:grid;gap:6px}.out-popover .device{display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--bg-2);border:1px solid var(--line);cursor:pointer;font-size:13px;color:var(--fg);text-align:left}.out-popover .device:hover{border-color:var(--accent)}.out-popover .device.active{background:linear-gradient(180deg,#ff7a3d2e,#ff7a3d14);border-color:#ff7a3d66;color:var(--accent-2)}.out-popover .device .dot{width:8px;height:8px;border-radius:50%;background:var(--muted-2);flex-shrink:0}.out-popover .device.active .dot{background:var(--accent);box-shadow:0 0 8px var(--accent-glow)}.out-popover .device .name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.out-popover .device .kind{color:var(--muted-2);font-size:11px;text-transform:uppercase;letter-spacing:.06em}.master .favs-card{background:var(--bg-1);border:1px solid var(--line);padding:10px 12px;height:100%;display:flex;flex-direction:column;min-height:0}.master .favs-card .favs-header{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-shrink:0}.master .favs-card .favs-header h3{flex:1;margin:0;font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-weight:600}.master .favs-card .genre-filter{background:var(--bg-2);color:var(--fg);border:1px solid var(--line);padding:4px 8px;font-size:12px;max-width:200px}.master .favs-nav{width:32px;height:32px;display:grid;place-items:center;background:var(--bg-2);border:1px solid var(--line);color:var(--fg);font-size:20px;line-height:1;cursor:pointer;transition:background .12s,border-color .12s,transform 80ms}.master .favs-nav:hover{background:var(--bg-3);border-color:var(--accent)}.master .favs-nav:active{transform:scale(.94)}.master .favs-grid{flex:1;min-height:0;display:flex;flex-direction:column;flex-wrap:wrap;align-content:flex-start;overflow-x:auto;overflow-y:hidden;gap:8px;padding:2px 2px 8px;scroll-snap-type:x proximity;scrollbar-gutter:stable;cursor:grab;-webkit-user-select:none;user-select:none;scroll-behavior:smooth}.master .favs-grid.dragging{cursor:grabbing;scroll-behavior:auto}.master .favs-grid.dragging .fav-tile{pointer-events:none}.master .favs-grid::-webkit-scrollbar{height:10px}.master .fav-tile{display:grid;grid-template-rows:1fr auto;gap:6px;padding:6px;background:var(--bg-2);border:1px solid var(--line);color:var(--fg);cursor:pointer;text-align:left;transition:transform 80ms ease,border-color .12s,background .12s;height:calc((100% - 8px)/2);aspect-ratio:1 / 1;flex-shrink:0;scroll-snap-align:start;min-height:0}.master .fav-tile:hover{border-color:var(--accent);background:var(--bg-3)}.master .fav-tile:active{transform:scale(.97)}.master .fav-tile.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset,0 0 16px var(--accent-glow)}.master .fav-art{width:100%;min-height:0;background:var(--bg-1) center/cover no-repeat;position:relative;aspect-ratio:1 / 1;justify-self:center;max-height:100%}.master .fav-art.empty:after{content:"♪";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:var(--muted-2);font-size:28px}.master .fav-name{font-size:12px;line-height:1.2;color:var(--fg);overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.master .favs-empty{color:var(--muted);font-size:12px;padding:12px 4px;text-align:center;width:100%}.master .fav-tabs{display:flex;gap:4px;padding:6px 12px 0;overflow-x:auto;scrollbar-width:none}.master .fav-tabs::-webkit-scrollbar{display:none}.master .fav-tab{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--bg-2);color:var(--muted);border:1px solid var(--line);border-bottom:none;font-size:12px;cursor:pointer;white-space:nowrap;height:30px}.master .fav-tab:hover{color:var(--fg);background:var(--bg-3)}.master .fav-tab.active{background:var(--bg-1);color:var(--fg);border-color:var(--accent);border-bottom-color:var(--bg-1);margin-bottom:-1px}.master .fav-tab.main .fav-tab-glyph{color:var(--accent-2)}.master .fav-tab.self{font-weight:700}.master .fav-tab-glyph{font-size:13px;line-height:1}.master .fav-count{color:var(--muted-2);font-weight:400;font-size:12px}.master .fav-readonly{color:var(--muted-2);font-weight:400;font-size:11px;font-style:italic}.master .topbar .user-pill{display:inline-flex;align-items:center;gap:6px;padding:4px 10px 4px 4px;cursor:pointer}.master .topbar .user-pill .avatar{display:inline-grid;place-items:center;width:24px;height:24px;background:var(--accent);color:#1a0a00;font-weight:800;font-size:12px;line-height:1;border-radius:0}.master .topbar .user-pill .caret{color:var(--muted-2);font-size:10px}.master .topbar .user-pill.active{border-color:var(--accent);color:var(--fg)}.master .topbar .follow-pill{cursor:pointer}.master .topbar .follow-pill.active{border-color:var(--accent);color:var(--accent-2)}.avatar-popover-wrap{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;display:grid;place-items:center;z-index:100}.avatar-popover{min-width:340px;max-width:480px;background:var(--bg-1);border:1px solid var(--line);box-shadow:0 24px 60px #0009}.avatar-popover-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--line)}.avatar-popover-head h3{margin:0;font-size:14px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.avatar-popover .close{background:transparent;border:none;color:var(--muted);font-size:20px;cursor:pointer;line-height:1}.avatar-list{display:grid;gap:4px;padding:8px;max-height:60vh;overflow-y:auto}.avatar-row{display:flex;align-items:center;gap:12px;padding:10px 12px;background:var(--bg-2);border:1px solid var(--line);color:var(--fg);cursor:pointer;text-align:left;font-size:14px}.avatar-row:hover{border-color:var(--accent)}.avatar-row.active{background:var(--bg-3);cursor:default}.avatar.lg{display:inline-grid;place-items:center;width:36px;height:36px;background:var(--accent);color:#1a0a00;font-weight:800;font-size:16px;line-height:1}.avatar-name{flex:1}.avatar-tag{color:var(--accent-2);font-size:11px;font-weight:600;letter-spacing:.04em}.avatar-tag.dim{color:var(--muted-2);font-weight:400}.avatar-hint{padding:8px 16px 14px;color:var(--muted-2);font-size:11px;text-align:center}.play-gate-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000b8;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:9999}.play-gate-card{background:#161a22;border:1px solid #262b36;border-radius:20px;padding:28px 32px;min-width:320px;max-width:480px;box-shadow:0 18px 40px #0000008c;text-align:center;color:#e9ecf2}.play-gate-title{margin:0 0 8px;font-size:20px}.play-gate-station{color:#ff7a3d;font-weight:600;margin-bottom:4px}.play-gate-sub{color:#8a90a0;font-size:13px;margin-bottom:16px}.play-gate-row{display:flex;gap:12px;justify-content:center;margin-top:18px}.play-gate-start,.play-gate-cancel{padding:10px 22px;border-radius:14px;border:1px solid #262b36;background:#1f242e;color:#e9ecf2;font-size:15px;cursor:pointer}.play-gate-start{background:#ff7a3d;border-color:#ff7a3d;color:#07080b;font-weight:600}.play-gate-start:hover{filter:brightness(1.1)}.play-gate-cancel:hover{background:#0e1116}.play-gate-dismiss{display:flex;gap:8px;align-items:center;justify-content:center;margin-top:14px;color:#8a90a0;font-size:12px;cursor:pointer}.zones-panel{margin-top:12px;padding:10px 12px;background:#0e1116;border:1px solid #262b36;border-radius:12px;display:flex;flex-direction:column;gap:6px}.zones-label{font-size:11px;color:#8a90a0;text-transform:uppercase;letter-spacing:.1em}.zone-row{display:grid;grid-template-columns:120px 1fr 44px;align-items:center;gap:10px}.zone-name{font-size:13px;color:#e9ecf2;overflow:hidden;text-overflow:ellipsis}.zone-row input[type=range]{width:100%}.zone-val{font-size:12px;color:#8a90a0;text-align:right;font-variant-numeric:tabular-nums}