Files
djaudiovisualizer/src/style.css
T
lucianoandClaude Opus 5.5 e754656585 Mapping: mini-editor in the control panel + keyboard nudge
Editing corners with handles on the output is unmanageable on a large LED
wall (7x4 m): the hand has to work on the giant screen. The Mappatura tab
now has a scaled replica of the output (aspect from the output display or
the LED area) where zones are dragged by corner or whole, with live sync
to the output (one IPC per frame). Keyboard on the focused editor: arrows
nudge 1 output pixel, Shift 10 px, Tab cycles corners, Esc = whole zone;
keys never leak to the global hotkeys. Selection is shared with the zone
list and highlighted on the output handles; a status line shows pixel
coordinates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVMQ2JhsJ77pGKqcvvFHqc
2026-10-02 09:13:01 +02:00

1009 lines
30 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; }
/* Dark native controls: on Windows the select popup/scrollbars render WHITE
without this, with our light text on top -> unreadable dropdowns. */
:root { color-scheme: dark; }
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; }
/* LED-wall active area: the whole show renders inside this box, black
outside. The transform makes it the containing block for the
position:fixed layers, so resizing it confines everything at once. */
#stage {
position: fixed;
inset: 0;
overflow: hidden;
background: #000;
/* NOTE: no transform here — it would isolate the compositing group and
break mix-blend-mode of the overlay images against the canvas. Children
use position:absolute so the box still confines them (LED area). */
}
#gl {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
/* Projection-mapping layer: sits over the visual, black outside the zones */
#map-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: none;
pointer-events: none;
}
#map-canvas.show { display: block; }
#map-canvas.edit { pointer-events: auto; cursor: crosshair; }
/* ============ 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.drop-above { box-shadow: inset 0 3px 0 0 rgba(120,170,255,0.9); }
.track.drop-below { box-shadow: inset 0 -3px 0 0 rgba(120,170,255,0.9); }
/* Scene-editor element reordering */
.cue-grip { cursor: grab; opacity: 0.45; font-size: 13px; }
.cue.dragging { opacity: 0.4; }
.cue.drop-above { box-shadow: inset 0 3px 0 0 rgba(120,170,255,0.9); }
.cue.drop-below { box-shadow: inset 0 -3px 0 0 rgba(120,170,255,0.9); }
.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 ============ */
/* LED area controls (Schermo tab) */
.led-row { align-items: center; gap: 4px; }
.led-num { width: 64px; }
.led-chk { display: flex; align-items: center; gap: 4px; margin-right: 6px; white-space: nowrap; }
body.output { cursor: none; background: #000; }
/* ---- Image overlay layer ----
NOTE: the output layers (videos, images, logos, ticker…) carry NO z-index:
they stack by DOM order in output.html. A z-index would turn each layer
into a stacking context, which isolates it for compositing and silently
disables the overlay images' mix-blend-mode against the canvas below. */
#overlay-images {
position: absolute;
inset: 0;
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: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
pointer-events: none;
display: none;
}
.bg-video.show { display: block; }
.track-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
pointer-events: none;
display: none;
}
.track-video.show { display: block; }
/* ---- Per-track scene image ---- */
.scene-image {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
max-width: 62vw;
max-height: 62vh;
object-fit: contain;
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: absolute;
left: 50%;
top: 50%;
width: 20vw;
transform: translate(-50%, -50%);
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: absolute;
left: 0;
right: 0;
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: absolute;
inset: 0;
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: absolute;
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; background-color: #1a1a26; }
select option { background: #1a1a26; color: #e8e8f0; }
.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;
}
/* Auto VJ toggle */
#auto-vj.vj-on { background: #2e7d32; border-color: #4caf50; color: #fff; }
.av-fams { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
.av-fams.hidden { display: none; }
.av-fam { font-size: 11px; padding: 4px 8px; border: 1px solid #444; border-radius: 8px; cursor: pointer; opacity: .75; }
.av-fam.on { border-color: #4caf50; background: rgba(76,175,80,.15); opacity: 1; }
.av-fam input { display: none; }
/* Dance repertoire: a proper aligned two-column checkbox list (the Auto VJ
pill style hides the checkbox and wraps raggedly with long clip names) */
#glb-anims { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }
#glb-anims .av-fam {
display: flex;
align-items: center;
gap: 7px;
padding: 5px 8px;
font-size: 11.5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#glb-anims .av-fam input {
display: inline-block;
width: 13px;
height: 13px;
margin: 0;
flex: 0 0 auto;
accent-color: #4caf50;
}
#glb-anims .hint-mini { grid-column: 1 / -1; }
/* Model library: remembered GLBs for one-click switching */
.glb-models { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }
.glb-model {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 8px;
border: 1px solid #444;
border-radius: 8px;
cursor: pointer;
font-size: 11.5px;
opacity: 0.8;
}
.glb-model.on { border-color: #7aa2ff; background: rgba(110,150,255,0.14); opacity: 1; }
.glb-model .gm-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.glb-model .gm-del {
flex: 0 0 auto;
width: auto;
background: transparent;
border: none;
color: #ff8a8a;
padding: 0 2px;
font-size: 11px;
opacity: 0.7;
}
.glb-models .hint-mini { grid-column: 1 / -1; }
/* Projection-mapping guide */
.map-guide { margin-top: 10px; font-size: 11.5px; }
.map-guide summary {
cursor: pointer;
opacity: 0.8;
padding: 6px 8px;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 9px;
}
.map-guide summary:hover { background: rgba(255,255,255,0.07); }
.map-guide[open] summary { border-radius: 9px 9px 0 0; background: rgba(255,255,255,0.05); }
.map-guide .mg-body {
border: 1px solid rgba(255,255,255,0.1);
border-top: none;
border-radius: 0 0 9px 9px;
padding: 4px 12px;
line-height: 1.55;
opacity: 0.85;
}
.map-guide .mg-body p { margin: 7px 0; }
/* Projection-mapping zone list */
.map-zone {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
margin: 4px 0;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 9px;
font-size: 11.5px;
}
.map-zone .mz-ico { flex: 0 0 auto; }
.map-zone .mz-name { width: 90px; flex: 0 0 auto; }
.map-zone .mz-src { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: 0.75; }
.map-zone .mz-rect { flex: 1; display: flex; align-items: center; gap: 2px; white-space: nowrap; opacity: 0.9; }
.map-zone .mz-fx { flex: 1; display: flex; gap: 4px; min-width: 0; }
.map-zone .mz-fx select { flex: 1; min-width: 0; padding: 4px 6px; font-size: 11px; }
.map-zone .mz-r { width: 44px; flex: 0 0 auto; }
.map-zone .mz-op { flex: 0 0 70px; }
.map-zone .mz-del {
flex: 0 0 auto;
width: auto;
background: transparent;
border: none;
color: #ff8a8a;
padding: 0 3px;
}
/* Mapping mini-editor (control panel): scaled replica of the output */
.map-ed-wrap { margin: 8px 0 4px; display: flex; justify-content: center; }
#map-editor {
display: block; width: 100%; max-height: 440px;
background: #07070c; border: 1px solid rgba(255,255,255,0.14); border-radius: 8px;
cursor: crosshair; outline: none;
}
#map-editor:focus { border-color: #5f7fe8; box-shadow: 0 0 0 2px rgba(95,127,232,0.35); }
.map-ed-info { display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; font-size: 11px; margin-bottom: 6px; }
.map-ed-info #map-ed-status { color: #9fc0ff; }
.map-ed-info .map-ed-keys { opacity: 0.55; }
.map-zone.sel { border-color: #5f7fe8; background: rgba(95,127,232,0.10); }
/* Recording crop guide on the output (overlay only, never recorded) */
#rec-guide {
position: fixed;
display: none;
border: 2px dashed rgba(130, 185, 255, 0.85);
box-sizing: border-box;
/* dim everything outside the recorded area */
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
pointer-events: none;
}
#rec-guide.show { display: block; }
/* Multi-output rows (tab Schermo) */
.aux-row {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
margin: 4px 0;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 9px;
font-size: 11.5px;
}
.aux-row .aux-chk { flex: 1 1 120px; min-width: 0; display: flex; align-items: center; gap: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aux-row .aux-chk small { opacity: 0.6; }
/* the global control select is width:100%: pin flex sizes explicitly */
.aux-row .aux-mode { flex: 0 0 170px; width: 170px; padding: 4px 6px; font-size: 11px; }
.aux-row .aux-fx { flex: 1 1 220px; display: flex; gap: 4px; min-width: 0; }
.aux-row .aux-fx select { flex: 1; min-width: 0; padding: 4px 6px; font-size: 11px; }
.aux-row .aux-img { flex: 0 0 auto; width: auto; padding: 4px 8px; font-size: 11px; }
.aux-row .aux-img-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: 0.75; }
.aux-row { flex-wrap: wrap; }
.aux-row .aux-txt {
flex: 0 0 auto; width: auto; padding: 4px 8px; font-size: 12px;
background: transparent; border: 1px solid rgba(255,255,255,0.15); border-radius: 7px;
}
.aux-row .aux-txt.active { background: #2b3a6e; border-color: #5f7fe8; }
.aux-row .aux-text-row {
flex: 1 0 100%; display: flex; align-items: center; gap: 6px;
margin-top: 4px; padding-top: 6px; border-top: 1px dashed rgba(255,255,255,0.1);
}
.aux-row .atx-val { flex: 1 1 160px; min-width: 0; width: auto; padding: 4px 6px; font-size: 11px; }
.aux-row .atx-dir, .aux-row .atx-fx, .aux-row .atx-font, .aux-row .atx-pos {
flex: 1 1 0; min-width: 0; width: auto; padding: 4px 6px; font-size: 11px;
}
.aux-row .atx-lbl { flex: 0 0 auto; font-size: 10px; opacity: 0.6; }
.aux-row .atx-size, .aux-row .atx-speed { flex: 0 0 70px; }
.aux-row .atx-color { flex: 0 0 30px; width: 30px; height: 22px; padding: 1px; }
.aux-row .atx-b { flex: 0 0 auto; display: flex; align-items: center; gap: 3px; font-size: 11px; }