Files
djAudiovisualizaerPi/public/css/style.css
T
lucianoandClaude Opus 4.8 b94c787231 DJ Visualizer Pi: standalone Node+WebSocket server with mobile PWA remote
New separate project (the Electron Mac app stays untouched). Reuses the visual
engine (WebGL shaders, effect catalog, Web Audio) verbatim and replaces Electron
IPC with a Node HTTP+WebSocket server:

- server.js: serves the kiosk output page + mobile control PWA, relays the same
  ctl/rpt JSON protocol over WebSocket, plus REST for media library/upload,
  JSON state (pads), waveform peaks and MP4 recording (ffmpeg).
- public/js/net.js: window.djv shim over WebSocket+HTTP so output.js runs intact.
- public/index.html + control.js: touch-first mobile remote (effects, media/queue,
  ticker, pads, audio/EQ, fullscreen + recording).
- PWA (manifest + service worker + generated icons).
- scripts/: Chromium kiosk launcher + install-pi.sh (systemd units for Pi 5).

Verified: REST endpoints, WebSocket relay both directions, control PWA loads the
2958-effect catalog and renders. (Headless WebGL output not testable without GPU.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 13:06:15 +02:00

799 lines
22 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
color: #e8e8f0;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", Helvetica, Arial, sans-serif;
cursor: default;
}
body.hide-cursor { cursor: none; }
#gl {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
display: block;
}
/* ============ CONTROL WINDOW LAYOUT ============ */
body.control {
display: flex;
flex-direction: column;
height: 100vh;
overflow-x: hidden; /* never a horizontal scrollbar */
overflow-y: auto;
background: #0e0e16;
cursor: default;
}
#topbar {
flex: 0 0 auto;
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid rgba(255,255,255,0.08);
background: rgba(20,20,30,0.6);
}
#topbar strong { font-size: 15px; letter-spacing: 0.3px; }
#topbar #status { font-size: 11px; opacity: 0.6; font-variant-numeric: tabular-nums; }
/* Tab bar */
#tabs {
flex: 0 0 auto;
display: flex;
gap: 4px;
padding: 8px 10px 0;
border-bottom: 1px solid rgba(255,255,255,0.08);
background: rgba(20,20,30,0.45);
overflow-x: auto;
}
#tabs button {
flex: 0 0 auto;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
border-radius: 8px 8px 0 0;
padding: 8px 12px;
opacity: 0.6;
font-size: 13px;
}
#tabs button:hover { background: rgba(255,255,255,0.06); opacity: 0.9; }
#tabs button.active {
opacity: 1;
background: rgba(110,150,255,0.14);
border-bottom-color: #7aa2ff;
}
#content { flex: 1 1 auto; overflow-y: auto; min-width: 0; }
.tab-panel { display: none; }
.tab-panel.active {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
padding: 14px;
align-content: start;
}
.tab-panel section {
min-width: 0; /* allow grid cells to shrink, no overflow */
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 14px;
padding: 12px;
}
.tab-panel section.span2 { grid-column: 1 / -1; }
@media (max-width: 460px) {
.tab-panel.active { grid-template-columns: 1fr; }
}
body.control label {
display: block;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.6px;
opacity: 0.6;
margin: 8px 0 6px;
}
body.control label:first-child { margin-top: 0; }
body.control label span { float: right; opacity: 0.9; text-transform: none; letter-spacing: 0; }
body.control .chk { text-transform: none; letter-spacing: 0; opacity: 0.85; margin: 0; }
#hints {
flex: 0 0 auto;
font-size: 10.5px;
opacity: 0.5;
line-height: 1.7;
padding: 10px 16px;
border-top: 1px solid rgba(255,255,255,0.08);
}
body.control select { width: 100%; max-width: 100%; }
/* Per-band equaliser labels + sliders */
.band { display: block; }
#bass-gain { accent-color: #5ad1ff; }
#mid-gain { accent-color: #7aa2ff; }
#treble-gain { accent-color: #ff7ad1; }
.band-bass span, .band-mid span, .band-treble span { color: #fff; }
/* ---- Effect library ---- */
.effect-list {
margin-top: 8px;
max-height: 220px;
overflow-y: auto;
border: 1px solid rgba(255,255,255,0.07);
border-radius: 10px;
padding: 4px;
}
.fx {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 8px;
border-radius: 7px;
font-size: 12px;
cursor: pointer;
}
.fx:hover { background: rgba(255,255,255,0.07); }
.fx.active { background: rgba(110,150,255,0.25); }
.fx .fx-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx .fx-add {
flex: 0 0 auto;
padding: 2px 8px;
border-radius: 6px;
font-size: 12px;
}
#effect-family { flex: 0 0 45%; }
#effect-search { flex: 1; margin: 0; }
/* ---- Pad bank (5×5 launchpad) ---- */
.pad-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
margin-top: 8px;
}
.pad {
position: relative;
aspect-ratio: 1 / 1;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 10px;
background: rgba(255,255,255,0.04);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
cursor: pointer;
overflow: hidden;
padding: 4px;
transition: background 0.12s ease, box-shadow 0.12s ease;
}
.pad:hover { background: rgba(255,255,255,0.09); }
.pad.filled { background: rgba(90,120,200,0.18); border-color: rgba(120,150,255,0.4); }
.pad.active {
background: rgba(60,200,120,0.35);
border-color: #5ad18a;
box-shadow: 0 0 12px rgba(90,230,150,0.55);
}
.pad-num { position: absolute; top: 3px; left: 5px; font-size: 9px; opacity: 0.5; }
.pad-name {
font-size: 9.5px;
line-height: 1.15;
word-break: break-word;
max-height: 100%;
overflow: hidden;
opacity: 0.92;
}
.pad-plus { font-size: 22px; opacity: 0.3; }
.pad-x {
position: absolute;
top: 1px; right: 2px;
background: transparent;
border: none;
color: #ff8a8a;
font-size: 11px;
padding: 0 3px;
opacity: 0;
flex: none;
width: auto;
}
.pad:hover .pad-x { opacity: 0.9; }
.pad-key {
position: absolute;
bottom: 2px;
right: 3px;
background: rgba(255,255,255,0.12);
border: none;
color: #cdd6ff;
font-size: 9px;
font-weight: 700;
line-height: 1;
padding: 2px 4px;
border-radius: 4px;
min-width: 14px;
width: auto;
flex: none;
opacity: 0.85;
}
.pad-key:hover { background: rgba(120,150,255,0.45); }
.pad-time {
position: absolute;
bottom: 3px;
left: 4px;
font-size: 8.5px;
opacity: 0.7;
font-variant-numeric: tabular-nums;
}
.pad-prog {
position: absolute;
left: 0; bottom: 0;
height: 3px;
background: linear-gradient(90deg, #5ad18a, #aef0c0);
width: 0;
}
/* Larger ticker text area */
textarea.textarea-lg {
min-height: 78px;
resize: vertical;
font-size: 14px;
line-height: 1.4;
}
/* ---- Recording status ---- */
.rec-status {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
font-size: 13px;
}
#rec-dot { width: 12px; height: 12px; border-radius: 50%; flex: 0 0 auto; }
#rec-dot.off { background: #555; }
#rec-dot.on { background: #ff3b30; box-shadow: 0 0 10px #ff3b30; animation: recpulse 1s infinite; }
#rec-time { margin-left: auto; font-variant-numeric: tabular-nums; opacity: 0.85; }
@keyframes recpulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
/* ---- SVG quick-pick chips ---- */
.svg-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.svg-chip {
display: flex;
align-items: center;
gap: 6px;
flex: 0 0 auto;
padding: 4px 8px;
}
.svg-chip img {
width: 26px; height: 26px;
object-fit: contain;
background: #9aa3b2;
border-radius: 5px;
padding: 2px;
}
.svg-chip span {
font-size: 11px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- Playlist ---- */
.playlist { list-style: none; margin: 8px 0 0; padding: 0; }
.hint-mini { font-size: 10px; opacity: 0.45; margin-top: 6px; }
.track {
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0;
margin-bottom: 6px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 9px;
font-size: 12px;
overflow: hidden;
}
.track .track-head {
position: relative;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
}
.track .ttime {
flex: 0 0 auto;
font-size: 10px;
opacity: 0.65;
font-variant-numeric: tabular-nums;
min-width: 34px;
text-align: right;
}
.track .tprog {
position: absolute;
left: 0; bottom: 0;
height: 2px;
background: linear-gradient(90deg, #5ad1ff, #7aa2ff, #ff7ad1);
width: 0;
}
.track .track-body { padding: 0 8px 8px; }
.track .track-times {
display: flex; gap: 10px; align-items: center;
font-size: 10px; font-variant-numeric: tabular-nums; margin-bottom: 5px;
}
.track .track-times .tt-el { color: #9be6a0; }
.track .track-times .tt-tot { opacity: 0.7; }
.track .track-times .tt-rem { color: #ffb3b3; margin-left: auto; }
.track .wave {
display: block; width: 100%; height: 46px;
background: rgba(0,0,0,0.28); border-radius: 6px; cursor: ew-resize;
touch-action: none;
}
.track .track-trim {
display: flex; align-items: center; gap: 5px; margin-top: 6px; font-size: 10px; flex-wrap: wrap;
}
.track .track-trim .trim-lbl { opacity: 0.6; }
.track .track-trim .trim-lbl-s { color: #9be6a0; opacity: 0.9; }
.track .track-trim .trim-lbl-e { color: #ffb3b3; opacity: 0.9; }
.track .track-trim input { width: 52px; text-align: center; padding: 4px 3px; font-variant-numeric: tabular-nums; }
.track .track-trim button { padding: 3px 6px; }
.track .track-trim .trim-reset { margin-left: auto; }
.track.playing {
background: rgba(110,150,255,0.22);
border-color: rgba(120,170,255,0.6);
}
.track.dragging { opacity: 0.4; }
.track .grip { cursor: grab; opacity: 0.4; font-size: 14px; }
.track .tname {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.track .tname:hover { color: #cde0ff; }
.track button {
flex: 0 0 auto;
padding: 3px 8px;
border-radius: 7px;
font-size: 11px;
}
.track .key-btn {
min-width: 30px;
font-weight: 700;
background: rgba(255,255,255,0.1);
}
.track .play-btn { color: #8ef0b0; }
.track .del-btn { color: #ff8a8a; }
.track .scene-btn { filter: grayscale(1) opacity(0.5); }
.track .scene-btn.active { filter: none; }
/* Per-track scene editor */
.scene-editor {
list-style: none;
margin: -2px 0 9px;
padding: 12px;
background: rgba(110,150,255,0.10);
border: 1px solid rgba(120,150,255,0.38);
border-radius: 12px;
}
.scene-editor .se-title { font-size: 12px; font-weight: 600; opacity: 0.92; margin-bottom: 10px; }
.scene-editor .se-title small { font-weight: 400; opacity: 0.55; }
.scene-editor .se-empty { font-size: 11px; opacity: 0.5; padding: 6px 2px 10px; }
.scene-editor .cue {
background: rgba(0,0,0,0.22);
border: 1px solid rgba(255,255,255,0.08);
border-left: 3px solid rgba(255,255,255,0.18);
border-radius: 10px;
padding: 9px;
margin-bottom: 9px;
}
.scene-editor .cue-effect { border-left-color: #8fb6ff; }
.scene-editor .cue-text { border-left-color: #ffd27a; }
.scene-editor .cue-image { border-left-color: #9be6a0; }
.scene-editor .cue-head { display: flex; align-items: center; gap: 6px; }
.scene-editor .cue-ico { flex: 0 0 auto; }
.scene-editor .cue-type { flex: 0 0 auto; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; opacity: 0.85; }
.scene-editor .cue-at { opacity: 0.5; flex: 0 0 auto; margin-left: 4px; }
.scene-editor .cue-time { width: 58px; flex: 0 0 auto; text-align: center; padding: 6px 4px; font-variant-numeric: tabular-nums; }
.scene-editor .cue-durl { flex: 0 0 auto; opacity: 0.5; font-size: 11px; }
.scene-editor .cue-dur { width: 46px; flex: 0 0 auto; text-align: center; padding: 6px 4px; }
.scene-editor .cue-del { flex: 0 0 auto; color: #ff9a9a; margin-left: auto; }
.scene-editor .cue-body { margin-top: 8px; }
.scene-editor .cue-eff-fam, .scene-editor .cue-eff-pre { flex: 1; min-width: 0; }
.scene-editor .cue-eff-cur { flex: 0 0 auto; }
.scene-editor .cue-eff-name { flex: 1; min-width: 0; font-size: 10px; opacity: 0.72; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scene-editor .cue-text { width: 100%; margin: 0 0 7px; }
.scene-editor .cue-row { display: flex; align-items: center; gap: 6px; }
.scene-editor .cue-row + .cue-row { margin-top: 6px; }
.scene-editor .cue-tx-dir, .scene-editor .cue-tx-fx,
.scene-editor .cue-tx-font, .scene-editor .cue-tx-pos { flex: 1; min-width: 0; }
.scene-editor .cue-tx-size, .scene-editor .cue-tx-speed, .scene-editor .cue-img-size { flex: 1; }
.scene-editor .cue-tx-color { flex: 0 0 auto; width: 34px; padding: 0; }
.scene-editor .cue-tx-bold { width: auto; }
.scene-editor .cue-img, .scene-editor .cue-img-clear { flex: 0 0 auto; }
.scene-editor .cue-name { flex: 1; min-width: 0; font-size: 10px; opacity: 0.72; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scene-editor .cue-img-pos { flex: 0 0 auto; width: 92px; }
.scene-editor .cue-dim { flex: 0 0 auto; font-size: 10px; opacity: 0.55; }
.scene-editor .se-foot { display: flex; gap: 6px; margin-top: 2px; flex-wrap: wrap; }
.scene-editor .se-foot .se-clear { color: #ff9a9a; margin-left: auto; }
.scene-editor .se-gap { display: flex; align-items: center; gap: 6px; margin-top: 9px; font-size: 12px; opacity: 0.9; }
.scene-editor .se-gap input { width: 56px; text-align: center; padding: 6px 4px; }
.scene-editor .se-gap small { opacity: 0.5; font-size: 10px; }
.playlist .gap-badge { margin-left: 6px; font-size: 9.5px; padding: 1px 5px; border-radius: 6px; background: rgba(255,200,120,0.18); color: #ffd27a; vertical-align: middle; }
.playlist .tname.is-interlude { color: #c9b6ff; font-style: italic; }
/* ============ OUTPUT WINDOW ============ */
body.output { cursor: none; background: #000; }
/* ---- Image overlay layer ---- */
#overlay-images {
position: fixed;
inset: 0;
z-index: 4;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
--beat-scale: 1;
}
#overlay-images img {
position: absolute;
max-width: calc(var(--img-size, 70) * 1vw);
max-height: calc(var(--img-size, 70) * 1vh);
object-fit: contain;
mix-blend-mode: screen;
opacity: 0;
transform: scale(var(--beat-scale));
transition: opacity 0.8s ease;
filter: drop-shadow(0 0 30px rgba(0,0,0,0.6));
}
#overlay-images img.show { opacity: 1; }
/* ---- Video layer (VJ loop) ---- */
.bg-video {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
z-index: 1; /* above the WebGL canvas, below overlays */
pointer-events: none;
display: none;
}
.bg-video.show { display: block; }
.track-video {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
object-fit: contain;
background: #000;
z-index: 2; /* main visual when a video track plays */
pointer-events: none;
display: none;
}
.track-video.show { display: block; }
/* ---- Per-track scene image ---- */
.scene-image {
position: fixed;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
max-width: 62vw;
max-height: 62vh;
object-fit: contain;
z-index: 4;
pointer-events: none;
display: none;
filter: drop-shadow(0 0 24px rgba(0,0,0,0.5));
}
.scene-image.show { display: block; }
/* ---- Independent logos ---- */
.logo {
position: fixed;
left: 50%;
top: 50%;
width: 20vw;
transform: translate(-50%, -50%);
z-index: 5;
pointer-events: none;
display: none;
filter: drop-shadow(0 0 18px rgba(0,0,0,0.45));
}
.logo.show { display: block; }
/* ---- Scrolling text ticker ---- */
#ticker {
position: fixed;
left: 0;
right: 0;
z-index: 6;
pointer-events: none;
overflow: hidden;
display: none;
padding: 14px 0;
background: linear-gradient(transparent, rgba(0,0,0,0.35), transparent);
}
#ticker.show { display: block; }
#ticker.pos-bottom { bottom: 4vh; }
#ticker.pos-top { top: 4vh; }
#ticker.pos-middle { top: 50%; transform: translateY(-50%); }
#ticker-track {
display: inline-flex;
white-space: nowrap;
will-change: transform;
font-size: 6vh;
font-weight: 800;
color: #fff;
text-transform: uppercase;
}
.tick-copy { display: inline-flex; padding: 0 2vw; }
/* Horizontal scroll (default) */
#ticker.dir-h #ticker-track {
animation: ticker-scroll var(--ticker-dur, 18s) linear infinite;
}
/* Vertical scroll: full-height column, the two copies stacked and scrolling */
#ticker.dir-vup, #ticker.dir-vdown {
top: 0 !important;
bottom: 0 !important;
height: 100vh;
padding: 0;
background: none;
transform: none !important;
}
#ticker.dir-vup.show, #ticker.dir-vdown.show {
display: flex;
align-items: flex-start;
justify-content: center;
}
#ticker.dir-vup #ticker-track, #ticker.dir-vdown #ticker-track {
flex-direction: column;
align-items: center;
white-space: nowrap;
}
#ticker.dir-vup .tick-copy, #ticker.dir-vdown .tick-copy { padding: 32vh 0; }
#ticker.dir-vup #ticker-track { animation: ticker-scroll-vup var(--ticker-dur, 18s) linear infinite; }
#ticker.dir-vdown #ticker-track { animation: ticker-scroll-vdown var(--ticker-dur, 18s) linear infinite; }
@keyframes ticker-scroll-vup { from { transform: translateY(0); } to { transform: translateY(-50%); } }
@keyframes ticker-scroll-vdown { from { transform: translateY(-50%); } to { transform: translateY(0); } }
/* Side text: vertical stacked characters scrolling on both edges */
#side-text {
position: fixed;
inset: 0;
z-index: 6;
pointer-events: none;
display: none;
font-size: 6vh;
font-weight: 800;
color: #fff;
text-transform: uppercase;
}
#side-text.show { display: block; }
.side-col { position: absolute; top: 0; bottom: 0; overflow: hidden; display: flex; }
.side-col.left { left: 1.5vw; }
.side-col.right { right: 1.5vw; }
.side-track { display: flex; flex-direction: column; align-items: center; will-change: transform; }
.side-col.left .side-track { animation: side-scroll-down var(--ticker-dur, 18s) linear infinite; }
.side-col.right .side-track { animation: side-scroll-up var(--ticker-dur, 18s) linear infinite; }
.side-copy { display: flex; flex-direction: column; align-items: center; padding: 18vh 0; }
.side-copy .tl {
display: block;
line-height: 1.0;
letter-spacing: 0;
text-shadow: 0 0 calc(8px + var(--ticker-glow, 0px)) rgba(140,180,255,0.9),
0 2px 20px rgba(0,0,0,0.8);
}
@keyframes side-scroll-up { from { transform: translateY(0); } to { transform: translateY(-50%); } }
@keyframes side-scroll-down { from { transform: translateY(-50%); } to { transform: translateY(0); } }
.tl {
display: inline-block;
white-space: pre;
letter-spacing: 2px;
text-shadow: 0 0 calc(8px + var(--ticker-glow, 0px)) rgba(140,180,255,0.9),
0 2px 30px rgba(0,0,0,0.8);
}
@keyframes ticker-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* Text movement effects (applied per letter) */
.fx-updown .tl { animation: tl-updown 1.1s ease-in-out infinite; }
.fx-wave .tl { animation: tl-wave 1.2s ease-in-out infinite; animation-delay: calc(var(--i) * 0.05s); }
.fx-zoom .tl { animation: tl-zoom 0.9s ease-in-out infinite; animation-delay: calc(var(--i) * 0.04s); }
.fx-flash .tl { animation: tl-flash 0.6s steps(1, end) infinite; }
.fx-rotate .tl { animation: tl-rotate 2s ease-in-out infinite; animation-delay: calc(var(--i) * 0.03s); }
@keyframes tl-updown { 0%, 100% { transform: translateY(-0.18em); } 50% { transform: translateY(0.18em); } }
@keyframes tl-wave { 0%, 100% { transform: translateY(-0.24em); } 50% { transform: translateY(0.24em); } }
@keyframes tl-zoom { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.35); } }
@keyframes tl-flash { 0%, 100% { opacity: 1; } 50% { opacity: 0.12; } }
@keyframes tl-rotate { 0%, 100% { transform: rotate(-12deg); } 50% { transform: rotate(12deg); } }
/* ---- Drop hint ---- */
#drop-hint {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
pointer-events: none;
text-shadow: 0 2px 20px rgba(0,0,0,0.8);
transition: opacity 0.4s ease;
}
#drop-hint .big { font-size: 34px; font-weight: 600; }
#drop-hint .small { font-size: 15px; opacity: 0.7; }
#drop-hint.hidden { opacity: 0; }
body.dragover #drop-hint { opacity: 1; }
body.dragover::after {
content: "";
position: fixed;
inset: 18px;
border: 3px dashed rgba(120,170,255,0.8);
border-radius: 18px;
pointer-events: none;
}
/* ---- Control panel ---- */
#panel {
position: fixed;
top: 16px;
left: 16px;
width: 320px;
max-height: calc(100vh - 32px);
overflow-y: auto;
background: rgba(14, 14, 22, 0.82);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 14px 16px;
font-size: 13px;
z-index: 10;
transition: opacity 0.25s ease, transform 0.25s ease;
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
}
#panel.hidden { opacity: 0; transform: translateX(-12px); pointer-events: none; }
#panel header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 10px;
}
#panel header strong { font-size: 15px; letter-spacing: 0.3px; }
#fps { font-size: 11px; opacity: 0.55; font-variant-numeric: tabular-nums; }
#panel section { margin-bottom: 14px; }
#panel label {
display: block;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.6px;
opacity: 0.6;
margin-bottom: 6px;
}
#panel label span { float: right; opacity: 0.9; text-transform: none; letter-spacing: 0; }
.row { display: flex; gap: 6px; margin-bottom: 6px; }
.scene-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 6px;
}
.scene-grid button { font-size: 12px; padding: 7px 6px; text-align: left; }
button, select {
font: inherit;
color: #e8e8f0;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 9px;
padding: 7px 10px;
cursor: pointer;
flex: 1;
transition: background 0.15s ease, border-color 0.15s ease;
white-space: nowrap;
}
button:hover, select:hover { background: rgba(255,255,255,0.12); }
button:disabled { opacity: 0.4; cursor: default; }
button.active {
background: rgba(110,150,255,0.28);
border-color: rgba(120,170,255,0.7);
}
button.wide { width: 100%; }
select { appearance: none; }
.chk {
display: flex !important;
align-items: center;
gap: 6px;
flex: 1;
text-transform: none !important;
letter-spacing: 0 !important;
opacity: 0.85 !important;
font-size: 12px;
margin: 0 !important;
cursor: pointer;
}
.chk input { accent-color: #7aa2ff; cursor: pointer; }
.textfield {
width: 100%;
font: inherit;
color: #e8e8f0;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 9px;
padding: 8px 10px;
margin-bottom: 6px;
}
.textfield::placeholder { color: rgba(232,232,240,0.4); }
input[type=range] {
width: 100%;
accent-color: #7aa2ff;
}
/* ---- Meters ---- */
.meters { display: flex; flex-direction: column; gap: 6px; }
.meter { display: flex; align-items: center; gap: 8px; }
.meter span { width: 38px; font-size: 10px; opacity: 0.6; }
.meter .bar {
flex: 1;
height: 8px;
background: rgba(255,255,255,0.08);
border-radius: 6px;
overflow: hidden;
}
.meter .bar i {
display: block;
height: 100%;
width: 0%;
border-radius: 6px;
background: linear-gradient(90deg, #5ad1ff, #7aa2ff, #ff7ad1);
transition: width 0.05s linear;
}
#panel footer {
font-size: 10.5px;
opacity: 0.5;
line-height: 1.7;
border-top: 1px solid rgba(255,255,255,0.08);
padding-top: 10px;
}
kbd {
font-family: ui-monospace, Menlo, monospace;
font-size: 10px;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 4px;
padding: 1px 5px;
margin: 0 1px;
}