:root {
  --bg: #0b0e1f;
  --panel: #171d38;
  --panel-2: #202748;
  --tile: #05070f;
  --border: #2f3860;
  --text: #f3f5ff;
  --muted: #a9b2d6;
  --accent: #4f8cff;
  --accent-2: #9b6bff;
  --success: #24d59a;
  --danger: #ff4d63;
  --live: #ff3b5c;
  --grad: linear-gradient(135deg, #4f8cff 0%, #9b6bff 100%);
  --banner-color: #4f8cff;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* The two corner washes are variables so a LIGHT studio theme can fade them
     out — hardcoded purple/blue over a white --bg is what made "white" come
     out lavender. Fallbacks keep the original dark look untouched. */
  background:
    radial-gradient(900px 500px at 80% -10%, var(--wash-1, rgba(155,107,255,0.18)), transparent 60%),
    radial-gradient(900px 500px at 0% 0%, var(--wash-2, rgba(79,140,255,0.16)), transparent 55%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  min-height: 100vh;
}

header {
  display: flex;
  align-items: center;
  /* Not space-between: that dumped ALL the leftover width into the middle, so
     the two groups sat 90px apart with nothing between them. flex-start plus
     auto margins on both sides of .header-right splits the leftover evenly —
     half in the middle, half after Go live — which halves the middle gap
     (Marcelo) without a magic number that would be wrong in another language. */
  justify-content: flex-start;
  /* ~30% thinner (Marcelo). In a live session the right side carries the
     schedule, studio picker, ratio, quality and Go live, which used to squeeze
     the left group until it broke across four rows and the bar stood 129px
     tall. Everything below keeps it to ONE row: the wide empty middle is the
     space this borrows. */
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  gap: 6px 8px;
  /* Wrapping stays possible so "Go live" is never clipped — but it now wraps
     between the two GROUPS, not inside them, so the worst case is two short
     rows instead of the four-row pile this used to make. */
  flex-wrap: wrap;
}

.brand {
  font-weight: 600;
  font-size: 17px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.brand .dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--accent);
}

.brand-logo {
  /* Sized for legibility, not just for fitting. The mark carries a face,
     headphones and a turntable inside a ring — below ~36px those merge into a
     blob. No border-radius: the transparent mark has its own silhouette. */
  height: 38px;
  width: auto;
}
.brand-name { font-weight: 700; font-size: 15px; letter-spacing: 0.2px; }

.tag {
  font-size: 11px;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 2px 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* 🔄 ask-YouTube, beside Upcoming | Past. .ctrl.small carries `flex: 1` for
   button ROWS, which stretched this one across the whole head — and an inline
   width:auto cannot beat a flex-basis. Pin it to its glyph. */
#home-sync-btn { flex: 0 0 auto; width: auto; padding: 7px 12px; }
.header-right { display: flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 0;
  margin-left: auto; margin-right: auto; }
/* A third auto margin, on an empty flex item at the end. Auto margins split the
   leftover width EQUALLY between them, so three of them put a third in the
   middle and two thirds after Go live, instead of the half-and-half two would.
   That is the 50% cut Marcelo asked for on the middle gap, expressed as a ratio
   so it still holds in the languages where there is barely any leftover. */
header::after { content: ''; margin-left: auto; }
.header-left { display: flex; align-items: center; gap: 7px; flex: 0 1 auto; min-width: 0; }
/* The left group is a flex ROW, not a block. As a block its buttons were
   inline-blocks that wrapped one under another the moment the row ran out of
   width — the four-row header. nowrap makes them share the horizontal space
   instead, which is what all that emptiness in the middle is for. */
.lang-wrap { display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; }
/* Header controls only — .ctrl.small elsewhere (backstage cards, loop rows,
   preset rows) keeps its own size. */
header .ctrl.small { padding: 5px 8px; font-size: 11.5px; line-height: 1.25; }
header .brand { font-size: 14px; white-space: nowrap; }
header .brand .tag { font-size: 10.5px; padding: 3px 9px; }
/* The right-hand group — studio picker, ★Studio, ratio, quality, Go live.
   Scoped to the header so the same controls keep their normal size anywhere
   else they appear. */
.header-right .hdr-res { font-size: 11px; padding: 0 5px; height: 26px; }
.header-right .golive-top { font-size: 11.5px; padding: 6px 11px; }
/* ✏️ + "Scheduled" over the date, another 10% down (Marcelo). */
.header-right .sched-indicator { font-size: 10.5px; gap: 4px; }
.header-right .si-badge { font-size: 10.5px; padding: 2px 8px; }
.header-right .si-when { font-size: 10.5px; margin-top: 2px; }
.header-right .si-edit { padding: 4px; }
.header-right .si-edit svg { width: 17px; height: 17px; }
.header-right .studio-sel-wrap, .header-right .studio-sel-wrap select,
.header-right .studio-sel-wrap button { font-size: 12px; }

/* ── One line at a narrow window ──────────────────────────────────────────
   Measured at Marcelo's 993px window with a live session's full header:
   the two groups want 1206px and have 965px, so 213px has to come from
   somewhere or the bar wraps to two rows. Everything above is the free part
   (gaps and type). These two are the paid part, and they are the two most
   redundant things in the bar:

   - `.brand-name` — the word "StreamerJoy" sitting directly beside the StreamerJoy
     logo, which Marcelo redrew himself. 89px to say it twice.
   - `#trial-badge.plan-static` — "★ Studio", settled account status he cannot
     act on and already sees on Home. 91px. Only ever the static variant: a
     trial countdown keeps its place (see the plan badge in app.js).

   Both come back above 1150px, where there is room for them. Verified one line
   in all 12 languages at 993px — Português and Español are the tightest, with
   12px to spare.

   THE NAME NO LONGER HIDES ANYWHERE (Marcelo, 2026-08-11). He asked for it on
   Home, then again inside the studio: the brand is the point, and hiding it was
   only ever an optimisation. Re-measured with the studio's full session header
   (schedule chip, studio picker, 16:9, quality, Record): it wants 907px without
   the name and 1010px with it, on a paid account like his — the 1206px figure
   above included a trial badge and an Upgrade button that a paying Host never
   sees. So from ~1010px up the bar is still one line WITH the name, which
   covers his window and every laptop. Narrower than that it takes a second row,
   costing ~38px of stage height — the trade he chose knowingly.

   `#trial-badge.plan-static` keeps the original rule: it is the redundant one,
   and dropping it is what buys the name its room. */
@media (max-width: 1150px) {
  .header-right #trial-badge.plan-static { display: none; }
}

/* PHONES — the `header` above wraps between its two GROUPS, which is enough
   down to a tablet and not enough on a phone: every group INSIDE it is
   `flex-wrap: nowrap` (that nowrap is what keeps the bar one row on a laptop,
   so it stays there — this only relaxes it below 760px). The 🌐/📖/✂️/🛍/⚙
   strip stayed one unbreakable 301px block and ran 44px off a 375px screen,
   and `.header-right` did the same again the moment a session opened its
   ratio/quality/Go live group — 638px of content in a 375px window. Either one
   scrolls the WHOLE page sideways, studio included. Let the rows wrap here: the
   bar grows a line instead. Same move the `.layout-strip` rule further down
   makes for the same reason.

   The auto margins that split the leftover width on a laptop go to 0 — with
   wrapping on, a group alone on its row would sit centred under a left-aligned
   one. `min-width: 0` on `.brand` so a long localized "host" tag shrinks with
   its row rather than pushing it wide again, and the padding comes in 4px
   because at 320px (the narrowest phone anyone still ships) 🛍 Store wants all
   of it. Checked at 320, 375, 390 and 430px, both with the signed-in header
   and with every live-session group revealed — no sideways scroll at any. */
@media (max-width: 760px) {
  header { padding: 8px 8px; }
  .header-left, .header-right, .lang-wrap { flex-wrap: wrap; }
  .header-left .brand { min-width: 0; }
  .header-right { margin-left: 0; margin-right: 0; }
  header::after { margin-left: 0; }
}
#stream-key.needs-key { border: 2px solid var(--accent); box-shadow: 0 0 0 3px rgba(79,140,255,0.3); }
.primary.gated { opacity: 0.5; cursor: not-allowed; }
/* Hover button to remove a tile from the panel (StreamYard-style) */
/* Low on the tile, not across the middle of the face (Marcelo). Anchored to
   the bottom but never below ~34px, so it still clears the name tag in its
   corner on a small tile; --tsb-i stacks a second button above the first
   instead of on top of it. */
/* 70% smaller than it was (Marcelo, twice: half, then smaller still). At full
   size it covered the face of a portrait tile and sat on top of the 🔺 flip.
   Padding and type both come down, so it is the AREA that drops by 70%, not
   just the height — 169x36 at HEAD, ~92x16 now. The anchor floors scale with
   it, or a short tile would push the pill off its own bottom edge. */
.tile-stage-btn {
  position: absolute; left: 50%; top: auto;
  bottom: min(calc(max(18px, 12%) + var(--tsb-i, 0) * 21px), calc(100% - 19px));
  transform: translateX(-50%);
  background: var(--accent); color: #fff; border: none; border-radius: 999px;
  padding: 2px 7px; font: inherit; font-weight: 600; font-size: 9px; cursor: pointer;
  opacity: 0; transition: opacity .12s; white-space: nowrap; z-index: 6;
}
.tile:hover .tile-stage-btn { opacity: 1; }
.tile-stage-btn:hover { filter: brightness(1.08); }
.lang-wrap { position: relative; }
/* 🌐 in the header. It wears .ctrl.small for the shape, but that class carries
   `flex: 1` for button rows — on a <select> in the header that stretches it
   across the whole wrap, so pin it to its content width here. */
#lang-sel {
  flex: 0 1 auto; width: auto; max-width: 104px; min-width: 0;
  padding: 4px 6px; font-size: 11.5px;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; cursor: pointer;
}
.lang-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 300;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 6px; display: flex; flex-direction: column; min-width: 150px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.4);
}
.lang-menu button {
  background: transparent; border: none; color: var(--text); text-align: left;
  padding: 9px 12px; border-radius: 7px; font: inherit; font-size: 14px; cursor: pointer;
}
.lang-menu button:hover { background: var(--tile); }
[dir="rtl"] .lang-menu { left: auto; right: 0; }
[dir="rtl"] .lang-menu button { text-align: right; }
.top-schedule {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-radius: 999px; cursor: pointer;
  background: var(--grad); color: #fff; border: none; font: inherit; font-weight: 600;
  box-shadow: 0 4px 14px rgba(79,140,255,0.35);
}
.top-schedule:hover { filter: brightness(1.08); }
.trial-badge {
  font-size: 13px;
  font-weight: 500;
  color: var(--accent);
  background: var(--panel-2);
  border: 1px solid var(--border);
  padding: 5px 12px;
  border-radius: 20px;
}
.trial-badge.ended { color: var(--live); border-color: var(--live); }
.auth-sub { color: var(--muted); font-size: 14px; margin: -8px 0 20px; }
.auth-switch { font-size: 13px; color: var(--muted); margin-top: 16px; text-align: center; }
.auth-switch a { color: var(--accent); text-decoration: none; }

.live-badge {
  color: var(--live);
  font-weight: 700;
  letter-spacing: 0.05em;
  animation: pulse 1.4s infinite;
}

@keyframes pulse { 50% { opacity: 0.4; } }

.panel {
  max-width: 420px;
  margin: 8vh auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px;
}

.panel h1 { margin: 0 0 22px; font-size: 22px; }

label {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 16px;
}

input[type=text], input[type=password], input[type=email], input[type=number] {
  width: 100%;
  margin-top: 6px;
  padding: 11px 13px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 9px;
  color: var(--text);
  font-size: 15px;
}

button {
  cursor: pointer;
  font-size: 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  padding: 10px 16px;
  background: var(--panel-2);
  color: var(--text);
  transition: transform 0.05s, background 0.15s, border-color 0.15s;
}
button:hover { background: #2b3358; border-color: var(--accent); }
button:active { transform: scale(0.97); }
button:disabled { opacity: 0.45; cursor: default; }
/* Async controls acknowledge the FIRST click immediately. The small spinner
   is language-neutral, so Enter studio and stage moves keep their translated
   labels while config/token/LiveKit work is still in flight. */
@keyframes action-pending-spin { to { transform: rotate(360deg); } }
.action-pending { cursor: progress !important; }
button.action-pending:disabled { opacity: 0.82; }
button.action-pending::after,
.backstage-card.action-pending::after {
  content: '';
  display: inline-block;
  width: 0.8em; height: 0.8em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: action-pending-spin 0.7s linear infinite;
}
button.action-pending::after { margin-inline-start: 0.55em; vertical-align: -0.08em; }
.backstage-card.action-pending::after {
  position: absolute; right: 10px; bottom: 10px;
  color: var(--accent);
}
/* Anchor styled as a control (e.g. header "Edit" link) — matches buttons exactly. */
a.ctrl { display: inline-flex; align-items: center; justify-content: center; gap: 5px; cursor: pointer;
  font-size: 14px; border-radius: 10px; border: 1px solid var(--border); padding: 10px 16px;
  background: var(--panel-2); color: var(--text); text-decoration: none; white-space: nowrap;
  transition: transform 0.05s, background 0.15s, border-color 0.15s; }
a.ctrl:hover { background: #2b3358; border-color: var(--accent); color: var(--text); }
/* The top-right header controls share one clean, centred, equal-height pill look. */
#lang-btn, #help-btn, #editor-link, #store-link, #settings-btn, #lang-sel {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  /* Smaller (Marcelo, twice) — 34px was the tallest thing in the left cluster
     and set the height of the whole header row. */
  height: 28px; padding: 5px 8px; font-size: 11.5px; line-height: 1; }

.primary { background: var(--grad); border: none; color: var(--on-accent, #fff); width: 100%; font-weight: 600; box-shadow: 0 6px 18px rgba(79,140,255,0.35); }
.primary:hover { filter: brightness(1.08); background: var(--grad); }
.primary.small { padding: 7px 14px; font-size: 13px; box-shadow: none; }
.primary.sched-big { width: 100%; padding: 16px; font-size: 16px; border-radius: 12px; box-shadow: 0 8px 22px rgba(79,140,255,0.42); }
.primary.sched-big:hover { transform: translateY(-1px); }

/* Add-destination picker (StreamYard-style) */
.dest-picker { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 6px; }
.dest-card { display: flex; align-items: center; gap: 14px; padding: 16px 18px; border-radius: 12px;
             border: 1px solid var(--border); background: var(--panel-2); color: var(--text);
             cursor: pointer; text-align: left; transition: border-color .15s, transform .1s; }
.dest-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.dest-card-icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center;
                  justify-content: center; font-size: 20px; color: #fff; flex: none; }
.dest-card-icon.yt { background: #ff0000; }
.dest-card-name { font-weight: 700; font-size: 16px; }
.dest-card-sub { margin-left: auto; font-size: 13px; opacity: .6; }
.danger { background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 600; }

#studio { padding: 20px; }

.grid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr;   /* column count is set per tile-count in JS (fitGrid) */
  grid-auto-rows: 1fr;
  gap: 8px;
  padding: 8px;
}

.tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  background: var(--tile);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Shared screens never crop — the CONTENT matters; letterbox instead. */
.tile.screen-tile video { object-fit: contain; background: #000; }

.placeholder {
  width: 100%; height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 44px;
  font-weight: 600;
  color: var(--muted);
  background: #14171d;
}

/* ── LIVING AVATARS — camera-off tiles perform instead of sitting still.
   Idle: a slow breath. Speaking: a lively bounce + a glow ring in the
   brand color. Applied by the ActiveSpeakersChanged wiring in JS. ── */
@keyframes avatar-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.015); }
}
@keyframes avatar-talk {
  0%, 100% { transform: scale(1.02); }
  30% { transform: scale(1.07); }
  60% { transform: scale(1.035); }
}
.tile .placeholder { animation: avatar-breathe 4.2s ease-in-out infinite; transform-origin: center 62%; }
.tile.speaking .placeholder { animation: avatar-talk 0.42s ease-in-out infinite; }
/* Camera ON tiles skip the bounce (real faces move by themselves) and get
   the classic "who's talking" glow ring only. */
.tile.speaking { box-shadow: 0 0 0 3px var(--accent), 0 0 22px var(--accent); }
.backstage-card.speaking { box-shadow: 0 0 0 3px var(--accent), 0 0 14px var(--accent); }

.tile .label {
  position: absolute;
  z-index: 6;               /* names sit ABOVE avatars and overlays in the tile */
  bottom: 5px; left: 5px;   /* hug the corner, StreamYard-style */
  background: rgba(0,0,0,0.6);
  padding: 4px 11px;
  border-radius: 6px;
  font-size: 14px;
  cursor: move;             /* grab the name tag to place it anywhere on the tile */
  /* The Host's Opacity, fading plate + stripe + letters as one. The STUDIO
     paints its labels from CSS (theme classes and these variables); only the
     guest monitor and the compositor go through NametagEngine.decorate(),
     which writes the same number inline. Two paths, one control — so this
     variable is what makes the studio agree with what goes on air. */
  opacity: var(--nametag-op, 1);
}
.tile .label:hover { outline: 2px dashed rgba(255,255,255,.5); outline-offset: 1px; }
/* once you drag a name tag, every tile's name tag follows to that spot
   (grow from the top-left drop point when it's been placed) */
/* The ONE position model (nametag-engine.js decorate): --nametag-x/y are a
   TRAVERSAL fraction of the tile — left X% of the tile, pulled back X% of the
   tag's own box, so 0 = flush left/top, 100% = flush right/bottom and the tag
   can never leave the tile whatever its rendered size. The engine writes the
   identical formula inline for the guest monitor and the broadcast; this rule
   is the studio's copy of it. */
#stage.nametag-moved .tile .label { left: var(--nametag-x, 0%); top: var(--nametag-y, 0%); bottom: auto; right: auto;
  width: max-content;   /* natural size at every station — see nametag-engine.js */
  --nametag-tf: translate(calc(var(--nametag-x, 0%) * -1), calc(var(--nametag-y, 0%) * -1));
  --nametag-origin: left top; }

.cam-kill {
  position: absolute;
  top: 8px; right: 8px;
  font-size: 12px;
  padding: 5px 10px;
  background: rgba(0,0,0,0.65);
  border-color: rgba(255,255,255,0.2);
}

.controls {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;          /* tight? second line — never buttons on buttons */
  gap: 8px 12px;
  /* 5px, from 8: another 10% off the dock (Marcelo). The 38px round buttons set
     the height, so the padding is the only part left to give — 56px → 50px.
     The inline-end reserve is the ⏺ Record fix: .controls-golive is absolutely
     positioned over this centred row, so once the dock narrowed the row slid
     underneath it and #stream-key ate every click meant for ⏺ and 🗣️ — dead
     from a 682px dock down, which is the default panel at a 993px window. The
     row now stops before the lane the box occupies. Logical, so it reserves the
     correct side in rtl too — and scoped with :has() below, because guest.html
     uses this same class with no golive box at all. */
  padding: 5px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  position: relative;
}
/* Round icon buttons, StreamYard-style */
/* 38px, not 54: the dock's height is padding + this, and Marcelo asked for the
   whole bar 30% thinner — 80px → 56px. The 🎧 button below scales with it, or
   an absolutely-positioned 72px circle would hang out of a 56px bar. */
.controls .round {
  width: 38px; height: 38px; border-radius: 50%; padding: 0;
  /* nowrap: the invite button's label is TWO emoji (👤➕) and at this size they
     wrapped onto a second line, pushing the ➕ out of the circle. */
  font-size: 16px; white-space: nowrap; display: flex; align-items: center; justify-content: center;
  background: var(--panel-2); border: 1px solid rgba(255,255,255,0.08);
}
.controls .round.off { background: var(--danger); border-color: var(--danger); color: #fff; }
/* 🎨 PER-BUTTON COLOURS (Marcelo, 2026-08-13: "give them each one of the 4…
   the record button doesn't need it, stay red because he's a record button").

   Painted with an INSET shadow rather than `background`, for one reason worth
   keeping: an unset custom property resolves to `transparent`, so a button
   nobody has coloured renders byte-identically to before — no defaults to keep
   in sync with the themeable --panel-2, and no class for JS to toggle. The
   emoji sits above the shadow, so the glyph never dims.

   Only the FILL is painted: a border-color here loses to the `border`
   shorthand on `.controls .round` in Chrome however specific the selector is
   (measured), and the faint white rim every button shares is what keeps the
   row looking like one row anyway.

   `:not(.off)` is the safety line. Muted mic, stopped camera and live screen
   share all wear .off = danger red, and that signal must never be paintable
   over — a Host has to be able to see at a glance that their mic is dead. */
.controls .round:not(.off) {
  /* Two layers, and the ORDER is the point: the hairline is listed first, so
     it paints above the flood. That soft rim is what every uncoloured button
     already wears (the 1px white-at-8% border) — a saturated fill swallows it,
     and Marcelo asked for it back: "this very soft line… when I change the
     colour it goes away, I like that." Both layers default to `transparent`,
     so a button nobody has coloured is still byte-identical to before. */
  box-shadow: inset 0 0 0 1px var(--ctl-ring, transparent),
              inset 0 0 0 999px var(--ctl-tint, transparent);
}
.controls #mic-btn.round:not(.off)    { --ctl-tint: var(--ctl-mic, transparent); --ctl-ring: var(--ctl-mic-ring, transparent); }
.controls #cam-btn.round:not(.off)    { --ctl-tint: var(--ctl-cam, transparent); --ctl-ring: var(--ctl-cam-ring, transparent); }
.controls #screen-btn.round:not(.off) { --ctl-tint: var(--ctl-screen, transparent); --ctl-ring: var(--ctl-screen-ring, transparent); }
.controls #invite-btn.round:not(.off) { --ctl-tint: var(--ctl-invite, transparent); --ctl-ring: var(--ctl-invite-ring, transparent); }
.controls #voice-btn.round:not(.off)  { --ctl-tint: var(--ctl-voice, transparent); --ctl-ring: var(--ctl-voice-ring, transparent); }
/* The dot that paints each button, tucked onto its lower-right corner: the
   negative start margin pulls it back over the button it belongs to, so the
   pairing is unmistakable without wrapping every button in a box, and
   align-self keeps it inside the strip's height — the bar stays as narrow as
   its 38px buttons make it. */
.ctl-tint {
  flex: 0 0 auto; width: 14px; height: 14px; padding: 0;
  margin: 0 0 0 -10px; align-self: flex-end; cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); border-radius: 50%;
  background: none; opacity: .55; transition: opacity .12s, transform .12s;
}
.ctl-tint:hover { opacity: 1; transform: scale(1.15); }
/* 🎨 Side-panel colour pickers (Style ▸ Side panel) — a labelled swatch each,
   sized like the studio-colour input beside them so the block reads as one
   family rather than a bolted-on row. */
.panel-tint-row { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.panel-tint { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--muted); cursor: pointer; }
.panel-tint input[type="color"] {
  width: 34px; height: 26px; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: 7px; background: none;
}
.ctl-tint::-webkit-color-swatch-wrapper { padding: 0; }
.ctl-tint::-webkit-color-swatch { border: 0; border-radius: 50%; }
/* The invite button carries two glyphs where every other button carries one,
   so it needs a smaller size to sit inside the same circle. */
.controls #invite-btn.round { font-size: 13px; }
/* `inset-inline-end`, not `right`: under rtl a physical inset keeps this box on
   the right while the button row mirrors past it, so the buttons it covered
   changed from ⏺/🗣️ to 🎤/📷 instead of the problem going away. */
.controls-golive {
  position: absolute; inset-inline-end: 14px; display: flex; align-items: center; gap: 8px;
}
.controls-golive input { width: 150px; margin: 0; }
.controls .golive { width: auto; padding: 12px 22px; }
/* Go live button, top-right corner (StreamYard-style) */
.header-golive { display: flex; align-items: center; gap: 8px; }
.golive-top {
  background: #2563eb; color: #fff; border: 0; border-radius: 8px;
  padding: 9px 20px; font-weight: 700; font-size: 14px; cursor: pointer; width: auto;
}
.golive-top:hover { background: #1d4ed8; }
.header-golive .danger.small { padding: 9px 16px; font-size: 14px; }
.ctrl.off { background: var(--danger); border-color: var(--danger); color: #fff; }

/* Layout selector strip under the stage — also hosts the 📐 size sliders */
/* ONE LINE: the layout buttons and the name side by side, centred as a pair
   (Marcelo: "move the buttons left, the title right… enough to create one
   line"). It was a two-column grid so the size sliders would line up under each
   other in column 2 — but all three have since moved onto the edges of the
   things they size, and with only two items left a stacked grid was spending a
   whole row on 125px of text. Measured before committing: 426px of buttons +
   125px of title + the gap = 575 in a 703px strip.
   `flex-wrap: wrap` is the safety net — on a window too narrow for both, they
   drop back to two lines instead of running off the end. */
.layout-strip {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center; gap: 2px 6px; padding: 8px 0 2px;
}
/* The buttons may wrap onto a second line when the column has to narrow. They
   used to be nowrap, which a grid cannot honour: with a wide side panel the
   row simply ran off the end of the strip instead of giving way. */
.layout-strip .layout-row { flex-wrap: wrap; gap: 6px; justify-content: center; }
/* Says whose controls these are. With the DJ mixer open, the stage's own row
   sits above the mixer's and the two look alike enough to be mistaken for each
   other (Marcelo: "sometimes I get confused").

   The label shares the sliders' row rather than taking one of its own: the
   sliders go to the right-hand end and the name fills the space that was empty
   anyway — "you don't need to make another line… just move the side panel and
   the tabs controls more to the right side corner". No breakpoint, so the size
   never changes with the window, which is what went wrong twice before. */
.layout-strip .strip-bottom {
  display: flex; align-items: center; gap: 6px; min-width: 0;
}
/* …and the sliders stop well short of the right edge rather than hugging it.
   space-between alone threw them into the corner and left a canyon in the
   middle (Marcelo: "you still have a lot of space in the middle… bring them
   back a bit"). The right padding is what pulls them in; it is a percentage so
   the gap stays proportional instead of collapsing on a narrow window. */
.layout-strip .strip-title {
  /* The label claims the left 46% and the sliders start straight after it, so
     they land near the middle at EVERY width. space-between threw them into
     the right corner and opened a 611px canyon on a wide window while looking
     fine on a narrow one — the gap has to be proportional, not absolute. */
  flex: 0 1 auto; min-width: 0; text-align: left; margin: 0;
  /* 19px, down from 33. The name and the eight layout buttons now share ONE
     line (Marcelo: "I want both of them to fit in one line… reduce the size"),
     and at 993px with a 340px panel the strip is 595px — 8×34 buttons + this
     name + the gaps come to 562. It is the size that made one line possible,
     not a taste change. */
  /* +10% (Marcelo, 2026-08-13): 20px → 22px. The one-line budget above still
     holds — the label grows ~12px against the ~33px of measured slack. */
  font-size: 22px; line-height: 1.1; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; opacity: .9;
  /* the Host's own colour, falling back to the panel's muted tone */
  color: var(--strip-title-color, var(--muted));
}
.layout-strip .strip-title-color {
  flex: 0 0 auto; width: 22px; height: 22px; padding: 0; border: 1px solid var(--border);
  border-radius: 6px; background: none; cursor: pointer; opacity: .5; transition: opacity .12s;
}
.layout-strip .strip-title-color:hover { opacity: 1; }
/* right-hand end of that row — the margin that used to centre it is gone */
/* The stack is a GRID ITEM now, not a child of .strip-bottom — the old
   selector stopped matching when it moved, which left the base 24px margin on
   and pushed it 25px past the column edge (and off the end of the strip).
   The left padding is the ▫ icon plus its gap, so the blue sliders line up
   with the GREEN one rather than with the column edge behind it. */
.share-row input { flex: 1; margin: 0; }

.msg { color: var(--muted); font-size: 13px; margin-top: 12px; min-height: 18px; }
.error { color: var(--danger); font-size: 13px; }
.hidden { display: none !important; }

/* ─── Studio layout: stage + side panel ─────────────────────────────
   The controls bar lives INSIDE the stage column, under the stage and
   ABOVE the DJ booth (Marcelo: in DJ mode the bottom made no sense).
   Its 📐 sliders moved to the layout strip, so it stays light; if it
   ever gets tight it wraps — never buttons on buttons. */
.studio-layout {
  display: flex;
  gap: 18px;
  align-items: flex-start;
}
.stage-col { flex: 1; min-width: 0; }
.stage-col .controls { margin-top: 14px; }

/* 📱 Portrait broadcast — the stage becomes a 9:16 "Short monitor".
   Same height budget (--stage-avail); only the shape flips. Everything ON
   the monitor (tiles, gear, banners, logos) rides along in percentages. */
.stage.vertical {
  aspect-ratio: 9 / 16;
  width: min(100%, calc(var(--stage-avail, calc(100dvh - 480px)) * 9 / 16));
  min-width: 240px;
}
.stage {
  position: relative;
  aspect-ratio: 16 / 9;   /* fixed 16:9 canvas */
  /* StreamYard-style app layout: JS measures the exact height everything
     else needs (header, layout row, controls, tray, messages) and the
     stage gets precisely what remains — via --stage-avail. The 16:9
     broadcast shape NEVER distorts; width is capped by allowed height. */
  width: min(100%, calc(var(--stage-avail, calc(100dvh - 480px)) * 16 / 9));
  min-width: 380px;   /* shrink further on short windows instead of overflowing */
  margin-inline: auto;    /* stays centered when height-capped */
  background-size: cover;
  background-position: center;
  border-radius: 12px;
  overflow: hidden;
  /* Element Capture will only restrict a recording to an element that forms a
     stacking context, and `position: relative` with z-index auto does not.
     This is the no-paint-change way to form one. Nothing inside the stage was
     ever able to escape it anyway — `overflow: hidden` already clips every
     descendant to this box — so the only ordering this can affect is against
     siblings that overlap the stage, and those (the prompter layer, modals)
     are all deliberately above it. */
  isolation: isolate;
}

.bg-row { display: flex; gap: 8px; flex-wrap: wrap; }
.bg-thumb {
  flex: 1 1 54px;
  height: 54px;
  min-width: 54px;
  background-size: cover;
  background-position: center;
  font-size: 11px;
  color: var(--muted);
  padding: 0;
}
/* "+" add tile — clearly an "add your own" action, like StreamYard */
.add-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  font-size: 20px;
  line-height: 1;
  color: var(--accent);
  border: 1px dashed var(--border-strong, var(--accent));
  background: transparent;
}
.add-tile small { font-size: 10px; color: var(--muted); }
.add-tile:hover { background: var(--panel-2); border-color: var(--accent); border-style: dashed; }
/* "+ Add …" full-width buttons for clips + music */
.add-btn {
  width: 100%;
  margin: 8px 0;
  padding: 12px;
  font-size: 14px;
  color: var(--accent);
  border: 1.5px dashed var(--accent);
  background: rgba(79,140,255,0.07);
  font-weight: 600;
  border-radius: 10px;
}
.add-btn small { color: var(--muted); font-weight: 400; }
.add-btn:hover { background: rgba(79,140,255,0.16); border-color: var(--accent); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); opacity: 0; }
/* Fixed pink, never var(--accent): the Host repaints the studio with the accent
   and a selected marker painted in it disappears. */
.bg-thumb.active { border-color: #ff4d6d; box-shadow: 0 0 0 2px rgba(255,77,109,.28); }
/* ── Media library category groups — nested INSIDE #bg-row/#overlay-row/
   #logo-row so every `#bg-row .bg-thumb` selector (restore, the wantedBg
   crash-guard) still matches. A group spans the full row; its own row wraps. */
.ml-cat { flex: 1 1 100%; display: flex; flex-direction: column; gap: 5px; margin-top: 4px; }
.ml-cat-h { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.ml-cat-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* An empty group (no uploads yet) shows nothing, not a lonely heading. */
.ml-cat:has(.ml-cat-row:empty) { display: none; }

/* ─── ⋯ on every media tile ─────────────────────────────────────────
   A wrapper, because a tile IS a <button> and a button inside a button is
   invalid HTML — the browser drops one of them. The wrapper takes over the
   tile's flex sizing so the rows lay out exactly as before, and every
   `#bg-row .bg-thumb` selector still matches, being a descendant query. */
.ml-tile { position: relative; display: flex; flex: 1 1 54px; min-width: 54px; height: 54px; }
.ml-tile > .bg-thumb { flex: 1 1 auto; width: 100%; height: 100%; min-width: 0; }
.ml-dots {
  position: absolute; top: 2px; right: 2px;
  width: 22px; height: 20px; padding: 0;
  font-size: 15px; line-height: 1;
  border-radius: 6px;
  background: rgba(10, 14, 30, 0.82);
  border: 1px solid var(--border);
  color: #fff;
  opacity: 0;
  transition: opacity 0.14s;
}
.ml-tile:hover .ml-dots, .ml-dots:focus-visible, .ml-dots:focus { opacity: 1; }
/* A logo tile IS its silhouette: .logo-thumb paints `contain` (below), centred,
   with transparent margins — so unlike a cover-cropped background there is no
   spare scenery for the ⋯ to sit on. Measured at the shipped panel width the
   mark and the button overlapped by 15px on a 71px tile. 78px = the 54px tile
   floor plus the 24px gutter, so reserving the gutter never shrinks the mark
   below its usual size. `:has` rather than a class, so this stays CSS-only. */
.ml-tile:has(> .logo-thumb) { flex: 1 1 78px; min-width: 78px; padding-right: 24px; }
/* A custom EFFECT tile is a labelled button in a grid, not a 54px thumbnail,
   so its wrapper takes the grid cell instead of the media-tile box. The gutter
   keeps the ⋯ off the label the way the logo rule keeps it off the mark. */
.ml-tile:has(> .vfx-btn) { flex: 1 1 auto; min-width: 0; height: auto; padding-right: 26px; }
.ml-tile > .vfx-btn { width: 100%; height: 100%; }
/* The recipe card needs room for its selects; the plain menu does not. */
.ml-pop.ml-pop-wide { min-width: 250px; }
.ml-pop-field select {
  width: 100%; padding: 7px 9px; font-size: 13px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
}
/* No hover on a touch screen, so the ⋯ is always there. The tile stays ≥54px
   with a 22px control in its corner, which leaves a tappable tile behind it. */
@media (hover: none), (pointer: coarse) { .ml-dots { opacity: 1; } }
/* ⏱ A delay is assigned — visible without opening anything, as asked. Fixed
   pink, never var(--accent): the Host repaints the studio with the accent and a
   marker painted in it disappears (the .bg-thumb.active precedent above). */
.ml-chip {
  position: absolute; left: 2px; bottom: 2px;
  padding: 1px 5px; border-radius: 999px;
  font-size: 9px; font-weight: 800; line-height: 1.5;
  background: rgba(255, 77, 109, 0.92); color: #fff;
  pointer-events: none;
}
.ml-tile.ml-pending .ml-chip { background: #ff4d6d; box-shadow: 0 0 0 2px rgba(255, 77, 109, 0.3); }
.ml-tile.ml-pending > .bg-thumb { opacity: 0.55; }

/* One popover for every tile. Fixed-position and viewport-clamped because at
   ≤900px the panel's content lives in a 62vh scroller, and a menu positioned
   inside that scroller would be clipped by it. */
.ml-pop {
  position: fixed; z-index: 400;
  min-width: 210px; max-width: min(280px, calc(100vw - 16px));
  /* The effect-editor view of this popover is the tall one — on a short phone
     it ran off the bottom with its Save button unreachable. Cap it against the
     viewport and let the contents scroll instead. */
  max-height: calc(100vh - 24px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--panel);
  border: 1px solid var(--border-strong, var(--border));
  border-radius: 12px;
  padding: 6px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.ml-pop-h {
  font-size: 12px; color: var(--muted);
  padding: 7px 9px 6px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ml-pop-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  font-size: 14px; padding: 9px 10px;
  border: none; background: transparent; border-radius: 8px;
}
.ml-pop-item:hover { background: var(--panel-2); border: none; }
.ml-pop-field { display: flex; flex-direction: column; gap: 4px; padding: 6px 9px 8px; }
.ml-pop-field span { font-size: 11px; color: var(--muted); }
.ml-pop-field input {
  width: 100%; padding: 7px 9px; font-size: 13px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
}
.ml-pop-bar { display: flex; gap: 6px; padding: 0 9px 6px; }
.ml-pop-bar button { flex: 1; padding: 7px 0; font-size: 13px; border-radius: 8px; }
.ml-pop-save { background: #ff4d6d; border-color: #ff4d6d; color: #fff; font-weight: 700; }
.ml-pop-save:hover { background: #ff3355; border-color: #ff3355; }

/* 🔎 One filter per row, shown only where a row is long enough to need it. */
.ml-search { margin: 2px 0 6px; }
.ml-search input {
  width: 100%; padding: 7px 10px; font-size: 12.5px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
}
.ml-search input::placeholder { color: var(--muted); }
/* Filtered out. `!important` because .ml-tile sets display:flex and .ml-cat
   display:flex — both would otherwise win on specificity order. */
.ml-hide { display: none !important; }
/* Full-frame overlays: contain, exactly like the guest view and the compositor
   (.sv-ovbox.full img / .ov-full img) — the studio used to stretch these in
   9:16 while YouTube letterboxed them. Boxed overlays keep `fill`: the image
   deliberately takes the shape the Host sculpts. */
.overlay-box.ov-full img { object-fit: contain; }
.bg-none { background: var(--bg); }
.logo-thumb { background-size: contain; background-repeat: no-repeat; background-color: var(--tile); }

/* ─── Per-tile "⋮" menu ─────────────────────────────────────────── */
.tile-menu-wrap { position: absolute; top: 8px; right: 8px; z-index: 8; }
.tile-menu-btn {
  width: 34px; height: 34px;
  padding: 0;
  font-size: 20px;
  line-height: 1;
  border-radius: 8px;
  background: rgba(10,14,30,0.75);
  border: 1px solid var(--border);
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s;
}
.tile:hover .tile-menu-btn, .tile-menu-btn:focus { opacity: 1; }
.tile-menu {
  position: absolute;
  top: 40px; right: 0;
  min-width: 200px;
  z-index: 150;   /* floats from <body> when open — above tiles, below modals */
  background: var(--panel);
  border: 1px solid var(--border-strong, var(--border));
  border-radius: 12px;
  padding: 6px;
  box-shadow: 0 12px 30px rgba(0,0,0,0.5);
}
.tile-menu-name {
  font-size: 13px;
  color: var(--muted);
  padding: 8px 10px 6px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.tile-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  font-size: 14px;
  padding: 9px 10px;
  border: none;
  background: transparent;
  border-radius: 8px;
}
.tile-menu-item:hover { background: var(--panel-2); border: none; }
.tmi-icon { font-size: 15px; width: 18px; text-align: center; }

/* ─── Backstage ─────────────────────────────────────────────────── */
.backstage {
  /* 10% thinner (Marcelo): the panel's own padding and the dashed invite box's
     padding are the whole of the empty band under "Present or invite". */
  margin-top: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 9px 16px;
}
.backstage-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 10px;
}
.backstage-head b { color: var(--text); }
.stage-count {
  background: var(--panel-2);
  padding: 3px 10px;
  border-radius: 20px;
  color: var(--accent);
  font-weight: 600;
}
.backstage-tray { display: flex; gap: 10px; align-items: stretch; }
.backstage-tray .backstage-list { flex: 1; flex-wrap: nowrap; overflow-x: auto; align-items: center; }
.backstage-list { display: flex; gap: 10px; flex-wrap: wrap; min-height: 20px; }
.backstage-empty { font-size: 12px; color: var(--muted); }
.present-invite {
  flex: none; width: 150px;
  border: 2px dashed rgba(255,255,255,0.2); border-radius: 12px;
  background: transparent; color: var(--muted);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; padding: 10px 8px; font-size: 13px;
}
.present-invite:hover { border-color: var(--accent); color: var(--text); }
.present-invite .pi-icon { font-size: 20px; }
.backstage-card {
  width: var(--backstage-card-w, 150px);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.backstage-preview {
  aspect-ratio: 16 / 9;
  border-radius: 6px;
  overflow: hidden;
  background: var(--tile);
  display: flex;
  align-items: center;
  justify-content: center;
}
.backstage-preview video { width: 100%; height: 100%; object-fit: cover; }
.backstage-av {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--grad);
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 600; color: #fff;
}
.backstage-info { display: flex; flex-direction: column; gap: 5px; }
.backstage-name { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.backstage-card .ctrl.small { width: 100%; }
/* 📐 Below ~110px the card is mostly chrome, and narrowing it alone buys almost
   nothing: "↑ Add to stream" wraps into more HEIGHT than the picture has, so the
   tile ends up half as wide and just as tall. Set by initBackstageSize()
   (app-stage.js) from the tray's own size slider. */
body.bs-card-tiny .backstage-card { padding: 4px; gap: 4px; border-radius: 8px; }
body.bs-card-tiny .backstage-info { gap: 2px; }
body.bs-card-tiny .backstage-name { font-size: 10px; }
body.bs-card-tiny .backstage-av { width: 24px; height: 24px; font-size: 11px; }
/* The chip is a LABEL, not a control: the whole card carries the click and the
   title ("Click to add to the stream"), and `.onstage` already states the state
   as an accent border. Nothing is lost by dropping the words at thumbnail size —
   which is the one change that actually makes the tile shorter. */
body.bs-card-tiny .backstage-chip { display: none; }
body.bs-card-tiny .backstage-card .tile-menu-btn { width: 18px; height: 18px; font-size: 12px; }
body.bs-card-tiny .backstage-card .tile-menu-wrap { top: 5px; right: 5px; }
/* …and the PANEL has to come down with them, or shrinking the cards buys nothing
   (Marcelo: "50% thinner as well when we go small"). The tray stretches to its
   tallest child, and at thumbnail size that stopped being the card: "Present or
   invite" was 72px against the card's 48, so IT was setting the height. Everything
   the panel spends on air gives some back too. */
body.bs-card-tiny .present-invite { width: 104px; padding: 5px 6px; gap: 2px; font-size: 11px; border-radius: 9px; }
body.bs-card-tiny .present-invite .pi-icon { font-size: 14px; }
body.bs-card-tiny .backstage { padding: 6px 16px; }
body.bs-card-tiny .backstage-head { margin-bottom: 6px; font-size: 12px; }
body.bs-card-tiny .backstage > .bs-size { margin: -6px -16px 6px; }
body.bs-card-tiny .backstage-tray { gap: 6px; }
body.bs-card-tiny .backstage-list { gap: 6px; }
/* One-click add/remove (StreamYard-style people tray) */
.backstage-card { cursor: pointer; transition: border-color .15s, transform .1s, box-shadow .15s; }
/* No transform here — it would trap the floating ⋮ menu (fixed-position). */
.backstage-card:hover { border-color: var(--accent); }
.backstage-card.onstage { border: 2px solid var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent); }
.backstage-chip {
  font-size: 11px; font-weight: 700; text-align: center;
  padding: 4px 6px; border-radius: 999px;
  background: rgba(255,255,255,0.08); color: var(--text);
}
.backstage-chip.live { background: var(--accent); color: #fff; }

/* ─── Co-host keys ──────────────────────────────────────────────── */
.pill {
  font-size: 10px;
  background: var(--panel-2);
  color: var(--accent);
  padding: 2px 8px;
  border-radius: 20px;
  margin-left: 6px;
  vertical-align: middle;
}
.cohost-list { display: flex; flex-direction: column; gap: 6px; }
.cohost-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}
.cohost-row span { flex: 1; }

/* ─── Guest stage status banner ─────────────────────────────────── */
.stage-status {
  padding: 12px 16px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 14px;
}
.backstage-status { background: var(--panel-2); color: var(--muted); }
.onstage-status { background: rgba(255,59,92,0.15); color: var(--live); border: 1px solid rgba(255,59,92,0.4); }

/* Layout variants change how the grid arranges tiles */
.stage[data-layout="side"] .grid { grid-template-columns: 1fr 1fr; }

/* Solo — only the first person, full stage */
.stage[data-layout="solo"] .grid { grid-template-columns: 1fr; }
.stage[data-layout="solo"] .tile:not(:first-child) { display: none; }

/* Spotlight — first person big on top, the rest in a small strip below */
.stage[data-layout="spotlight"] .grid { grid-template-columns: repeat(4, 1fr); grid-template-rows: 3fr 1fr; }
.stage[data-layout="spotlight"] .tile:first-child { grid-column: 1 / -1; grid-row: 1; }

/* Picture-in-picture — first person fills, others float bottom-right */
.stage[data-layout="pip"] .grid { display: block; }
.stage[data-layout="pip"] .tile:first-child { width: 100%; height: 100%; }
.stage[data-layout="pip"] .tile:not(:first-child) {
  position: absolute;
  width: 24%;
  bottom: 14px;
  right: 14px;
  z-index: 3;
  box-shadow: 0 4px 14px rgba(0,0,0,0.5);
}
.stage[data-layout="pip"] .tile:nth-child(3) { right: calc(24% + 26px); }
.stage[data-layout="pip"] .tile:nth-child(4) { right: calc(48% + 38px); }

/* Interview — one big on the left, the rest stacked on the right */
.stage[data-layout="side-spot"] .grid { grid-template-columns: 3fr 1fr; }
.stage[data-layout="side-spot"] .tile:first-child { grid-row: 1 / -1; }

/* Screen share (presentation) — the shared screen big on top, cameras in a strip */
.stage[data-layout="screen"] .grid { grid-template-columns: repeat(5, 1fr); grid-template-rows: 3fr 1fr; }
.stage[data-layout="screen"] .screen-tile { grid-column: 1 / -1; grid-row: 1; order: -1; }
.stage[data-layout="screen"] .tile:not(.screen-tile) { grid-row: 2; }

/* Custom — free drag + resize */
.stage[data-layout="custom"] .grid { display: block; }
.stage[data-layout="custom"] .tile { position: absolute; cursor: grab; }
.stage[data-layout="custom"] .tile:active { cursor: grabbing; }
.resize-handle {
  position: absolute;
  /* 10px, not 18 — 70% less area (Marcelo), the same cut the tile's
     `Remove from panel` pill took. The corner radii below come down with it,
     or a 10px square with a 10px radius stops being a leaf and becomes a dot. */
  width: 10px; height: 10px;
  background: var(--accent);
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 9;
}
/* one handle per corner — each resizes toward the OPPOSITE corner */
.resize-handle.rh-br { right: 0; bottom: 0; cursor: nwse-resize; border-radius: 6px 0 6px 0; }
.resize-handle.rh-bl { left: 0; bottom: 0; cursor: nesw-resize; border-radius: 0 6px 0 6px; }
.resize-handle.rh-tr { right: 0; top: 0; cursor: nesw-resize; border-radius: 0 6px 0 6px; }
.resize-handle.rh-tl { left: 0; top: 0; cursor: nwse-resize; border-radius: 6px 0 6px 0; }
.stage[data-layout="custom"] .tile:hover .resize-handle { opacity: 0.9; }
/* mid-edge grips — stretch ONE axis only: the shape-disturbers */
.resize-handle.rh-e { right: 0; top: 50%; transform: translateY(-50%); cursor: ew-resize; border-radius: 6px 0 0 6px; }
.resize-handle.rh-w { left: 0; top: 50%; transform: translateY(-50%); cursor: ew-resize; border-radius: 0 6px 6px 0; }
.resize-handle.rh-s { bottom: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; border-radius: 6px 6px 0 0; }
.resize-handle.rh-n { top: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; border-radius: 0 0 6px 6px; }
/* ⭕ CIRCLES — the custom free-drag layout with round camera bubbles
   (Marcelo). Same drag/resize logic; the tile is masked to a circle. */
.stage[data-layout="circle"] .grid { display: block; }
/* The round cut lives on the MEDIA, not on the tile. Clipping the whole tile
   also clipped the name tag — which is why shapes used to hide names entirely.
   The tile stays a plain rectangle that shows nothing of itself, so the label
   can sit on top of the circle exactly as it does on a square. */
.stage[data-layout="circle"] .tile { position: absolute; cursor: grab;
  overflow: visible; border: 0; background: transparent; box-shadow: none;
  filter: drop-shadow(0 10px 28px rgba(0,0,0,.45)); }
.stage[data-layout="circle"] .tile > video,
.stage[data-layout="circle"] .tile > img,
.stage[data-layout="circle"] .tile > canvas,
.stage[data-layout="circle"] .tile > .placeholder {
  border-radius: 50%; overflow: hidden;
  border: 3px solid rgba(255,255,255,.55); box-sizing: border-box; }
.stage[data-layout="circle"] .tile:active { cursor: grabbing; }
.stage[data-layout="circle"] .tile video { object-fit: cover; width: 100%; height: 100%; }
.stage[data-layout="circle"] .tile:hover .resize-handle { opacity: 0.9; }
/* corner handles nudge inward so they stay visible inside the round mask */
.stage[data-layout="circle"] .resize-handle.rh-br { right: 9%; bottom: 9%; border-radius: 50%; }
.stage[data-layout="circle"] .resize-handle.rh-bl { left: 9%; bottom: 9%; border-radius: 50%; }
.stage[data-layout="circle"] .resize-handle.rh-tr { right: 9%; top: 9%; border-radius: 50%; }
.stage[data-layout="circle"] .resize-handle.rh-tl { left: 9%; top: 9%; border-radius: 50%; }
/* 🎭 MIXED SHAPES — the layout where the shape belongs to the TILE, not to the
   layout: one camera a circle, the next a triangle, the next a heart, exactly
   as each overlay already chooses its own. Free-drag like its siblings; the cut
   comes from a .shape-* class the tile carries, so the wire only has to send a
   word per tile and every surface reads it the same way. */
.stage[data-layout="mix"] .grid { display: block; }
.stage[data-layout="mix"] .tile { position: absolute; cursor: grab;
  overflow: visible; border: 0; background: transparent; box-shadow: none;
  filter: drop-shadow(0 10px 24px rgba(0,0,0,.45)); }
.stage[data-layout="mix"] .tile:active { cursor: grabbing; }
.stage[data-layout="mix"] .tile:hover .resize-handle { opacity: 0.9; }
.stage[data-layout="mix"] .tile video { object-fit: cover; width: 100%; height: 100%; }
/* A tile with no shape of its own stays a plain rectangle — the same thing a
   Custom-layout tile is, so nothing surprises a Host who has not chosen yet. */
.tile.shape-circle > video, .tile.shape-circle > img,
.tile.shape-circle > canvas, .tile.shape-circle > .placeholder {
  border-radius: 50%; overflow: hidden;
  border: 3px solid rgba(255,255,255,.55); box-sizing: border-box; }
.tile.shape-tri > video, .tile.shape-tri > img,
.tile.shape-tri > canvas, .tile.shape-tri > .placeholder {
  -webkit-clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%);
  clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%); }
.tile.shape-tri.tri-down > video, .tile.shape-tri.tri-down > img,
.tile.shape-tri.tri-down > canvas, .tile.shape-tri.tri-down > .placeholder {
  -webkit-clip-path: polygon(0% 0%, 100% 0%, var(--tri-apex, 50%) 100%);
  clip-path: polygon(0% 0%, 100% 0%, var(--tri-apex, 50%) 100%); }
.tile.shape-heart > video, .tile.shape-heart > img,
.tile.shape-heart > canvas, .tile.shape-heart > .placeholder {
  -webkit-mask-image: var(--heart-mask); mask-image: var(--heart-mask);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
/* The per-tile shape picker: four little buttons, on hover, like the flip badge. */
.tile-shapes { position: absolute; left: 50%; bottom: -14px; transform: translateX(-50%);
  display: flex; gap: 3px; z-index: 9; opacity: 0; transition: opacity .12s; }
.tile:hover > .tile-shapes { opacity: 1; }
@media (hover: none) { .tile-shapes { opacity: 1; } }
.tile-shapes button { width: 22px; height: 22px; border-radius: 6px; padding: 0;
  border: 1px solid rgba(255,255,255,.28); background: rgba(0,0,0,.6); color: #fff;
  font-size: 12px; line-height: 1; cursor: pointer; }
.tile-shapes button.on { border-color: #ffb020; color: #ffb020; }

/* 🔼❤️ TRIANGLES & HEARTS — the circle family grows: same free-drag
   bubbles, new masks (Marcelo: "it will look fun… people would love it").
   Borders/shadows come via drop-shadow so they follow the cut shape. */
:root { --heart-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'><path d='M50 88 C22 64 4 46 4 28 C4 12 16 4 29 4 C38 4 46 9 50 17 C54 9 62 4 71 4 C84 4 96 12 96 28 C96 46 78 64 50 88 Z'/></svg>"); }
.stage[data-layout="tri"] .grid, .stage[data-layout="heart"] .grid { display: block; }
.stage[data-layout="tri"] .tile, .stage[data-layout="heart"] .tile { position: absolute; cursor: grab;
  overflow: visible;   /* the shape is cut on the media; the tile must not re-clip the name */
  border: 0; background: transparent; box-shadow: none;
  filter: drop-shadow(0 10px 22px rgba(0,0,0,.45)); }
/* Cut the media, not the tile — see the circle note above. The placeholder
   is media too: it is what a camera-off guest shows, so it must be cut by the
   very same rule or turning a camera off would square the shape off. */
.stage[data-layout="tri"] .tile > video,
.stage[data-layout="tri"] .tile > img,
.stage[data-layout="tri"] .tile > canvas,
.stage[data-layout="tri"] .tile > .placeholder {
  -webkit-clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%);
          clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%); }
.stage[data-layout="heart"] .tile > video,
.stage[data-layout="heart"] .tile > img,
.stage[data-layout="heart"] .tile > canvas,
.stage[data-layout="heart"] .tile > .placeholder {
  -webkit-mask-image: var(--heart-mask); mask-image: var(--heart-mask);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.stage[data-layout="tri"] .tile:active, .stage[data-layout="heart"] .tile:active { cursor: grabbing; }
.stage[data-layout="tri"] .tile video, .stage[data-layout="heart"] .tile video { object-fit: cover; width: 100%; height: 100%; }
/* Names work on shapes now. Centred along the bottom rather than hugging the
   corner: a triangle, circle and heart all narrow toward their corners, so the
   square's bottom-left position would hang the tag out in empty space. */
.stage[data-layout="circle"] .tile .label,
.stage[data-layout="tri"] .tile .label,
.stage[data-layout="heart"] .tile .label {
  /* --nametag-tf, not `transform`: the size scale lives on the transform and
     must survive being centred. Origin moves to the centre so growing the tag
     opens both ways instead of pushing it off the shape. */
  left: 50%; right: auto; --nametag-tf: translateX(-50%); --nametag-origin: center bottom;
  white-space: nowrap; z-index: 7; }
.stage[data-layout="circle"] .tile .label { bottom: 8%; }
.stage[data-layout="tri"] .tile .label { bottom: 6%; }
.stage[data-layout="tri"] .tile.tri-down .label { bottom: auto; top: 8%; }
.stage[data-layout="heart"] .tile .label { bottom: 22%; }
/* A host who dragged their name somewhere specific keeps that choice — the
   same traversal formula as the base rule above, repeated here only to beat
   the shaped-layout centring in specificity order. */
#stage.nametag-moved .tile .label { left: var(--nametag-x, 0%);
  --nametag-tf: translate(calc(var(--nametag-x, 0%) * -1), calc(var(--nametag-y, 0%) * -1)); }
.stage[data-layout="tri"] .tile:hover .resize-handle, .stage[data-layout="heart"] .tile:hover .resize-handle { opacity: 0.9; }
/* handles tucked where each shape has flesh: triangle low, heart in the lobes */
.stage[data-layout="tri"] .resize-handle.rh-br { right: 6%; bottom: 6%; border-radius: 50%; }
.stage[data-layout="tri"] .resize-handle.rh-bl { left: 6%; bottom: 6%; border-radius: 50%; }
/* Slope handles follow the leaning edge instead of floating over the part
   that got clipped away. Left slope: x = apex*s. Right slope: + (100-100s). */
.stage[data-layout="tri"] .resize-handle.rh-tr { right: auto; left: calc(var(--tri-apex, 50%) * 0.58 + 42%); top: 42%; transform: translateX(-50%); border-radius: 50%; }
.stage[data-layout="tri"] .resize-handle.rh-tl { left: calc(var(--tri-apex, 50%) * 0.58); top: 42%; transform: translateX(-50%); border-radius: 50%; }
/* 🔺/🔻 the triangle flips — badge on hover; direction rides pos.td */
.tile .tri-flip { display: none; }
/* Also for a tile wearing the triangle in the Mixed layout: the flip belongs
   to the SHAPE, not to the old one-shape-for-everyone layout. */
.stage[data-layout="tri"] .tile .tri-flip,
.tile.shape-tri .tri-flip { display: flex; position: absolute; left: 30%; transform: translateX(-50%);
  /* Left of centre and low, because dead-centre-bottom is the one place on a
     triangle that is never free: `Remove from panel` is anchored there (z 6,
     opaque — it swallowed the flip whole, so Marcelo saw the ↔ apex and no ⇅)
     and the rh-s resize handle sits at x 47–53% with z 9 underneath it. The
     band from x 15–45% below the pill is clear of both, and on a triangle it
     is still inside the shape because the base runs the full width. */
  bottom: 2%; width: 26px; height: 26px; align-items: center; justify-content: center;
  border-radius: 50%; border: 0; background: rgba(0,0,0,.55); color: #fff; font-size: 14px;
  cursor: pointer; opacity: 0; z-index: 7; padding: 0; transition: opacity .15s; }
.stage[data-layout="tri"] .tile:hover .tri-flip,
.tile.shape-tri:hover .tri-flip { opacity: 0.9; }
.stage[data-layout="tri"] .tile.tri-down .tri-flip,
.tile.shape-tri.tri-down .tri-flip { bottom: auto; top: 2%; }
/* ↔ drag the tip left/right. 26px of circle inside a transparent 8px ring,
   so the finger target is 42px while the dot stays small. */
.tile .tri-apex { display: none; }
.stage[data-layout="tri"] .tile .tri-apex,
.tile.shape-tri .tri-apex { display: flex; position: absolute;
  left: var(--tri-apex, 50%); transform: translateX(-50%); top: 2%;
  width: 26px; height: 26px; box-sizing: content-box; align-items: center; justify-content: center;
  border-radius: 50%; border: 8px solid transparent; background: rgba(0,0,0,.55); background-clip: padding-box;
  color: #fff; font-size: 14px; cursor: ew-resize; opacity: 0; z-index: 10; padding: 0;
  transition: opacity .15s; touch-action: none; }
.stage[data-layout="tri"] .tile:hover .tri-apex,
.tile.shape-tri:hover .tri-apex { opacity: 0.9; }
.stage[data-layout="tri"] .tile.tri-down .tri-apex,
.tile.shape-tri.tri-down .tri-apex { top: auto; bottom: 2%; }
/* The flip has to move with the base cut. Leaving it on .tile while the upright
   triangle moved to the media intersected the two shapes into a diamond. */
.stage[data-layout="tri"] .tile.tri-down > video,
.stage[data-layout="tri"] .tile.tri-down > img,
.stage[data-layout="tri"] .tile.tri-down > canvas,
.stage[data-layout="tri"] .tile.tri-down > .placeholder {
  -webkit-clip-path: polygon(var(--tri-apex, 50%) 100%, 100% 0%, 0% 0%);
          clip-path: polygon(var(--tri-apex, 50%) 100%, 100% 0%, 0% 0%); }
/* flipped: the flesh is on TOP — handles trade places */
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-tr { right: 6%; top: 6%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-tl { left: 6%; top: 6%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-br { right: auto; left: calc(var(--tri-apex, 50%) * 0.58 + 42%); bottom: 42%; transform: translateX(-50%); }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-bl { left: calc(var(--tri-apex, 50%) * 0.58); bottom: 42%; transform: translateX(-50%); }
.stage[data-layout="heart"] .resize-handle.rh-tr { right: 13%; top: 13%; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-tl { left: 13%; top: 13%; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-br { right: 34%; bottom: 22%; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-bl { left: 34%; bottom: 22%; border-radius: 50%; }
/* edge grips tucked into each shape's flesh */
.stage[data-layout="circle"] .resize-handle.rh-e, .stage[data-layout="circle"] .resize-handle.rh-w,
.stage[data-layout="circle"] .resize-handle.rh-n, .stage[data-layout="circle"] .resize-handle.rh-s { border-radius: 50%; }
.stage[data-layout="tri"] .resize-handle.rh-e { right: auto; left: calc(var(--tri-apex, 50%) * 0.28 + 72%); top: 72%; transform: translateX(-50%); border-radius: 50%; }
.stage[data-layout="tri"] .resize-handle.rh-w { left: calc(var(--tri-apex, 50%) * 0.28); top: 72%; transform: translateX(-50%); border-radius: 50%; }
/* rh-n shares the tip with the ↔ grip, and 16% is not far enough on a short
   tile: the grip's finger target is 42px of transparent ring from top 2%, so
   rh-n was landing INSIDE it and — one layer up at z 9 — taking every drag.
   Marcelo grabbed the ↔ and the tile resized out from under his mouse ("I move
   the triangle left, the mouse move right"). A px floor clears the ring at
   every tile size; the percentage still wins on a tall one. */
.stage[data-layout="tri"] .resize-handle.rh-n { left: var(--tri-apex, 50%); top: max(16%, 48px); border-radius: 50%; }
/* Same collision for a triangle tile in the Mixed layout, where rh-n is not
   moved to the tip but still starts at the top edge, under the grip. */
.tile.shape-tri:not(.tri-down) .resize-handle.rh-n { top: 48px; }
.stage[data-layout="tri"] .resize-handle.rh-s { bottom: 3%; border-radius: 50%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-e { left: calc(var(--tri-apex, 50%) * 0.20 + 80%); top: 20%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-w { left: calc(var(--tri-apex, 50%) * 0.20); top: 20%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-n { top: 3%; }
.stage[data-layout="tri"] .tile.tri-down .resize-handle.rh-s { left: var(--tri-apex, 50%); bottom: 16%; }
.stage[data-layout="heart"] .resize-handle.rh-n { top: 20%; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-s { bottom: 13%; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-e { right: 15%; top: 44%; transform: none; border-radius: 50%; }
.stage[data-layout="heart"] .resize-handle.rh-w { left: 15%; top: 44%; transform: none; border-radius: 50%; }

/* Overlay layers sit on top of the video grid */
.logo-overlay {
  position: absolute;
  width: 90px;
  height: auto;
  z-index: 12;   /* logos stay visible over playing clips too */
  pointer-events: none;
}
.logo-overlay.pos-tl { top: 14px; left: 14px; }
.logo-overlay.pos-tr { top: 14px; right: 14px; }
.logo-overlay.pos-bl { bottom: 14px; left: 14px; }
.logo-overlay.pos-br { bottom: 14px; right: 14px; }

.overlay-box {
  position: absolute;
  left: 0; top: 0; width: 100%; height: 100%;   /* default: full frame */
  z-index: 12;
  cursor: grab;
  border-radius: 12px;
}
.overlay-box:active { cursor: grabbing; }
.overlay-box img {
  width: 100%;
  height: 100%;
  object-fit: fill;   /* the image TAKES the shape you sculpt — stretch it
                         tall, wide, square; the graphic follows your hands */
  pointer-events: none;
  display: block;
}
.overlay-box.fade { transition: opacity 0.4s ease; }
.overlay-box:hover .resize-handle { opacity: 0.9; }   /* reshape in ANY layout */
.check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 10px;
}
.check-row input { width: auto; margin: 0; }

/* Every banner on air lives in here, drawn by banner-engine.js — the SAME
   renderer the compositor and the guest monitor mount. The geometry used to be
   spelled out here, in egress.html and in stage-view.js, and the three copies
   had already drifted (16s scroll here, 18s on both remotes), which is exactly
   why the host's preview stopped matching the broadcast. z-15 puts banners over
   playing clips/loops and over stage text, under the pinned comment — the band
   the two remote renderers already used. */
.banner-layer { position: absolute; inset: 0; z-index: 14; pointer-events: none; }
/* touch-action:none so dragging a banner up the stage on a tablet moves the
   banner instead of scrolling the page out from under it. */
.banner-layer .bn-slot { pointer-events: auto; touch-action: none; }

/* Banner list cards (StreamYard-style, with hover Show / Off) */
.banner-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
/* Banner cards — StreamYard-style: the whole card is the on/off button.
   The live one turns solid accent (like SY's blue card). */
.banner-card {
  position: relative;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  cursor: pointer;
  transition: background .15s, transform .08s;
}
.banner-card:hover { background: rgba(255,255,255,0.1); }
.banner-card:active { transform: scale(0.99); }
/* LIVE state is hard-coded pink, never var(--accent): the accent is the host's
   to repaint, and a host who paints the studio in their banner colour must not
   lose the one indicator that says "this is going out right now". Same rule and
   same pink as the 🅣 stage-text cards. */
.banner-card.on-air { border-color: #ff4d6d; box-shadow: 0 0 0 2px rgba(255,77,109,.28); }
.banner-card.on-air.waiting { border-color: #ffb43d; box-shadow: 0 0 0 2px rgba(255,180,61,.26); }
.banner-live { font-size: 9px; font-weight: 800; letter-spacing: .08em; color: #ff4d6d; margin-left: auto; }
.banner-card.waiting .banner-live { color: #ffb43d; letter-spacing: 0; font-size: 12px; }
/* Keep the title clear of the ✏️ / 🗑 pair, which sits over this corner. They
   used to appear only on hover, so nothing had to make room for them. */
/* the on/off switch — a real button, full width, above the control rows */
/* One line, shaped like the "Create a banner…" field above it. Left to wrap, a
   long message broke into a column of two-word lines and made a tall ragged
   block of the card — the panel is a narrow sidebar, so there is never room for
   a sentence. Truncating keeps every card the same height and the list scannable;
   the full text is still there to read and change under ✏️ Edit. */
.banner-card-text { display: block; width: 100%; text-align: left; font: inherit; font-size: 14px;
  margin: 0 0 10px; line-height: 1.35; padding: 10px 48px 10px 14px; cursor: pointer;
  background: rgba(255,255,255,.05); border: 1px solid transparent; border-radius: 20px; color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.banner-card-text:hover { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.18); }
.banner-card-text.live { border-color: #ff4d6d; }
/* wraps, because the panel is a resizable sidebar and this row now carries
   the type chip, Edit, two colours, a speed fader and the direction switch */
.banner-card-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 8px; }
.banner-chip {
  background: rgba(255,255,255,0.14); border: 0; color: #fff;
  border-radius: 16px; padding: 5px 14px; font-size: 12px; font-weight: 600; cursor: pointer; width: auto;
}
.banner-chip:hover { background: rgba(255,255,255,0.24); }
/* The way into everything per-banner — it has to read as a button. */
/* Name-tag orientation controls — labelled rows in the Style panel flow. */
.nt-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.nt-opt { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
  color: inherit; border-radius: 8px; padding: 5px 11px; font-size: 12px; cursor: pointer; width: auto; }
/* Fixed pink for the selected state — never var(--accent), which the Host repaints. */
.nt-opt.on { border-color: #ff4d6d; color: #ff4d6d; box-shadow: 0 0 0 1px rgba(255,77,109,.3); }
.banner-edit-chip { background: rgba(255,255,255,0.10); }
.banner-edit-chip.open { background: #ff4d6d; color: #fff; }
 /* This banner's own colours and speed, right on the card. */
.banner-swatch-in { width: 26px; height: 24px; padding: 0; margin: 0; flex: none; cursor: pointer;
  border: 1px solid rgba(255,255,255,.28); border-radius: 6px; background: none; }
.banner-swatch-in::-webkit-color-swatch-wrapper { padding: 2px; }
.banner-swatch-in::-webkit-color-swatch { border: none; border-radius: 4px; }
.banner-dir { padding: 4px 9px; font-size: 13px; }
/* Dimmed while the banner is only following the panel's font; solid once it
   has its own, with a ✕ to hand it back. */
.banner-font.following { opacity: .68; }
.banner-font-x { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
  color: inherit; border-radius: 50%; width: 20px; height: 20px; padding: 0; line-height: 18px;
  font-size: 10px; cursor: pointer; flex: none; }
.banner-font-x:hover { border-color: #ff5aa0; color: #ff5aa0; }
.banner-font { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* this banner's own − / + size, mirroring the panel's stepper above */
.banner-size-step { display: inline-flex; align-items: center; gap: 4px; background: rgba(0,0,0,.22);
  border: 1px solid var(--border); border-radius: 8px; padding: 2px 4px; }
.banner-size-step .zoom-btn { width: 22px; height: 22px; padding: 0; font-size: 13px; line-height: 1; }
.banner-size-step b { font-size: 11px; min-width: 38px; text-align: center; font-variant-numeric: tabular-nums; }
.banner-anim-row .bn-anim { padding: 3px 8px; font-size: 13px; }
.banner-speed { flex: 1 1 70px; min-width: 60px; max-width: 150px; height: 18px; margin: 0; cursor: pointer; }
/* 🌈 cycling reads as a rainbow, never as the accent — hosts repaint that. */
.banner-cycling { background: linear-gradient(90deg, #ff4d4d, #ffd24d, #4dff88, #4dd2ff, #b64dff);
  -webkit-background-clip: text; background-clip: text; }
.banner-badge { font-size: 11px; opacity: .75; }
/* Always visible, never hover-only: on a touch screen a hover-only ✏️ / 🗑 is
   a control that does not exist. */
.banner-tools { position: absolute; top: 8px; right: 8px; z-index: 2; display: flex; gap: 8px; }
.bt-btn { background: transparent; border: 0; cursor: pointer; font-size: 14px; padding: 2px; }
.banner-alloff { align-self: flex-start; background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.16); color: inherit; border-radius: 8px;
  padding: 4px 12px; font-size: 11.5px; cursor: pointer; width: auto; }
.banner-alloff:hover { border-color: #ff5aa0; color: #ff5aa0; }

/* ── The per-banner editor, in a drawer under its own card ──────────────
   Opening it under the card it edits means "which banner is this?" is never a
   question, and nothing about it is modal — the stage stays live behind it. */
.banner-item { display: flex; flex-direction: column; }
.banner-editor {
  background: var(--panel-2); border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 12px 12px; margin-top: -6px; padding: 12px 14px 14px;
  display: flex; flex-direction: column; gap: 10px;
}
/* The panel is a narrow sidebar, so the label sits ABOVE its control rather
   than beside it — side-by-side left ~130px for a slider plus its two end
   markers, and the 🐇 fell off the edge. */
.bn-row { display: flex; flex-direction: column; align-items: stretch; gap: 5px; }
.bn-lbl { font-size: 11.5px; opacity: .72; }
.bn-inline { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; flex-wrap: wrap; }
.bn-range { flex: 1; min-width: 90px; height: 20px; margin: 0; cursor: pointer; }
.bn-val { font-size: 11px; opacity: .8; min-width: 38px; font-variant-numeric: tabular-nums; }
.bn-num { width: 62px; margin: 0; padding: 4px 6px; font-size: 12px; }
.bn-text { width: 100%; margin: 0; padding: 6px 8px; font: inherit; font-size: 13px; resize: vertical;
  background: rgba(0,0,0,.22); border: 1px solid var(--border); border-radius: 8px; color: inherit; }
.bn-stop { position: relative; display: inline-flex; }
.bn-stop-x { position: absolute; top: -6px; right: -6px; width: 15px; height: 15px; padding: 0;
  line-height: 13px; text-align: center; font-size: 9px; border-radius: 50%; cursor: pointer;
  background: var(--panel); color: inherit; border: 1px solid var(--border); }
.bn-stop-x:hover { border-color: #ff5aa0; color: #ff5aa0; }
.bn-preset {
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
  color: inherit; border-radius: 8px; padding: 4px 9px; font-size: 11.5px;
  cursor: pointer; width: auto;
}
.bn-preset:hover { border-color: #ff5aa0; }
.bn-preset.on { border-color: #ff4d6d; color: #ff4d6d; box-shadow: 0 0 0 1px rgba(255,77,109,.3); }
.bn-turtle { font-size: 12px; opacity: .7; }
.bn-hint { font-size: 11px; opacity: .6; }
.bn-timed { display: flex; flex-direction: column; gap: 3px; font-size: 10.5px; opacity: .8; }
.banner-editor input[type=color] {
  width: 38px; height: 28px; padding: 0; border: 1px solid rgba(255,255,255,.22);
  border-radius: 7px; background: none; cursor: pointer; margin: 0;
}
.bn-seq input[type=color] { width: 28px; }
/* 🌈 toggle — the fixed rainbow of the DJ deck's own cycle button. */
/* 🌈 bar / 🎨 letters over one fader — the stage gear's own arrangement. */
.bn-anim-wrap { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.bn-anim-lbl { font-size: 10.5px; letter-spacing: .06em; opacity: .7; margin-left: 4px; }
.bn-anim { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
  border-radius: 8px; padding: 3px 8px; font-size: 13px; cursor: pointer; width: auto; }
.bn-anim.on { background: linear-gradient(90deg, #ff4d4d, #ffd24d, #4dff88, #4dd2ff, #b64dff);
  color: #fff; border-color: transparent; box-shadow: 0 0 10px rgba(255,255,255,.25); }

/* ─── Style tab: brand color, themes, text size ───────────────────── */
.brand-color-row { display: flex; gap: 10px; align-items: center; }
.brand-color-row input[type=color] {
  width: 54px; height: 44px; padding: 0; border: 1px solid var(--border);
  border-radius: 10px; background: none; cursor: pointer;
}
.brand-color-row input[type=text] { flex: 1; margin: 0; text-transform: uppercase; }
/* The banner's colour and font rows at 30% off the standard control size. They
   were built from the same full-size swatches and chips as the studio-wide
   settings, which is right on a settings page and too heavy inside a narrow
   panel where they sit above the banner list. Opted into per row with
   `bn-compact` rather than restyling `.brand-color-row`: the studio brand and
   store rows share that class and are meant to stay full size. */
.bn-compact input[type=color] { flex: none; width: 38px; height: 31px; border-radius: 7px; }
.bn-compact input[type=text] { padding: 8px 9px; border-radius: 6px; font-size: 11px; }
/* `.hint` is display:block, so as a flex item beside a flex:1 chip it wraps when
   the panel is dragged narrow — "🔤 Banner font" became three stacked lines and
   made the row TALLER than the full-size one it replaces. Its font-size already
   matches; holding it on one line is what this row actually needs. */
.bn-compact .hint { white-space: nowrap; }
.bn-compact .ctrl { padding: 6px 10px; border-radius: 7px; font-size: 10px; }
.theme-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }
.theme-btn {
  display: flex; align-items: center; justify-content: center;
  padding: 16px 4px; border-radius: 10px; background: var(--panel-2);
  border: 2px solid transparent; cursor: pointer; overflow: hidden;
}
/* Fixed pink, never var(--accent): the Host repaints the studio with the accent
   and a selected marker painted in it disappears. */
.theme-btn.active { border-color: #ff4d6d; box-shadow: 0 0 0 2px rgba(255,77,109,.28); }
/* Mini name-tag preview inside each theme button — uses the brand colour */
.tp { font-size: 11px; font-weight: 800; color: #0b1020; padding: 6px 10px; white-space: nowrap; }
.tp-bubble  { background: var(--accent); border-radius: 14px; }
.tp-block   { background: var(--accent); border-radius: 2px; }
.tp-classic { background: var(--accent); border-radius: 3px; padding-right: 16px; clip-path: polygon(0 0, 100% 0, 86% 100%, 0% 100%); }
.tp-minimal { background: #6b7280; color: #fff; border-left: 4px solid var(--accent); border-radius: 2px; }
.seg { display: flex; gap: 6px; background: rgba(255,255,255,0.06); border-radius: 10px; padding: 4px; }
.seg-btn {
  flex: 1; padding: 8px 0; border: 0; border-radius: 7px; background: transparent;
  color: inherit; font: inherit; font-weight: 600; opacity: 0.6; cursor: pointer;
}
.seg-btn.active { background: rgba(255,255,255,0.16); opacity: 1; }
/* Live avatar's two pickers at 30% off. `.seg-btn` takes `font: inherit`, so
   these labels ran at full panel size and "My room — avatar only" wrapped to
   four lines, turning a picker into a slab. Relative, not a pixel size: the
   panel is resizable and its text scales, so 0.7em stays 30% smaller at every
   width instead of only at the one this was measured on. Scoped by id — the
   camera-quality and privacy pickers use `.seg` too and keep their size. */
#la-bg-seg, #la-track-seg { padding: 3px; }
#la-bg-seg .seg-btn, #la-track-seg .seg-btn { font-size: 0.7em; padding: 4px 3px; line-height: 1.25; }

/* − / + zoom stepper (Name tag / Pinned comment / Chat text size) */
.size-zoom { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.06); border-radius: 10px; padding: 4px; }
.size-zoom .zoom-btn { flex: 0 0 auto; width: 42px; height: 32px; border: 0; border-radius: 7px;
  background: rgba(255,255,255,0.10); color: inherit; font-size: 20px; font-weight: 800; line-height: 1; cursor: pointer; transition: background .12s; }
.size-zoom .zoom-btn:hover { background: rgba(255,255,255,0.20); }
.size-zoom .zoom-btn:active { transform: translateY(1px); }
/* Chat-on-stage: label above, control beneath. Side by side, "Pinned comment
   size" wrapped to three lines and the − ＋ group — flex:1 with two 42px
   buttons — was pushed clean off the panel's right edge. Nothing here needs
   to share a line with a three-word label. */
/* `.inline-row` (display:flex) is declared LATER in this file, at the same
   specificity, so it beat the grid below and the row stayed side by side —
   which is why the − ＋ group kept hanging off the panel's right edge even
   after it was "fixed". Naming both classes wins on specificity rather than
   on source order, so it cannot be undone by something moving in the file. */
.inline-row.chat-size-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; }
.inline-row.chat-size-row > .hint { white-space: normal; }
.inline-row.chat-size-row .size-zoom { width: 100%; min-width: 0; box-sizing: border-box; }
.inline-row.chat-size-row .bn-range { width: 100%; min-width: 0; }
/* …and 30% smaller inside this panel, per the Owner. The chat panel is the
   narrowest column in the studio; controls sized for the wide Style panel
   swamp it. Only here — the same buttons elsewhere keep their size. */
.inline-row.chat-size-row .size-zoom { gap: 5px; padding: 3px; border-radius: 8px; }
.inline-row.chat-size-row .zoom-btn { width: 30px; height: 22px; font-size: 14px; border-radius: 5px; }
.inline-row.chat-size-row .zoom-val { font-size: 11px; }
.inline-row.chat-size-row > .hint { font-size: 11px; }
.size-zoom .zoom-val { flex: 1 1 auto; text-align: center; font-weight: 800; font-size: 14px;
  font-variant-numeric: tabular-nums; letter-spacing: .02em; }

/* Name-tag theme variants (applied to the tile label) */
/* Name tags: --nametag-color = background (falls back to brand),
   --nametag-text = the letters (falls back to white) */
/* 🌈/🎨 animated colours arrive as --nt-anim-bg / --nt-anim-fg on #stage, in
   FRONT of the static value in each chain — so every theme animates the surface
   it actually paints. classic colours its stripe (its box is a fixed dark) and
   minimal only its letters (it is transparent on purpose); animating
   `background` for all four would do nothing on classic and destroy minimal. */
#stage[data-nametag="block"] .label { background: var(--nt-anim-bg, var(--nametag-color, var(--accent))); color: var(--nt-anim-fg, var(--nametag-text, #fff)); border-radius: 0; }
#stage[data-nametag="bubble"] .label { background: var(--nt-anim-bg, var(--nametag-color, var(--accent))); color: var(--nt-anim-fg, var(--nametag-text, #fff)); border-radius: 20px; }
#stage[data-nametag="classic"] .label { background: rgba(0,0,0,0.6); border-left: 4px solid var(--nt-anim-bg, var(--nametag-color, var(--accent))); border-radius: 0 6px 6px 0; color: var(--nt-anim-fg, var(--nametag-text, #fff)); }
#stage[data-nametag="minimal"] .label { background: transparent; padding-left: 0; text-shadow: 0 1px 3px rgba(0,0,0,0.95); color: var(--nt-anim-fg, var(--nt-anim-bg, var(--nametag-text, var(--nametag-color, #fff)))); }
/* Text size — the − / + zoom scales the name tags via this variable.
   Use transform (not zoom) so it doesn't distort the drag-position math;
   origin = the corner the tag is anchored to, so it grows from that point. */
/* Name-tag size is a scale(), and it is the ONLY rule allowed to write
   `transform` on a label. Anything that needs to reposition a tag sets
   --nametag-tf instead, so moving a tag (or putting it on a shaped tile) can
   never silently disable the size control — which is exactly what happened
   when a dragged tag met `transform: none`. */
/* Rotation joins the SAME declaration — translate → rotate → scale, the order
   this repo standardised. Rotate sits after --nametag-tf so a shaped layout's
   centring translate is not itself rotated (the tag would swing off the shape).
   transform-origin is a var too, which finally makes the shaped-layout origin
   work: it was written at 0-4-0 specificity and this ID rule (1-1-0) had been
   silently winning, so a shaped tag would have pivoted about its left edge. */
#stage .label {
  transform: var(--nametag-tf, translate(0)) rotate(calc(var(--nametag-rot, 0deg) - var(--tile-rot, 0deg))) scale(var(--nametag-zoom, 1));
  transform-origin: var(--nametag-origin, left bottom);
  text-align: var(--nametag-align, left);
}
/* A rotated tag sweeps outside its tile, so the tile stops clipping — the same
   trade the shaped layouts already make, and the Host's preview shows it. */
#stage.nametag-rot .tile { overflow: visible; }
/* …but a SHAPED layout has already cut its media on purpose, and this rule is
   an ID selector (1-2-1) against the layout's 0-3-1, so it silently won: a
   rotated name tag squared the Host's round tile off. Triangles and hearts
   survived only because they are cut with clip-path, which a radius cannot
   touch — which is why the circle was the one shape that "didn't work". */
/* …and the tile must be excluded by its own SHAPE too, not only by the layout
   it sits in. Mixed lets each tile pick, so `data-layout` is "mix" for a tile
   that is nonetheless a circle, and this rule — an id beats two classes — was
   squaring it back off to a 12px rectangle. Same bug the layout list above
   already fixes, one level down. */
#stage.nametag-rot:not([data-layout="circle"]):not([data-layout="tri"]):not([data-layout="heart"]) .tile:not(.shape-circle):not(.shape-tri):not(.shape-heart) video,
#stage.nametag-rot:not([data-layout="circle"]):not([data-layout="tri"]):not([data-layout="heart"]) .tile:not(.shape-circle):not(.shape-tri):not(.shape-heart) .ph { border-radius: 12px; }
/* Vertical: one upright letter per row (nametag-engine.js builds the rows). */
#stage .label .nt-row { display: block; unicode-bidi: plaintext; }
/* Horizontal has no rows, so the same spacing control opens the letter gaps. */
#stage .label { letter-spacing: var(--nametag-letter, 0); }
/* …and the headline overrides it with its own, so the two lines can be spaced
   differently — separate controls, separate looks (Marcelo). Falls back to the
   name's value, so a studio saved before this renders unchanged. */
#stage .label .label-headline { letter-spacing: var(--nametag-hl-letter, var(--nametag-letter, 0)); }
#stage[data-nametag-layout="vertical"] .label { letter-spacing: normal; }
/* Show/hide names & headlines */
#stage.hide-names .label-name { display: none; }
#stage.hide-headlines .label-headline { display: none; }
/* Hide tile name tags while a lower-third banner / comment is on screen */
#stage.showing-lower .label { display: none; }
/* Ticker on: keep the name tag but lift it above the scrolling bar — by the
   bar's MEASURED height (banner-engine.js reports it), not a guess, so the lift
   survives a resized banner and matches the broadcast exactly. */
#stage.showing-ticker .label { bottom: calc(5px + var(--tag-lift, 43px)); }
/* On-stage chat sits bottom-left — names slide up above it (like the ticker) */
/* (chat is freely movable now — names STAY in the corner; drag the chat if it overlaps) */

/* Studio theme swatches (tint the whole UI) */
.studio-themes { display: flex; gap: 8px; flex-wrap: wrap; }
.studio-swatch { width: 42px; height: 42px; border-radius: 10px; border: 2px solid var(--border); cursor: pointer; padding: 0; }
/* Fixed pink, never var(--accent): the Host repaints the studio with the accent
   and a selected marker painted in it disappears. Sharpest here of anywhere —
   this row sits beside the picker that edits the accent, so the mark would
   vanish while the Host was looking straight at it. */
.studio-swatch.active { border-color: #ff4d6d; box-shadow: 0 0 0 2px rgba(255,77,109,.28); }

.intro-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 12px;
  z-index: 10;
  background: #000;
}

/* Short silent looping videos */
.loop-over {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 12px;
  z-index: 7;            /* on top of people, below the logo/scene screens */
  pointer-events: none;
}
.loop-full {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 12px;
  z-index: 10;           /* full-screen, covers the stage like the intro */
  background: #000;
}
.loop-acts { display: flex; gap: 6px; margin-top: 6px; }
.loop-acts .ctrl.small { flex: 1; }

/* ─── Live chat on stage (YouTube chat / Super Chat / Super Sticker) ── */
.chat-overlay {
  position: absolute;
  left: 16px;
  bottom: 70px;
  width: 340px;
  max-width: 45%;
  max-height: 74%;
  z-index: 20;   /* THE TOP LAYER of the stage — chat outranks everything:
                    clips, scene screens, banners, logos, pinned comments */
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
  overflow: hidden;
}
.superchat-pin, .chat-feed { display: flex; flex-direction: column; gap: 4px; }
.chat-feed { overflow: hidden; }

/* Regular chat — YouTube style: small avatar, gray name inline with message */
.chat-item {
  display: flex; gap: 8px; align-items: flex-start;
  background: transparent;
  padding: 2px 0;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
  animation: chatIn 0.2s ease;
}
.chat-avatar {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff;
  background: var(--accent); background-size: cover; background-position: center;
}
.chat-line { line-height: 1.3; min-width: 0; }
.chat-author { color: var(--chat-color, #d9d9d9); font-weight: 600; font-size: 13px; margin-right: 4px; }
.chat-text { font-size: 14px; word-wrap: break-word; overflow-wrap: anywhere; }

/* Super Chat / Super Sticker — YouTube two-tone card */
.superchat {
  border-radius: 8px; overflow: hidden; color: #fff;
  animation: chatIn 0.2s ease; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
}
.sc-head { display: flex; align-items: center; gap: 10px; padding: 8px 12px; }
.sc-avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.25); font-size: 15px; font-weight: 700;
  background-size: cover; background-position: center;
}
.sc-meta { flex: 1; min-width: 0; }
.sc-author { font-weight: 600; font-size: 13px; opacity: 0.95; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc-amount { font-weight: 700; font-size: 15px; }
.sc-text { padding: 8px 12px; background: rgba(0, 0, 0, 0.15); font-size: 14px; word-wrap: break-word; overflow-wrap: anywhere; }
.ss-emoji { font-size: 40px; line-height: 1; }

@keyframes chatIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* One comment shown on stage — StreamYard-style lower third */
.comment-lower {
  position: absolute;
  left: 24px;
  bottom: 28px;
  /* ABOVE playing clips/loops — and above stage text (14) and banners (15),
     which is the order both remote renderers already draw in. The studio used
     to disagree, so a banner and a text overlapping looked different on the
     preview than on the broadcast; free vertical position makes them overlap
     often enough that it matters. */
  z-index: 16;
  display: flex;
  align-items: center;
  max-width: 80%;
  animation: chatIn 0.25s ease;
}
.cl-avatar {
  width: 63px; height: 63px; border-radius: 50%; flex: none;
  background: var(--chat-color, var(--accent)); background-size: cover; background-position: center;
  box-shadow: 0 0 0 4px var(--chat-color, var(--accent));
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center;
}
.cl-init { color: #fff; font-size: 24px; font-weight: 800; }
.cl-yt {
  position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%);
  background: #fff; color: #ff0000; border-radius: 5px;
  font-size: 10px; line-height: 1; font-weight: 900; padding: 2px 5px;
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.cl-bubble { margin-left: -15px; z-index: 1; min-width: 195px; max-width: 100%; }
.cl-name {
  background: var(--chat-color, var(--accent)); color: #0b1020; font-weight: 800; font-size: 15px;
  padding: 6px 12px 6px 21px; border-radius: 9px 9px 0 0;
  display: inline-flex; gap: 6px; align-items: center;
}
.cl-badge { font-size: 11px; }
.cl-amount { font-weight: 800; margin-left: 6px; }
.cl-amount:empty { display: none; }
.cl-text {
  background: #fff; color: #111; font-size: 17px; font-weight: 600;
  padding: 9px 14px 10px 21px; border-radius: 0 9px 9px 9px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35); word-wrap: break-word; overflow-wrap: anywhere;
}
/* On-screen comment size (Small / Medium / Large) */
#comment-lower[data-size="small"] .cl-avatar { width: 42px; height: 42px; box-shadow: 0 0 0 3px var(--chat-color, var(--accent)); }
#comment-lower[data-size="small"] .cl-init { font-size: 15px; }
#comment-lower[data-size="small"] .cl-name { font-size: 11px; padding: 4px 8px 4px 15px; }
#comment-lower[data-size="small"] .cl-amount { font-size: 11px; }
#comment-lower[data-size="small"] .cl-text { font-size: 11px; padding: 6px 10px 7px 15px; }
#comment-lower[data-size="small"] .cl-bubble { min-width: 140px; margin-left: -11px; }

#comment-lower[data-size="large"] .cl-avatar { width: 82px; height: 82px; box-shadow: 0 0 0 4px var(--chat-color, var(--accent)); }
#comment-lower[data-size="large"] .cl-init { font-size: 32px; }
#comment-lower[data-size="large"] .cl-name { font-size: 21px; padding: 8px 14px 8px 28px; }
#comment-lower[data-size="large"] .cl-amount { font-size: 21px; }
#comment-lower[data-size="large"] .cl-text { font-size: 24px; padding: 13px 18px 14px 28px; }
#comment-lower[data-size="large"] .cl-bubble { min-width: 238px; margin-left: -20px; }

/* The pulled comment follows the selected Name-tag theme (shape) */
#stage[data-nametag="bubble"] .cl-name { border-radius: 22px; }
#stage[data-nametag="bubble"] .cl-text { border-radius: 22px; margin-top: 5px; }
#stage[data-nametag="classic"] .cl-name { border-radius: 5px 5px 0 0; }
#stage[data-nametag="classic"] .cl-text { border-radius: 0 0 5px 5px; }
#stage[data-nametag="block"] .cl-name { border-radius: 0; }
#stage[data-nametag="block"] .cl-text { border-radius: 0; }
#stage[data-nametag="minimal"] .cl-name { border-radius: 0; background: rgba(0,0,0,0.6); color: #fff; }
#stage[data-nametag="minimal"] .cl-text { border-radius: 0; }

/* Comment list in the Chat panel — click 📺 to put one on stage */
.chat-list { display: flex; flex-direction: column; gap: 6px; max-height: 320px; overflow-y: auto; }
.chat-row {
  display: flex; gap: 8px; align-items: center;
  background: rgba(255,255,255,0.05); border-radius: 8px; padding: 6px 8px;
}
.chat-row.super { background: rgba(33,201,94,0.15); }
.chat-row.on-air { box-shadow: 0 0 0 2px var(--accent); } /* highlight the comment that's live */
.chat-row-av {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--chat-color, var(--accent)); color: #fff; font-weight: 700; font-size: 13px;
  background-size: cover; background-position: center;
}
.chat-row-body { flex: 1; min-width: 0; }
.chat-row-name { font-size: 12px; font-weight: 700; opacity: 0.85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-row-text { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-row:hover { background: rgba(255,255,255,0.09); }

/* Compose box (post a comment / private chat) */
.chat-compose { display: flex; gap: 8px; margin-top: 10px; }
.chat-compose input { flex: 1; margin: 0; padding: 10px 12px; border-radius: 20px; }
.chat-send {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  background: var(--accent); border: 0; color: #fff; font-size: 15px; cursor: pointer;
}
.chat-send:hover { filter: brightness(1.1); }

/* Private chat bubbles */
.pchat-row { display: flex; flex-direction: column; gap: 1px; padding: 6px 10px; border-radius: 10px; background: rgba(255,255,255,0.05); }
.pchat-row.mine { background: rgba(79,140,255,0.18); }
.pchat-author { font-size: 11px; font-weight: 700; opacity: 0.8; }
.pchat-text { font-size: 14px; word-wrap: break-word; overflow-wrap: anywhere; }

/* ─── Branded scene screens ─────────────────────────────────────── */
.scene-overlay {
  position: absolute;
  inset: 0;
  z-index: 11;
  border-radius: 12px;
  background:
    radial-gradient(600px 400px at 50% 30%, rgba(155,107,255,0.4), transparent 70%),
    linear-gradient(135deg, #131a3d 0%, #0b0e1f 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 10px;
  overflow: hidden;
}
.scene-overlay::before {
  content: '';
  position: absolute;
  width: 140%; height: 140%;
  background: conic-gradient(from 0deg, transparent, rgba(79,140,255,0.10), transparent 40%);
  animation: scene-spin 8s linear infinite;
}
@keyframes scene-spin { to { transform: rotate(360deg); } }
.scene-logo { width: 92px; height: auto; position: relative; opacity: 0.95; }
.scene-title {
  position: relative;
  font-size: 40px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.5px;
}
.scene-timer {
  position: relative;
  font-size: 64px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 24px rgba(79,140,255,0.6);
}
.scene-timer:empty { display: none; }
.scene-sub {
  position: relative;
  font-size: 17px;
  color: var(--muted);
  max-width: 70%;
}

.screens-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.screen-btn { font-size: 12px; padding: 10px 8px; }
.screen-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ─── Media libraries (clips + music) ───────────────────────────── */
.media-list { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; max-height: 160px; overflow-y: auto; }
.media-row {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 6px 5px 10px;
}
.media-row.playing { border-color: var(--accent); }
.media-name {
  flex: 1;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.media-mini {
  width: 26px; height: 26px;
  padding: 0;
  font-size: 12px;
  border-radius: 6px;
  flex: 0 0 26px;
}
.media-mini.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ─── Intro / Outro slots ───────────────────────────────────────── */
.io-slots { display: flex; gap: 8px; margin: 4px 0 10px; }
.io-slot {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 8px;
  min-width: 0;
}
.io-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); font-weight: 600; }
.io-name { flex: 1; font-size: 12px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.io-play { width: 24px; height: 24px; padding: 0; font-size: 11px; border-radius: 6px; flex: 0 0 24px; }

/* ─── Clip thumbnail cards ──────────────────────────────────────── */
/* minmax(0,1fr): plain 1fr refuses to shrink below a long filename, which
   pushed the whole grid past the panel edge and let it slide sideways. */
.clip-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; margin: 8px 0; }
.clip-card { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.clip-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #05070f center/cover no-repeat;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  overflow: hidden;
}
.clip-thumb:hover { border-color: var(--accent); }
.clip-play {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; color: #fff; opacity: 0;
  background: rgba(0,0,0,0.35); transition: opacity 0.15s;
}
.clip-thumb:hover .clip-play { opacity: 1; }
.clip-dur {
  position: absolute; bottom: 4px; right: 4px;
  background: rgba(0,0,0,0.75); color: #fff;
  font-size: 10px; padding: 1px 5px; border-radius: 4px;
}
.clip-tag {
  position: absolute; top: 4px; left: 4px;
  background: var(--accent); color: #fff;
  font-size: 9px; font-weight: 600; padding: 2px 5px; border-radius: 4px; letter-spacing: 0.03em;
}
.clip-tag.out { background: var(--accent-2); }
.clip-foot { display: flex; align-items: center; gap: 4px; }
.clip-name { flex: 1; min-width: 0; font-size: 11px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.clip-menu { position: relative; }
.clip-menu-btn { width: 22px; height: 22px; padding: 0; font-size: 15px; background: transparent; border: none; color: var(--muted); }
.clip-menu-btn:hover { color: var(--text); background: transparent; border: none; }
.clip-menu .tile-menu { top: 24px; right: 0; min-width: 160px; }

/* ─── Side panel ────────────────────────────────────────────────── */
/* Width is the HOST's, through the 📐 sliders (--st-panel-w / --dj-panel-w
   below). Nothing here may impose a floor: a min-width added to keep the
   Library buttons on screen silently overrode both sliders, and the DJ mixer's
   panel stopped shrinking at all. The Library row solves its own width. */
.side-panel {
  width: 340px;
  flex: 0 0 340px;
  /* 🎨 the Host's own colour for the panel itself (Style ▸ Side panel) */
  background: var(--panel-bg-color, var(--panel));
  border: 1px solid var(--border);
  border-radius: 12px;
  max-height: 80vh;
  display: flex;
  overflow: hidden;
}
/* No tab open → only the rail shows; the empty 340px box never appears. */
.side-panel.collapsed { width: auto; flex: 0 0 auto; }
/* DJ mixer open (desktop layout only) → slim this panel + the tab rail so the mixer
   gets the room. HOW slim is the user's choice: the two vertical .sp-sizer sliders
   flanking the tab rail write --dj-panel-w / --dj-rail-w while the mixer is open
   (defaults 136px / 38px) — one engine, whichever mode is on screen. The mixer's
   centre column used to carry its own copy of that pair; it went when the vertical
   ones landed, because the Host can already see and reach these. When the rail is
   narrow, body.dj-rail-tiny hides the labels (icons only). On phones (≤900px) the
   panel is a full-width stack — leave it alone there. */
@media (min-width: 901px) {
  body.dj-monitor .side-panel:not(.collapsed) { width: var(--dj-panel-w, 136px); flex: 0 0 var(--dj-panel-w, 136px); }
  body.dj-monitor .side-panel .panel-rail { flex: 0 1 var(--dj-rail-w, 38px); min-width: 0; padding: 10px 3px; }
  body.dj-monitor.dj-rail-tiny .side-panel .rail-btn { padding: 8px 2px; }
  body.dj-monitor.dj-rail-tiny .side-panel .rail-btn span:not(.rail-icon):not(.rail-badge) { display: none; }
  body.dj-monitor.dj-rail-tiny .side-panel .rail-btn .rail-icon { font-size: 17px; }
}
/* PHONES — the strip's `flex-wrap: wrap` already drops the name onto its own
   line here; this only centres it once it lands there. (It used to force a
   one-column grid, back when a 220px slider column sat beside the name and the
   pair overflowed a 390px screen. Both of those sliders have since left the
   strip.) Checked at 390, 430, 820 and 1024px — no sideways scroll at any. */
@media (max-width: 760px) {
  .layout-strip .strip-bottom { justify-content: center; }
}

/* 📐 the SAME sizing for the normal studio view — the Style tab's sliders
   (the DJ-open rules above still win while the mixer is on screen). */
@media (min-width: 901px) {
  body:not(.dj-monitor) .side-panel:not(.collapsed) { width: var(--st-panel-w, 340px); flex: 0 0 var(--st-panel-w, 340px); }
  /* `0 1`, not `0 0`: the two vertical .sp-sizer sliders flank this rail, and at
     a narrow panel a rigid rail pushed the right-hand one past the panel's
     `overflow: hidden` — the control for widening the panel vanished exactly
     when you needed it. The rail gives way instead; it has its own slider. */
  body:not(.dj-monitor) .side-panel .panel-rail { flex: 0 1 var(--st-rail-w, 76px); min-width: 0; }
  body.st-rail-tiny:not(.dj-monitor) .side-panel .rail-btn { padding: 8px 2px; }
  body.st-rail-tiny:not(.dj-monitor) .side-panel .rail-btn span:not(.rail-icon):not(.rail-badge) { display: none; }
  body.st-rail-tiny:not(.dj-monitor) .side-panel .rail-btn .rail-icon { font-size: 17px; }

  /* 📐 CLOSED — the bottom of the panel slider takes the panel ALL the way out,
     content and tab rail both, rather than squeezing it into a one-word-per-line
     sliver or stopping at a rail that is still 94px of the stage (Marcelo:
     "sometimes I want to close it down completely" … "it didn't went all the way
     down to 0%"). What is left is 18px: the panel's 2px border and the two 8px
     .sp-sizer strips. Dragging one of them back up restores the panel at its
     remembered width with the same tab still open, which is why they are the one
     thing that must never be hidden here — see the escape-hatch assertion in
     tools/eval/studio-ui-inventory.ts.

     This is a different close from `.collapsed` (clicking the open tab), which
     deliberately KEEPS the rail so the tabs stay one click away. Two closes,
     two intents: put the tabs away, or take the whole panel off the screen.

     `aside.side-panel` on purpose: the two width rules above are (0,3,1) and
     this has to beat them wherever it sits in the file, not merely come later.
     A definite width, not `.collapsed`'s `width: auto` — auto does not
     interpolate, so the close would snap while every other move of this same
     slider glides. */
  body:not(.dj-monitor).st-panel-shut aside.side-panel,
  body.dj-monitor.dj-panel-shut aside.side-panel {
    width: 10px; flex: 0 0 10px; border-color: transparent; background: none;
  }
  /* Only the strip that REOPENS the panel is left. The rail-width slider has no
     rail to size once the panel is closed, and keeping it cost the escape
     hatch: a vertical range draws its thumb wider than its 8px track, so with
     the rail collapsed to nothing between them the rail slider's thumb sat on
     top of the panel slider and swallowed the click that brings the panel back
     (elementFromPoint at the panel strip's own centre returned #st-rail-w). */
  body:not(.dj-monitor).st-panel-shut aside.side-panel #st-rail-w,
  body.dj-monitor.dj-panel-shut aside.side-panel #st-rail-w { display: none; }
  body:not(.dj-monitor).st-panel-shut aside.side-panel .panel-content,
  body.dj-monitor.dj-panel-shut aside.side-panel .panel-content { display: none; }
  /* The rail goes to zero WIDTH but stays in flow. `display: none` on both
     children left the panel with nothing to take its height from: it collapsed
     from 455px to 129px and the .sp-sizer strips — stretched to a dead parent —
     stopped hit-testing to themselves. That is the one-way door this whole
     block is written to avoid, and it is only visible if you measure it. */
  body:not(.dj-monitor).st-panel-shut aside.side-panel .panel-rail,
  body.dj-monitor.dj-panel-shut aside.side-panel .panel-rail {
    flex: 0 0 0; width: 0; min-width: 0; padding: 0; border: 0;
    overflow: hidden; visibility: hidden;
  }
}
/* 📐 The panel's two size sliders, VERTICAL, flanking the tab rail: #st-panel-w
   on its left (where the content ends), #st-rail-w on its right (the panel's
   outer edge). Flex siblings of the rail, so they are the panel's full height
   for free and nothing measures anything.

   `direction: rtl` on a vertical range puts the MAXIMUM at the top, which is the
   only way round that matches the control — dragging up gives you more of the
   thing the slider is standing next to. 8px wide, like the mixer's own faders;
   the 16px the pair costs comes out of the content, never out of --st-panel-w
   (that is the Host's, see the panel-width rules further up). */
.sp-sizer {
  flex: 0 0 8px; align-self: stretch; width: 8px; height: auto; margin: 0;
  writing-mode: vertical-lr; direction: rtl;
  accent-color: var(--accent); cursor: pointer; background: transparent;
}
/* Dragged into the closing zone. A FIXED red, never var(--accent) — the Host
   repaints the studio with the accent, so a cue drawn in it can vanish against
   the very panel it is warning about. */
.sp-sizer.sp-sizer-shut { accent-color: #e5484d; }
/* The side-panel strip sits ABOVE its neighbour wherever the two meet. A
   vertical range draws its thumb wider than its 8px track, so as the panel
   narrows the rail strip's thumb starts overlapping this one — and this is the
   strip that brings the panel back, so it is the one that has to take the
   click (Marcelo, of the two thumbs touching: "the above controller"). */
#st-panel-w.sp-sizer { position: relative; z-index: 2; }
#st-rail-w.sp-sizer { position: relative; z-index: 1; }
/* Phones stack the panel full-width and the rail lies down as a bar on top —
   a full-height vertical slider there would be a 100%-tall hairline. */
@media (max-width: 900px) {
  .sp-sizer { display: none; }
}
/* the panel glides to its new size instead of snapping (Marcelo: "rough") */
@media (min-width: 901px) {
  .side-panel { transition: width .16s ease-out, flex-basis .16s ease-out; }
  .side-panel .panel-rail { transition: flex-basis .16s ease-out; }
}
/* 📐 panel sizing inline on the controls bar — a home that never shrinks.
   The two sliders stack on top of each other in one 220px block (Marcelo). */
/* The two rows sit close together (Marcelo). The gap comes down AND the
   range's own 2px browser margin goes, because that margin was most of what
   separated the first slider from the word TABS — 7px of the two. */
/* line-height 1: the label's BOX was 15px tall for 9px of text, and that
   leading — not any padding — was most of what sat between the two blue bars
   (19px, of which only 4px was air). */
.side-panel.collapsed .panel-content { display: none; }
.side-panel.collapsed .panel-rail { border-left: 0; }
.panel-content {
  flex: 1;
  min-width: 0;
  /* 10px sides, not 16: the two vertical .sp-sizer sliders take 16px off the
     panel's inner width and the content is what pays for them. Padding is the
     one part `min-width: 0` cannot shrink, so it is also the floor that decides
     how narrow the panel can go before anything is forced out of it. */
  padding: 8px 10px 18px;
  overflow-y: auto;
  overflow-x: hidden;   /* NEVER slide sideways — trackpad swipes dragged the panel */
}
.panel-rail {
  flex: 0 0 76px;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 10px 6px;
  gap: 6px;
  /* 🎨 …and for the rail beside it. Its own property rather than sharing the
     panel's: the two are different surfaces in every studio theme (the rail
     sits on --bg, the panel on --panel), and collapsing them into one colour
     would flatten a distinction the Host can see. */
  background: var(--rail-bg-color, var(--bg));
}
.rail-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px;
  font-size: 11px;
  border: none;
  background: transparent;
  /* 🎨 the Host's own colour for the tab rail (Style ▸ Side panel). The ACTIVE
     tab keeps its accent — that highlight is how you know where you are. */
  color: var(--rail-label-color, var(--muted));
  border-radius: 10px;
}
.rail-btn .rail-badge {
  position: absolute; top: 4px; right: 6px;
  min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box;
  background: var(--accent); color: #fff; font-size: 10px; font-weight: 700;
  line-height: 16px; text-align: center; border-radius: 9px;
}
.rail-btn .rail-icon { font-size: 20px; line-height: 1; }
.rail-btn:hover { background: var(--panel-2); border: none; color: var(--text); }
.rail-btn.active { background: var(--panel-2); color: var(--accent); }
.panel-section { padding: 14px 0; border-bottom: 1px solid var(--border); }
.panel-section:last-child { border-bottom: none; }
.panel-section h3 {
  margin: 0 0 10px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  /* 🎨 the Host's own colour for every panel heading (Style ▸ Side panel) */
  color: var(--panel-title-color, var(--muted));
}
.panel-section input[type=text], .panel-section select {
  width: 100%;
  margin: 0 0 8px;
}
.layout-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* `flex: 0 0`, not `1 1 44px`. Growing was why the buttons could never get out
   of the title's way: shrink the icon all you like and they just stretched back
   to fill the row, so the pair never fitted on one line and the strip spent two.
   A fixed 34px is what makes 8 buttons + the name 565px in a 595px strip — the
   measurement that decided this number is in the .layout-strip comment. */
.layout-btn {
  flex: 0 0 37px;
  width: 37px;
  padding: 4px 3px;
  color: var(--muted);
}
.layout-row { gap: 2px; }
.layout-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.lyt { display: block; width: 100%; height: 20px; }
.lyt rect { fill: none; stroke: currentColor; stroke-width: 1.6; }
.lyt circle { fill: currentColor; }
.lyt .scr { fill: currentColor; opacity: 0.28; stroke: currentColor; }

.file-label {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 8px;
}
.file-label input[type=file] {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  color: var(--text);
  width: 100%;
}
.inline-row { display: flex; gap: 8px; margin-bottom: 6px; }
.ctrl.small { font-size: 12px; padding: 7px 10px; flex: 1; }
.hint { font-size: 11px; color: var(--muted); margin: 4px 0 0; }
.panel-section input[type=range] { width: 100%; }

/* ─── Invite guests popup ─────────────────────────────────────────── */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  /* Popups taller than a phone screen scroll instead of pushing the ×
     close button off-screen (which made them impossible to close).
     NOTE: no -webkit-overflow-scrolling here — its compositing layer
     swallows taps inside the popup on iPhones (buttons went dead). */
  overflow-y: auto;
}
.modal { margin: auto; }   /* wins over centering when content overflows → scrollable from the top */
/* These open ON TOP of the schedule modal, so they need a higher stacking order. */
#add-dest-modal, #channel-modal { z-index: 200; }

/* Help & Guide inside the studio */
#help-modal { z-index: 220; }
#help-btn { font-size: 12.5px; padding: 5px 8px; line-height: 1; }   /* down with the rest of the cluster (Marcelo) */
.help-modal-card { width: min(940px, 94vw); height: 88vh; padding: 8px; display: flex; }
.help-frame { flex: 1; width: 100%; border: none; border-radius: 10px; background: var(--bg); }

/* Pricing modal */
.price-toggle { display: inline-flex; background: var(--panel-2); border-radius: 999px; padding: 4px; gap: 4px; margin: 6px 0 16px; }
.pt-btn { border: none; background: transparent; color: var(--muted); padding: 8px 18px; border-radius: 999px; font: inherit; font-size: 13px; cursor: pointer; }
.pt-btn.active { background: var(--tile); color: var(--text); }
.pricing-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
@media (max-width: 720px) { .pricing-grid { grid-template-columns: 1fr; } }
.plan-card { position: relative; border: 1px solid var(--border); border-radius: 12px; padding: 18px; background: var(--panel-2); display: flex; flex-direction: column; }
.plan-card.featured { border: 2px solid var(--accent); }
.plan-pop { position: absolute; top: -11px; left: 18px; background: var(--accent); color: #fff; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; }
.plan-name { font-size: 17px; font-weight: 700; }
.plan-tag { font-size: 12px; color: var(--muted); margin: 2px 0 12px; }
.plan-price { font-size: 26px; font-weight: 700; }
.plan-price span { font-size: 13px; font-weight: 400; color: var(--muted); }
.plan-billed { font-size: 12px; color: var(--muted); margin: 2px 0 14px; }
.plan-feats { list-style: none; padding: 0; margin: 0 0 14px; font-size: 13px; line-height: 1.9; color: var(--muted); flex: 1; }
.plan-feats li::first-letter { color: var(--accent); }
/* 💾 The storage point above the cards — reads as a claim, not a footnote, so it
   takes the accent border instead of the grey .hint treatment. */
.plan-storage-point { margin: 10px 0 2px; padding: 10px 14px; font-size: 13px; line-height: 1.6;
  border-inline-start: 3px solid var(--accent); border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 9%, transparent); }

/* Green room — device check before entering the studio */
.greenroom { display: flex; gap: 12px; margin: 14px 0 10px; }
.gr-preview-wrap { position: relative; flex: 1; aspect-ratio: 16/9; background: #000; border-radius: 12px; overflow: hidden; }
.gr-preview { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
.gr-cam-off { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--muted); background: var(--tile); }
.gr-name-chip { position: absolute; left: 10px; bottom: 10px; background: var(--accent); color: #fff; padding: 3px 11px; border-radius: 999px; font-size: 13px; font-weight: 700; }
.gr-side { width: 118px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 12px; padding: 10px; }
.gr-mic-icon { font-size: 20px; }
.gr-mic-bar { width: 14px; height: 84px; background: var(--tile); border-radius: 8px; overflow: hidden; display: flex; align-items: flex-end; }
.gr-mic-level { width: 100%; height: 0%; background: #22c55e; transition: height .06s linear; }
.gr-mic-status { font-size: 12px; color: var(--muted); text-align: center; }
.gr-controls { display: flex; gap: 8px; justify-content: center; margin-bottom: 4px;
  flex-wrap: wrap; max-width: 100%; }   /* tight panel → second centered row, never poking out */
.gr-controls .ctrl { width: auto; flex: 0 1 auto; min-width: 0; }

/* Visual-effects background picker */
.effects-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 12px; }
.effect-tile {
  position: relative; aspect-ratio: 16/9; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--border); background: var(--panel-2); color: var(--text);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 22px; background-size: cover; background-position: center;
}
.effect-tile.selected { outline: 2px solid var(--accent); outline-offset: -2px; }
.effect-tile .et-label {
  position: absolute; bottom: 0; left: 0; right: 0; padding: 3px 0;
  font-size: 11px; font-weight: 600; text-align: center;
  background: rgba(0,0,0,.45); color: #fff;
}
.et-del,
.effect-tile .et-del {
  position: absolute; top: 3px; right: 3px; width: 18px; height: 18px; line-height: 16px;
  border-radius: 50%; border: none; background: rgba(0,0,0,.6); color: #fff;
  font-size: 12px; cursor: pointer; padding: 0;
}
.effect-tile.et-add { border-style: dashed; font-weight: 600; font-size: 13px; flex-direction: column; gap: 2px; }

/* StreamYard-style date/time picker (Today / hour : minute / AM-PM) */
.sched-when-row { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
.sched-when-row select {
  padding: 11px 12px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--panel-2); color: var(--text); font: inherit; cursor: pointer;
  width: auto; min-width: 74px;   /* a global width:100% squeezed the DATE box to 28px */
}
.sched-when-row select:focus { outline: none; border-color: var(--accent); }
.sched-when-row .sched-date { flex: 1; min-width: 150px; }
.sched-colon { opacity: .6; font-weight: 700; }
.sched-thumb-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.sched-thumb-preview {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;   /* exactly how YouTube will crop it */
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--border);
  margin: 0 0 10px;
}

/* Required-field marker — the schedule form is long enough on a phone that
   Title scrolls out of sight, so it has to announce itself as required. */
.req { color: var(--danger); font-weight: 700; margin-left: 2px; }

/* Scheduled indicator in the studio header (near Go live) */
.sched-indicator { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
.si-edit {
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted); cursor: pointer;
  padding: 5px; border-radius: 8px; line-height: 0;
}
.si-edit:hover { color: var(--text); background: var(--panel-2); }
.si-body { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25; }
.si-badge { background: #5f6368; color: #fff; font-size: 13px; font-weight: 600; padding: 3px 12px; border-radius: 999px; }
.si-when { font-size: 13px; color: var(--muted); margin-top: 3px; }

/* Front-page "Upcoming streams" cards */
.stream-card {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--panel-2); margin-bottom: 8px;
}
/* status note = its own full-width row at the BOTTOM of the card */
.stream-note { flex-basis: 100%; font-size: 13px; margin: 2px 2px 0; }
.stream-thumb { width: 92px; height: 52px; object-fit: cover; border-radius: 8px; flex: none; background: var(--tile); }
.stream-thumb-ph { display: flex; align-items: center; justify-content: center; font-size: 22px; }
.stream-meta { flex: 1; min-width: 0; }
.stream-card .stream-title { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stream-sub { font-size: 12px; color: var(--muted); margin-top: 3px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.stream-badge { padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; }
.stream-badge.yt { background: rgba(255,0,0,.16); color: #ff5c5c; }
.stream-badge.plan { background: var(--tile); color: var(--muted); }
.stream-enter { flex: none; white-space: nowrap; }
.modal {
  position: relative;
  background: var(--panel);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 28px;
  width: 100%; max-width: 540px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.modal h2 { margin: 0 0 8px; font-size: 24px; }
.modal .hint { font-size: 14px; margin-bottom: 18px; line-height: 1.4; }
.modal-close {
  position: absolute; top: 10px; right: 10px;
  width: 44px; height: 44px; border-radius: 10px;   /* finger-size (Apple's minimum tap target) */
  border: 0; background: var(--panel-2); color: var(--text);
  font-size: 26px; cursor: pointer; line-height: 1;
}
/* iOS only delivers taps to elements that look clickable — without this the
   tap-outside-to-close dead zone does nothing on iPhone/iPad. */
.modal-backdrop { cursor: pointer; }
.modal { cursor: auto; }
.modal-close:hover { background: var(--panel-2); color: #fff; }
.invite-row { display: flex; gap: 10px; }
.invite-row input {
  flex: 1; padding: 12px 14px; border-radius: 10px;
  background: #0d1120; border: 1px solid rgba(255,255,255,0.12); color: #fff; font-size: 14px;
}
.invite-row .primary { width: auto; flex: none; padding: 12px 24px; white-space: nowrap; }
.invite-share { display: flex; gap: 22px; margin-top: 16px; }
.invite-share a {
  color: var(--accent); text-decoration: none; font-weight: 600; font-size: 14px; cursor: pointer;
}
.invite-share a:hover { text-decoration: underline; }
.modal textarea, .modal select, .modal input[type=datetime-local] {
  width: 100%; padding: 11px 13px; border-radius: 10px;
  background: #0d1120; border: 1px solid rgba(255,255,255,0.12); color: #fff; font: inherit; margin: 0;
}
.modal textarea { resize: vertical; min-height: 66px; }
.modal .file-label { display: block; margin-bottom: 12px; }

/* Connected YouTube channels as selectable destination avatars */
.yt-destinations { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-start; margin: 8px 0; }
/* 🎯 Picking a destination has to be unmistakable at a glance. The old state was
   a 0.55→1 opacity fade plus an --accent ring — and --accent is whatever colour
   the user themed their studio, so on an olive studio the "selected" ring was
   olive on olive: invisible. State now reads from three stacked signals that
   survive ANY theme: colour vs grey, a fixed-green ring, and a ✓ badge. */
.yt-avatar {
  width: 48px; height: 48px; border-radius: 50%; flex: none;
  border: 3px dashed var(--border); background: var(--panel-2); background-size: cover; background-position: center;
  color: #fff; font-weight: 700; font-size: 18px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
  filter: grayscale(1) brightness(0.65);       /* OFF: drained of colour, not merely faded */
  transition: filter .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.yt-avatar:hover { filter: grayscale(0.35) brightness(0.9); border-color: var(--muted); }
/* --success is NOT part of the studio theme (only --accent/--bg/--panel are), so
   this ring keeps its contrast whatever colour the studio is painted. */
.yt-avatar.selected {
  filter: none;
  border-style: solid;
  border-color: var(--success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 28%, transparent);
}
.yt-avatar.selected:hover { filter: none; border-color: var(--success); }
.yt-avatar.yt-add { border: 2px dashed rgba(255,255,255,0.3); background: transparent; color: var(--accent); filter: none; font-size: 22px; }
.yt-avatar.yt-add:hover { filter: none; border-color: var(--accent); }
.yt-avatar-wrap { position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 3px; max-width: 72px; }
.yt-avatar-name { font-size: 11px; color: var(--muted); text-align: center; max-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.yt-avatar-wrap.on .yt-avatar-name { color: var(--text); font-weight: 600; }
/* The ✓ tick — white on fixed green, ringed in the panel colour so it reads on
   top of any channel photo. */
.yt-tick {
  position: absolute; top: -3px; left: -3px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--success); color: #06281d; font-size: 12px; font-weight: 800; line-height: 1;
  border: 2px solid var(--panel); display: none; align-items: center; justify-content: center;
  pointer-events: none;
}
.yt-avatar-wrap.on .yt-tick { display: flex; }
/* The word, for people who shouldn't have to decode colour — and for anyone
   scanning ten channels fast. */
.yt-state {
  font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 1px 7px; border-radius: 999px; line-height: 1.5;
  border: 1px solid var(--border); color: var(--muted); background: transparent; white-space: nowrap;
  /* Safety net: a future translation longer than the 72px chip ellipsizes
     instead of spilling over its neighbour. (fr "À corriger" did exactly that.) */
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.yt-avatar-wrap.on .yt-state {
  color: #06281d; background: var(--success); border-color: var(--success);
}
/* Bottom-LEFT, which is the one corner of an avatar nothing else claims: the ✓
   is top-left, the ⚠️ reconnect badge top-right (and a FIX tile is precisely the
   one a Host wants to clear out), the 🔗 copy bottom-right. */
.yt-del {
  position: absolute; bottom: -4px; left: -4px; width: 20px; height: 20px; border-radius: 50%;
  border: 0; background: var(--danger); color: #fff; font-size: 13px; line-height: 1; cursor: pointer;
  display: none; align-items: center; justify-content: center;
}
.yt-avatar-wrap:hover .yt-del { display: flex; }
/* Armed: one more tap disconnects. Wider, so the change is felt as well as seen. */
.yt-del.armed { width: 26px; border-radius: 999px; font-weight: 800; }
.yt-del:disabled { opacity: .6; cursor: default; }
/* A touch screen has no hover to reveal it with, so the control simply was not
   reachable there — the same trap the rotate grip had. */
@media (hover: none) { .yt-avatar-wrap .yt-del { display: flex; } }
.yt-copy {
  position: absolute; bottom: -4px; right: -4px; width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--panel); background: #fff; font-size: 11px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.yt-copy:hover { filter: brightness(0.95); }

/* Tile name tag (name + optional headline) + avatar when camera is off */
.label-name { display: block; font-weight: 600; }
.label-headline { display: block; font-size: 13px; font-weight: 400; opacity: 0.85; margin-top: 1px; }
/* A vertical NAME turns the tag into two columns side by side — the name and
   the headline — instead of one long stack. Stacked they measured 410px for a
   240px tile, which is simply off the bottom of the picture (Marcelo: "put it
   next to it not under it… two lines not one long line"). */
#stage[data-nametag-layout="vertical"] .label {
  display: flex; flex-direction: row; align-items: flex-start; gap: 7px;
}
#stage[data-nametag-layout="vertical"] .label .label-headline { margin-top: 0; }
.placeholder.has-avatar { background-size: cover; background-position: center; color: transparent; }

/* ── Private chat — StreamYard-style panel ───────────────────────── */
.pchat-panel { display: flex; flex-direction: column; min-height: 100%; border-bottom: 0; }
.pchat-head { display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 12px; background: rgba(255,255,255,0.05); }
.pchat-head-icon { width: 42px; height: 42px; border-radius: 10px; background: rgba(79,140,255,0.15); display: flex; align-items: center; justify-content: center; font-size: 20px; flex: none; }
.pchat-head-title { font-weight: 700; font-size: 15px; }
.pchat-head-sub { font-size: 12px; opacity: 0.65; }
.pchat-list { flex: 1; display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.pchat-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 10px; padding: 30px 10px; }
.pchat-empty-icon { width: 44px; height: 44px; border: 2px dashed rgba(255,255,255,0.35); border-radius: 10px; }
.pchat-empty-title { font-weight: 700; font-size: 16px; }
.pchat-empty-sub { font-size: 13px; line-height: 1.5; opacity: 0.6; margin: 0; }
.pchat-compose-wrap { margin-top: auto; padding-top: 10px; }
.pchat-mention-hint { font-size: 12px; opacity: 0.6; margin: 0 0 6px 4px; }
.pchat-compose input { border: 1.5px solid var(--accent); background: transparent; }

/* ── Sound effects grid ──────────────────────────────────────────── */
.sfx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
.sfx-btn {
  padding: 10px 8px; border-radius: 10px; font-size: 13px; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--border); color: var(--text);
  transition: border-color .15s, transform .08s;
}
.sfx-btn:hover { border-color: var(--accent); }
.sfx-btn { position: relative; }
.sfx-remove { position: absolute; top: 2px; right: 2px; width: 17px; height: 17px; border-radius: 50%;
  background: #e11d48; color: #fff; font-size: 10px; line-height: 17px; text-align: center;
  opacity: 0; transition: opacity .15s; cursor: pointer; }
.sfx-btn:hover .sfx-remove { opacity: 1; }
.sfx-remove.confirm { opacity: 1; background: #ff9100; animation: sfxConfirmPulse 0.6s ease infinite alternate; }
.media-mini.confirm { background: #ff9100; color: #111; animation: sfxConfirmPulse 0.6s ease infinite alternate; }
@keyframes sfxConfirmPulse { from { transform: scale(1); } to { transform: scale(1.25); } }

/* Visual effects: canvas above clips (10) and loops, below chat (20) */
.vfx-canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 18; pointer-events: none; }
.vfx-btn, .la-btn {
  padding: 10px 6px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--panel-2, rgba(255,255,255,0.04)); color: var(--text);
  font-size: 13px; cursor: pointer; transition: border-color .15s, transform .1s;
}
.vfx-btn:hover, .la-btn:hover { border-color: var(--accent); }
.vfx-btn:active, .la-btn:active { transform: scale(.95); background: var(--accent); color: #fff; }
.vfx-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.la-btn.active { border-color: var(--accent); background: var(--accent); color: #fff; }
.sfx-btn:active { transform: scale(.95); background: var(--accent); color: #fff; }

/* ── Notes (host's private notepad) ──────────────────────────────── */
.notes-text {
  width: 100%; min-height: 260px; resize: vertical;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  color: var(--text); font: inherit; font-size: 14px; line-height: 1.5;
  padding: 12px;
}
.notes-text:focus { outline: none; border-color: var(--accent); }

/* ── Style presets ("My looks") ──────────────────────────────────── */
.preset-row { display: flex; gap: 8px; align-items: center; }
.preset-row select {
  flex: 1; min-width: 0; padding: 9px 10px; border-radius: 9px;
  background: var(--bg); border: 1px solid var(--border); color: var(--text); font: inherit; font-size: 14px;
}
.preset-row .ctrl.small { flex: none; }

/* ── "⋮" menu on people-tray cards ───────────────────────────────── */
.backstage-card { position: relative; }
.backstage-card .tile-menu-wrap { top: 10px; right: 10px; }
.backstage-card .tile-menu-btn { width: 26px; height: 26px; font-size: 15px; }
.backstage-card .tile-menu { min-width: 180px; }

/* ── StreamYard-style Comments panel ─────────────────────────────── */
/* Toggle switches (replace plain checkboxes) */
.switch-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 2px; font-size: 14px; cursor: pointer; margin: 0;
  color: var(--text);
}
.switch-row .switch-label { flex: 1; }
.switch-row input { position: absolute; opacity: 0; pointer-events: none; }
.switch {
  flex: none; width: 40px; height: 22px; border-radius: 999px;
  background: rgba(255,255,255,0.18); position: relative; transition: background .15s;
}
.switch::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%; background: #fff;
  transition: left .15s;
}
.switch-row input:checked + .switch { background: var(--accent); }
.switch-row input:checked + .switch::after { left: 20px; }

/* Intro card (like StreamYard's explainer card at the top of comments) */
.chat-intro {
  display: flex; gap: 10px; align-items: flex-start;
  background: rgba(255,255,255,0.06); border-radius: 12px;
  padding: 12px; font-size: 13px; line-height: 1.45; color: var(--muted);
  margin-bottom: 8px;
}
.chat-intro b { color: var(--text); }
.chat-intro-icon {
  flex: none; width: 34px; height: 34px; border-radius: 9px;
  background: rgba(79,140,255,0.18); display: flex; align-items: center; justify-content: center;
  font-size: 17px;
}

/* Softer, friendlier comment cards */
.chat-row { border-radius: 12px; padding: 10px 12px; gap: 10px; align-items: flex-start; }
.chat-row-av { width: 34px; height: 34px; font-size: 14px; margin-top: 1px; }
.chat-row-body { flex: 1; min-width: 0; }
.chat-row-top { display: flex; align-items: baseline; gap: 8px; }
.chat-row-name { color: var(--chat-color, var(--accent)); font-size: 13px; flex: 1; }
.chat-row-time { font-size: 11px; opacity: 0.5; flex: none; }
.chat-row-text { font-size: 14px; white-space: normal; overflow-wrap: anywhere; margin-top: 2px; }
.chat-row.super .chat-row-name { color: var(--success); }

/* ── Chat panel: slim top bar + full-height scrolling list ───────── */
.chatx-panel { display: flex; flex-direction: column; min-height: 100%; border-bottom: 0; }
.chatx-topbar {
  display: flex; align-items: center; gap: 8px;
  padding-bottom: 8px; border-bottom: 1px solid var(--border); margin-bottom: 8px;
}
.chatx-main-toggle { flex: 1; padding: 0; }
.chatx-gear {
  flex: none; width: 34px; height: 34px; padding: 0; border-radius: 9px;
  font-size: 16px; line-height: 1;
  background: transparent; border: 1px solid var(--border); color: var(--text);
}
.chatx-gear.open { background: var(--accent); border-color: var(--accent); color: #fff; }
.chatx-settings {
  background: rgba(255,255,255,0.04); border: 1px solid var(--border);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 8px;
}
.chatx-list { flex: 1; overflow-y: auto; min-height: 120px; }
.chatx-panel .chat-compose { margin-top: auto; padding-top: 10px; }

/* ── Drag tiles to rearrange the panel ───────────────────────────── */
.tile.dragging { opacity: 0.5; }
.tile.drag-over { outline: 3px dashed var(--accent); outline-offset: -3px; }
.tile[draggable="true"] { cursor: grab; }
.tile[draggable="true"]:active { cursor: grabbing; }

/* ── Side panel — StreamYard-style foldable headings + corner placement ──
   EVERY heading folds, on every tab. The state lives on the HEADING, not on the
   .panel-section, because Style stacks five headings in one section and
   Schedule two: folding has to stop at the next <h3>, not swallow the tab. */
.panel-section h3.sec-head {
  cursor: pointer; display: flex; align-items: center; gap: 8px; user-select: none;
}
/* A pseudo-element, not an appended <span>: applyLanguage() assigns textContent
   to every [data-i18n] heading and wiped the span, which is why only the
   headings that carry their data-i18n on an inner span still had a chevron. */
.panel-section h3.sec-head::after {
  content: ''; margin-left: auto; width: 9px; height: 9px; flex: none;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); transition: transform .15s; margin-top: -4px;
}
.panel-section h3.sec-head.folded::after { transform: rotate(-45deg); margin-top: 2px; }
/* !important, like .hidden: a folded row can carry a display of its own from a
   class or an inline style, and the fold has to win over both. */
.sec-folded { display: none !important; }

.pos-row { display: grid; grid-template-columns: 34px 34px; gap: 5px; }
.pos-btn {
  position: relative; width: 34px; height: 26px; flex: none; padding: 0;
  border-radius: 7px; background: var(--bg); border: 1px solid var(--border); cursor: pointer;
}
.pos-btn span { position: absolute; width: 11px; height: 8px; border-radius: 2px; background: var(--muted); }
.pos-btn[data-pos="tl"] span { top: 4px; left: 4px; }
.pos-btn[data-pos="tr"] span { top: 4px; right: 4px; }
.pos-btn[data-pos="bl"] span { bottom: 4px; left: 4px; }
.pos-btn[data-pos="br"] span { bottom: 4px; right: 4px; }
.pos-btn.active { border-color: var(--accent); }
.pos-btn.active span { background: var(--accent); }

/* ── "Present" menu (StreamYard-style) ───────────────────────────── */
.present-menu {
  position: fixed; z-index: 400;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 14px; padding: 8px; min-width: 215px;
  display: flex; flex-direction: column;
  box-shadow: 0 14px 40px rgba(0,0,0,0.5);
}
.present-menu.hidden { display: none; }
.pm-item {
  display: flex; align-items: center; gap: 12px;
  background: transparent; border: 0; color: var(--text); text-align: left;
  padding: 11px 12px; border-radius: 9px; font: inherit; font-size: 14px; cursor: pointer;
}
.pm-item:hover { background: var(--tile); }
.pm-icon { width: 22px; text-align: center; flex: none; }
.pm-sep { height: 1px; background: var(--border); margin: 6px 4px; }

/* ── Mic & camera settings modals ────────────────────────────────── */
.mic-meter { height: 10px; border-radius: 999px; background: rgba(255,255,255,0.1); overflow: hidden; }
.mic-meter-fill { height: 100%; width: 0%; background: var(--success); transition: width .05s linear; }
.device-list { display: flex; flex-direction: column; gap: 6px; }
.device-btn {
  text-align: left; background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 9px; padding: 10px 12px; color: var(--text); font-size: 14px; cursor: pointer;
}
.device-btn:hover, .device-btn.active { border-color: var(--accent); }
.modal h3 { font-size: 14px; margin: 14px 0 8px; }

/* ─── ⚙️ Settings modal — one account preference per group, separated ─── */
.gs-group + .gs-group {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.gs-group h3 { margin-top: 0; }
.gs-group select { width: 100%; }
/* The zone list is long — keep the picker readable rather than letting the
   longest "America/Argentina/…" label stretch the modal. */
#gs-tz { max-width: 100%; }
#gs-tz-now { font-variant-numeric: tabular-nums; }

/* Mirror only your own preview — everyone else still sees you normally. */
#stage.mirror-self video.self-video { transform: scaleX(-1); }

/* Guest page mirror (same idea as the host's) */
#grid.mirror-self video.self-video { transform: scaleX(-1); }

/* Invite modal extras */
.invite-help-link { color: var(--accent); }

/* ── Team (admins) card on the dashboard ─────────────────────────── */
.team-row {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 12px; margin-bottom: 6px;
}
.team-name { flex: 1; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Clip playback progress (StreamYard-style) ───────────────────── */
.clip-progress {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px;
  z-index: 11; width: min(60%, 480px);
  display: flex; align-items: center; gap: 10px;
  background: rgba(0,0,0,0.65); border-radius: 999px; padding: 8px 16px;
}
.clip-progress.hidden { display: none; }
.cp-bar { flex: 1; height: 6px; border-radius: 999px; background: rgba(255,255,255,0.25); overflow: hidden; }
.cp-fill { height: 100%; width: 0%; background: var(--accent); transition: width .2s linear; }
.cp-time { font-size: 12px; color: #fff; flex: none; font-variant-numeric: tabular-nums; }

/* Mirrored self-preview on the setup screen */
.gr-preview.mirror-video { transform: scaleX(-1); }

/* ── Members table (clean StreamYard-style) ──────────────────────── */
.team-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.team-head-right { display: flex; align-items: center; gap: 12px; }
.team-table {
  margin-top: 12px; border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
}
.team-thead, .team-trow {
  display: grid; grid-template-columns: 1fr 90px 110px;
  align-items: center; gap: 8px; padding: 10px 14px;
}
.team-thead {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); background: var(--panel-2); padding: 8px 14px;
}
.team-trow { border-top: 1px solid var(--border); }
.team-trow .team-name { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.team-role { font-size: 13px; color: var(--muted); }
.team-role.host { font-weight: 700; color: var(--text); }
.team-role.pending { font-style: italic; }
.team-trow .ctrl.small { justify-self: end; padding: 6px 10px; font-size: 12px; }

/* ── Clip card: live playback progress (badge counts + thin bar) ─── */
.clip-cardbar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
  background: rgba(255,255,255,0.18); opacity: 0; transition: opacity .15s;
}
.clip-cardbar-fill { height: 100%; width: 0%; background: var(--accent); transition: width .2s linear; }
.clip-card.playing .clip-cardbar { opacity: 1; }
.clip-card.playing .clip-thumb { box-shadow: inset 0 0 0 2px var(--accent); }

/* Tray-card "⋮" menus open UPWARD so they're never cut off at the bottom. */
.backstage-card .tile-menu { top: auto; bottom: calc(100% + 6px); right: 0; }

/* ── Invite QR — scan with the phone camera, no typing ───────────── */
.invite-qr-wrap { margin-top: 14px; display: flex; flex-direction: column; align-items: center; }
.invite-qr {
  background: #fff; padding: 12px; border-radius: 12px;
  width: 172px; height: 172px; display: flex; align-items: center; justify-content: center;
}
.invite-qr svg { width: 148px; height: 148px; display: block; }
.invite-qr:empty { display: none; }


/* ─── PHONE / SMALL-SCREEN STUDIO (old-StreamYard-mobile style):
   everything stacks vertically — stage on top, controls, then the panels
   FULL-WIDTH with the tab icons as a horizontal bar. Chat gets the whole
   screen width and is actually readable. ─────────────────────────────── */
/* Phone layout is for NARROW screens only. A short-but-wide window (browser
   sharing bars, half-height windows, fullscreen on wide monitors) keeps the
   desktop layout — the 16:9 stage simply scales down, StreamYard-style. */
@media (max-width: 900px) {
  #studio { padding: 10px; }
  .studio-layout { flex-direction: column; gap: 12px; }
  .stage-col { width: 100%; }
  .stage { width: 100% !important; min-width: 0; }   /* vertical layout scrolls; no desktop fit */
  .side-panel {
    width: 100%;
    flex: none;
    max-height: none;
    flex-direction: column-reverse;   /* tabs on top, content below */
  }
  .panel-rail {
    flex: none;
    flex-direction: row;
    justify-content: space-around;
    border-left: 0;
    border-bottom: 1px solid var(--border);
    padding: 6px;
    overflow-x: auto;
  }
  .panel-content { max-height: 62vh; overflow-y: auto; }
  header { flex-wrap: wrap; gap: 8px; }
  .controls { flex-wrap: wrap; }
}


/* Expired YouTube connection: red dashed ring + ⚠️ badge, click reconnects */
.yt-avatar.reconnect { outline: 2px dashed #e11d48; outline-offset: 2px; }
.yt-avatar-wrap { position: relative; }
.yt-reconnect-badge { position: absolute; top: -6px; right: -6px; font-size: 14px; pointer-events: none; }


/* The Create/Edit live-stream window is the star dialog — give it room so
   the 16:9 thumbnail preview and the form breathe. */
#schedule-modal .modal { max-width: 760px; }


/* Avatar wardrobe thumbs: fixed squares, x overlaid on the corner */
#avatar-lib-row { display: flex; flex-wrap: wrap; gap: 10px; }
#avatar-lib-row .bg-thumb { width: 72px; height: 72px; flex: none; background-size: cover; background-position: center; }
#avatar-lib-row .bg-none { width: auto; padding: 0 18px; }
#avatar-lib-row > div { position: relative; }


/* Avatar wardrobe — simple and self-explaining */
.avatar-current { text-align: center; margin: 6px 0 2px; }
.avatar-big {
  width: 110px; height: 110px; border-radius: 50%;
  background-size: cover; background-position: center;
  margin: 0 auto; border: 3px solid var(--accent);
}
.avatar-big.avatar-letter {
  display: flex; align-items: center; justify-content: center;
  font-size: 44px; font-weight: 700; background: var(--tile); color: var(--muted);
}
#avatar-lib-row { display: flex; flex-wrap: wrap; gap: 14px; min-height: 40px; }
.avatar-item { position: relative; }
.avatar-thumb {
  width: 76px; height: 76px; border-radius: 50%;
  background-size: cover; background-position: center;
  border: 3px solid transparent; cursor: pointer; padding: 0;
}
.avatar-item.wearing .avatar-thumb { border-color: var(--accent); }
.avatar-check {
  position: absolute; right: -2px; bottom: -2px;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--accent); color: #fff; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.avatar-item .et-del { top: -4px; right: -4px; }


/* Edit avatar — StreamYard-style tile grid */
#avatar-lib-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }
.avatar-tile {
  width: 86px; height: 86px; border-radius: 14px;
  background-size: cover; background-position: center;
  background-color: var(--tile);
  border: 3px solid transparent; cursor: pointer; padding: 0;
  font-size: 34px; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
}
.avatar-tile.wearing { border-color: var(--accent); }
.avatar-add-tile { border: 2px dashed var(--border); font-size: 30px; }
.avatar-item { position: relative; }
.avatar-item .et-del { top: -6px; right: -6px; opacity: 0; transition: opacity .15s; }
.avatar-item:hover .et-del { opacity: 1; }


/* Logos are free boxes too — drag anywhere, reshape from all corners */
.logo-box {
  position: absolute;
  z-index: 12;
  cursor: grab;
  pointer-events: auto;   /* the legacy .logo-overlay class disables it */
}
.logo-box:active { cursor: grabbing; }
.logo-box img {
  width: 100%;
  height: 100%;
  object-fit: fill;   /* the logo takes the shape you sculpt */
  pointer-events: none;
  display: block;
}
.logo-box:hover .resize-handle { opacity: 0.9; }
.chat-overlay:hover .resize-handle { opacity: 0.9; }   /* the stage chat reshapes like a logo */

/* Stage chat on-screen size — Small / Medium (default) / Large */
.chat-overlay[data-size="small"] .chat-text { font-size: 11px; }
.chat-overlay[data-size="small"] .chat-author { font-size: 10px; }
.chat-overlay[data-size="small"] .chat-avatar { width: 18px; height: 18px; font-size: 9px; }
.chat-overlay[data-size="small"] .sc-author,
.chat-overlay[data-size="small"] .sc-amount,
.chat-overlay[data-size="small"] .sc-text { font-size: 11px; }
.chat-overlay[data-size="small"] .sc-avatar { width: 20px; height: 20px; }
.chat-overlay[data-size="large"] .chat-text { font-size: 20px; }
.chat-overlay[data-size="large"] .chat-author { font-size: 18px; }
.chat-overlay[data-size="large"] .chat-avatar { width: 34px; height: 34px; font-size: 16px; }
.chat-overlay[data-size="large"] .sc-author,
.chat-overlay[data-size="large"] .sc-amount,
.chat-overlay[data-size="large"] .sc-text { font-size: 19px; }
.chat-overlay[data-size="large"] .sc-avatar { width: 38px; height: 38px; }


/* Your avatar in the green-room preview when the camera is off */
.gr-avatar {
  width: 96px; height: 96px; border-radius: 50%;
  background-size: cover; background-position: center;
  border: 3px solid var(--accent);
}


/* Pin an avatar onto the stage as a free sticker */
.avatar-pin {
  position: absolute; top: -6px; left: -6px;
  width: 24px; height: 24px; border-radius: 50%;
  border: none; background: var(--panel-2); font-size: 12px;
  cursor: pointer; padding: 0; opacity: 0; transition: opacity .15s;
}
.avatar-item:hover .avatar-pin { opacity: 1; }
.avatar-pin.pinned { opacity: 1; background: var(--accent); }


/* My camera-off tile as a scene: avatars inside, each its own free box */
.placeholder.avatar-scene { position: relative; overflow: hidden; }
.tile-avatar-box {
  position: absolute;
  cursor: grab;
  z-index: 2;
}
.tile-avatar-box:active { cursor: grabbing; }
.tile-avatar-box img {
  width: 100%;
  height: 100%;
  object-fit: cover;  /* takes the shape you sculpt by cropping — the face never squishes */
  pointer-events: none;
  display: block;
  border-radius: 8px;
}
/* inside the small tile the box often covers the whole tile — keep every grip
   fully inside so nothing is clipped by the tile's overflow */
.tile-avatar-box .resize-handle.rh-t { top: 2px; }
.tile-avatar-box .resize-handle.rh-b { bottom: 2px; }
.tile-avatar-box .resize-handle.rh-l { left: 2px; }
.tile-avatar-box .resize-handle.rh-r { right: 2px; }
.tile-avatar-box .resize-handle.rh-tl { top: 2px; left: 2px; }
.tile-avatar-box .resize-handle.rh-tr { top: 2px; right: 2px; }
.tile-avatar-box .resize-handle.rh-bl { bottom: 2px; left: 2px; }
.tile-avatar-box .resize-handle.rh-br { bottom: 2px; right: 2px; }
.tile-avatar-box:hover .resize-handle { opacity: 0.9; }
.tile-avatar-box .resize-handle { width: 8px; height: 8px; }   /* finer handles in the small canvas */

/* wardrobe: the "my tile" button, bottom-left of each avatar */
.avatar-scene-btn {
  position: absolute; bottom: -6px; left: -6px;
  width: 24px; height: 24px; border-radius: 50%;
  border: none; background: var(--panel-2); font-size: 12px;
  cursor: pointer; padding: 0; opacity: 0; transition: opacity .15s;
}
.avatar-item:hover .avatar-scene-btn { opacity: 1; }
.avatar-scene-btn.inscene { opacity: 1; background: var(--accent); }


/* Edge handles — mid-side grips that change ONLY width or ONLY height */
.resize-handle.rh-edge { background: var(--accent); border-radius: 4px; }
.resize-handle.rh-t { top: -2px; left: 50%; transform: translateX(-50%); width: 14px; height: 6px; cursor: ns-resize; right: auto; bottom: auto; }
.resize-handle.rh-b { bottom: -2px; left: 50%; transform: translateX(-50%); width: 14px; height: 6px; cursor: ns-resize; right: auto; top: auto; }
.resize-handle.rh-l { left: -2px; top: 50%; transform: translateY(-50%); width: 6px; height: 14px; cursor: ew-resize; right: auto; bottom: auto; }
.resize-handle.rh-r { right: -2px; top: 50%; transform: translateY(-50%); width: 6px; height: 14px; cursor: ew-resize; left: auto; bottom: auto; }

/* the LIVE duration counter — steady digits, no jitter */
#live-timer { font-variant-numeric: tabular-nums; margin-left: 4px; opacity: 0.95; }

/* stream cards: live state + delete */
.stream-badge.live { background: #e53935; color: #fff; animation: livepulse 1.6s ease-in-out infinite; }
.stream-card.live-now { border: 1px solid #e53935; }
.stream-del { flex: none; }
@keyframes livepulse { 50% { opacity: 0.75; } }

/* pinned SUPER CHAT — one solid YouTube-style money card */
#comment-lower.sc-card {
  border-radius: 16px;
  padding: 12px 18px;
}
#comment-lower.sc-card .cl-bubble { margin-left: 0; }
#comment-lower.sc-card .cl-name,
#comment-lower.sc-card .cl-text { border-radius: 0 !important; padding-left: 6px; }
#comment-lower.sc-card .cl-text { font-weight: 600; }

/* StreamYard app-mode v2: page locked, panels scroll internally.
   #studio gets its exact height from JS (lockStudioHeight) — no flex
   restructuring of <body>, so nothing shifts. */
body.studio-open footer { display: none; }
@media (min-width: 901px) {
  body.studio-open { overflow: hidden; }
  body.studio-open #studio { overflow: hidden; }
  body.studio-open .studio-layout { height: 100%; }
  body.studio-open .stage-col { height: 100%; overflow-y: auto; overflow-x: hidden; }
  body.studio-open .side-panel { height: 100%; overflow-y: auto; }
}

/* Display-size control (under the layout row) */
/* margin-top 6px, not 10: with the strip a grid, this margin and the grid's
   own 6px row-gap were the whole 13px between the green slider and the word
   SIDE PANEL — the gaps inside the 📐 stack are down to 2px and have nothing
   left to give. */
/* align-self: end — the buttons make row 1 taller than this row, and with the
   grid centring it the leftover slack sat UNDER the green slider as a 13px gap
   above SIDE PANEL. Pinning it to the bottom of its own row is what actually
   closes that space; the margin-top I first reached for only moved the whole
   row and left the gap alone (it grew by 1px). The gaps inside the 📐 stack are
   already 2px and have nothing left to give. */
/* Contents stay CENTRED. Aligning them to the bottom reclaimed the 7px of air
   the taller ⛶ button leaves under the slider and took the gap 10px → 5px —
   too much: "not a lot, maybe 10%". */
.display-size { display: flex; align-items: center; justify-content: flex-start; gap: 10px; margin-top: 6px; min-width: 0; align-self: end; }
/* (The studio's backstage-size slider left this header for the panel's bottom
   edge — see `.backstage > .bs-size`. The guest page keeps its own in .sv-head.) */
/* Guest studio: the same sliders sit at the end of the section headings. */
.sv-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sv-head .bs-size { margin: 0 0 0 auto; }
.sv-head .bs-size input[type="range"] { width: min(160px, 30vw); }
/* Guest program monitor width follows its slider (StageView refits itself). */
#stage-view { width: var(--gsv-w, 100%); max-width: 100%; margin: 0 auto; }
/* 100% of its own cell, not 40vw: in the strip's grid the cell can be much
   narrower than the viewport, and a fixed 40vw simply overflowed it. */
.display-size input[type="range"] { width: min(260px, 100%); accent-color: var(--accent); }
.display-size .ds-ico { color: var(--muted); font-size: 13px; }
/* The ⛶ button KEEPS .ctrl's flex:1 and stretches into whatever slack the row
   has — I "fixed" that as a regression and Marcelo asked for it back: "you made
   it before longer, this I like it". A wide, easy target beats a 32px square,
   and it is his call, not mine. */
.display-size #fullscreen-btn { width: auto; padding: 6px 10px; }

/* 🖥 …and the same row as its own bar flush UNDER the stage, exactly as wide as
   the stage (Marcelo: "align with the line of the bottom" — it was floating on
   the picture and he wants it out of it). placeStageSizeBar() matches the width,
   because the stage's is computed and CSS cannot pass that number sideways.
   In the flow on purpose: it costs its own height instead of covering the
   layout buttons, and it never overlaps the stage rectangle, so no capture API
   can pick it up. The studio's own chrome, so it repaints with the theme. */
/* 📐 THE THIN SIZE STRIP — ONE definition, two surfaces: the stage's
   (#stage-size-bar) and the backstage tray's (.backstage > .bs-size). A strip
   along the bottom of whatever it sizes, not a panel: every part is sized to the
   row rather than the row to its parts. 42px → 16px → 13px over two passes of
   "thinner" (Marcelo), and the ⛶ button — the tallest child, so the one that
   SETS the height — keeps trading height for width so the target stays honest
   (32×30 → 45×11: a third of the height, half again the width). Touch gets its
   real size back in the phone block below. Change the metrics HERE; a surface
   overrides only its own chrome and placement. */
.ds-thinbar {
  align-self: auto; max-width: 100%;
  padding: 0 8px; gap: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 11px;
}
.ds-thinbar input[type="range"] { flex: 1 1 auto; width: auto; min-width: 0; height: 7px; margin: 0; }
.ds-thinbar .ds-ico { font-size: 9px; line-height: 1; }
.ds-thinbar #fullscreen-btn {
  flex: 0 0 auto; padding: 0 18px; height: 11px; min-height: 0;
  font-size: 9px; line-height: 1;
}
.ds-thinbar[hidden] { display: none; }
/* The stage's: centred under the stage, width matched by placeStageSizeBar(). */
.ds-onstage { margin: 0 auto; }
/* The backstage tray's: a header strip INSIDE its panel, bleeding past the 16px
   side padding to reach the border and taking the panel's own top corners
   (Marcelo flipped it up from the bottom). --panel-2, because --panel on --panel
   would be an invisible bar. */
.backstage > .bs-size {
  margin: -9px -16px 10px; max-width: none; padding: 0 16px;
  background: var(--panel-2);
  border: 0; border-bottom: 1px solid var(--border);
  border-radius: 11px 11px 0 0;
}
/* The ONE case where it does lie on the picture: the DJ booth monitor leaves the
   flow and gets dragged around, so the bar goes fixed and rides its bottom edge.
   z 502 clears the monitor's 500, the same step the prompter takes to 501; the
   scrim earns its keep here because the stage is under it. Only the controls
   may take a click, or the monitor beneath them goes dead. */
body.dj-monitor .ds-onstage {
  position: fixed; z-index: 502; margin: 0; pointer-events: none;
  background: rgba(8, 10, 20, 0.5);
  border-color: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
}
body.dj-monitor .ds-onstage > * { pointer-events: auto; }
body.dj-monitor .ds-onstage .ds-ico { color: rgba(255, 255, 255, 0.82); }
/* 📱 Stacked layout: plain row under the stage, no bar chrome to spend width on.
   placeStageSizeBar() drops the matched width here so it simply fills. */
@media (max-width: 900px), (max-height: 540px) {
  .ds-onstage {
    margin: 8px 0 0; padding: 0; gap: 10px; background: none; border: 0;
  }
  /* A 14px-tall button is a mouse target, not a thumb one. The thin strip is a
     desktop trade; a phone gets the real control back. */
  .ds-thinbar input[type="range"] { height: auto; }
  .ds-thinbar .ds-ico { font-size: 13px; }
  .ds-thinbar #fullscreen-btn { padding: 6px 14px; height: auto; font-size: 12px; }
  body.dj-monitor .ds-onstage {
    position: static; backdrop-filter: none; -webkit-backdrop-filter: none;
  }
}

/* ───────────────── DJ MIXER ───────────────── */
.dj-mixer {
  position: relative;
  display: grid;
  /* FIXED-SHAPE mixer: the three columns are locked so the whole mixer keeps its shape.
     It's sized to its content and scaled as ONE unit (zoom in JS = fitMixer) so it just
     grows/shrinks with the window instead of the decks stretching out of proportion (Marcelo). */
  grid-template-columns: 470px 440px 470px;
  align-items: stretch;   /* columns share a height; the bottom POWER/FILTER XY pad fills the deck's leftover space (Marcelo) */
  gap: 12px;
  width: fit-content;
  margin: 12px auto;
  padding: 14px 14px 6px;   /* slim bottom lip — the equalizers reach the edge (Marcelo) */
  border-radius: 16px;
  background:
    radial-gradient(120% 140% at 50% 0%, rgba(155,107,255,.16), transparent 60%),
    #14091c;
  border: 1px solid rgba(255,90,170,.35);
  box-shadow: 0 0 24px rgba(255,60,150,.18), inset 0 0 40px rgba(120,40,120,.15);
}
.dj-mixer.hidden { display: none; }
/* 2 rows: the two decks on top, their 180°-rotated LIVE TWINS on the bottom; the centre
   column spans both rows (the "camera duplicate controls" idea — Marcelo). */
.dj-mixer { grid-template-rows: auto auto; }
.dj-mixer > .dj-deck[data-deck="a"] { grid-column: 1; grid-row: 1; }
.dj-mixer > .dj-deck[data-deck="b"] { grid-column: 3; grid-row: 1; }
.dj-mixer > .dj-center { grid-column: 2; grid-row: 1; }   /* centre stays its natural height in the top row (so the 📺 Stage button doesn't stretch huge) */
/* ── CDJ-3000-STYLE bottom decks (original design, every control wired to the real engine) ── */
.cdj-row { grid-column: 1 / 4; grid-row: 2; display: flex; gap: 14px; align-items: stretch; min-width: 0; }
/* DJ PRO / global controls (SPLIT · Stage · SPEED · 6 DECKS · REC · BPM) as a full-width
   footer UNDER the CDJ decks, kept centred so it looks the same, just relocated (Marcelo). */
.dj-globalbar { grid-column: 1 / 4; grid-row: 3; margin: 12px 0 0; display: flex; gap: 18px; align-items: stretch; justify-content: center; flex-wrap: wrap; }
/* the centre stack wraps 2–3 controls per line instead of one tall column
   (Marcelo: "reduce this space") */
.dj-gb-main { flex: 0 0 auto; width: 620px; max-width: 100%; display: flex; flex-wrap: wrap;
  justify-content: center; align-items: center; align-content: center; gap: 6px 12px; }
.dj-gb-main > * { margin: 0 !important; }
.dj-globalbar > .stage-eq-ctl { flex: 1 1 320px; max-width: 460px; margin-top: 0; }
.dj-gb-flank { flex: 1 1 320px; max-width: 460px; }
.dj-gb-flank > .stage-eq-ctl { width: 100%; max-width: none; flex: 0 0 auto; }
/* the VU meters grow to fill whatever height the flanks have */
.dj-gb-flank .dj-vu { flex: 0 0 34px; height: 100%; min-height: 200px; width: 34px; }
/* 🌈 spectrum + VU side by side, filling the flank to the last pixel */
.dj-vu-row { display: flex; gap: 8px; flex: 1 1 auto; align-self: stretch; align-items: stretch; min-height: 200px; }
.dj-spec { flex: 1 1 auto; height: 100%; min-height: 200px; min-width: 0; border-radius: 8px;
  background: #05080a; border: 1px solid rgba(255,255,255,.14); box-shadow: inset 0 2px 8px rgba(0,0,0,.6); }
.cdj-deck { --cdj-accent: #ff8a3d;   /* per-deck colour (set inline by JS, independent per deck) — tints the WHOLE deck body */
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; padding: 14px; border-radius: 16px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--cdj-accent) 88%, #ffffff 12%), color-mix(in srgb, var(--cdj-accent) 82%, #000000 18%));
  border: 1px solid color-mix(in srgb, var(--cdj-accent) 50%, rgba(255,255,255,.08));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 6px 20px rgba(0,0,0,.4), 0 0 26px color-mix(in srgb, var(--cdj-accent) 28%, transparent); }
.cdj-screen { background: #05060a; border: 1px solid rgba(120,150,220,.18); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 7px; box-shadow: inset 0 2px 12px rgba(0,0,0,.8); }
.cdj-scrhead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.cdj-title { flex: 1; min-width: 0; font-size: 14px; font-weight: 800; color: #eaf0ff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cdj-dsel { flex: 0 0 auto; font-size: 10px; font-weight: 800; letter-spacing: .06em; color: #ffd24a; background: rgba(255,210,74,.12); border: 1px solid rgba(255,210,74,.4); border-radius: 6px; padding: 3px 8px; cursor: pointer; white-space: nowrap; }
.cdj-dsel:hover { background: rgba(255,210,74,.22); }
.cdj-bpm { font-size: 12px; color: #7fdfff; font-weight: 800; white-space: nowrap; }
.cdj-bpm b { font-size: 20px; }
.cdj-bpm i { font-size: 9px; opacity: .7; margin-left: 2px; font-style: normal; }
.cdj-ovbox { position: relative; height: 30px; width: 100%; }
.cdj-wavebox { position: relative; height: 92px; width: 100%; background: #070810; border-radius: 6px; overflow: hidden; }
.cdj-ov, .cdj-wave { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.cdj-scrfoot { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.cdj-time-el { font-size: 20px; font-weight: 800; color: #fff; font-variant-numeric: tabular-nums; }
.cdj-time-rem { font-size: 15px; font-weight: 800; color: #9fb0d0; font-variant-numeric: tabular-nums; }
.cdj-badges { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; }
.cdj-b { font-size: 8px; font-weight: 800; letter-spacing: .04em; padding: 2px 5px; border-radius: 4px; color: #5b6478; background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.06); }
.cdj-b.on { color: #06121f; background: linear-gradient(180deg, #2dd4ff, #147fb8); border-color: transparent; box-shadow: 0 0 8px rgba(45,212,255,.6); }
.cdj-b-loop.on { background: linear-gradient(180deg, #39e75f, #17a83b); }
.cdj-b-master.on { background: linear-gradient(180deg, #ff9d3d, #d9660f); }
.cdj-b-slip.on, .cdj-b-quant.on { background: linear-gradient(180deg, #ffcc4a, #d98f10); }
.cdj-hotcues { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.cdj-hc { min-height: 36px; border-radius: 8px; border: 2px solid rgba(255,255,255,.12); background: #0e0f16; color: #8b93a8; font-weight: 800; cursor: pointer; position: relative; transition: transform .08s, box-shadow .12s; }
.cdj-hc span { font-size: 13px; }
.cdj-hc.set { color: #fff; border-color: var(--hc, #ff5aa0); box-shadow: 0 0 12px color-mix(in srgb, var(--hc, #ff5aa0) 55%, transparent), inset 0 0 10px color-mix(in srgb, var(--hc, #ff5aa0) 22%, transparent); background: color-mix(in srgb, var(--hc, #ff5aa0) 16%, #0e0f16); }
.cdj-hc.hit, .cdj-hc:active { transform: scale(.94); }
.cdj-mid { display: flex; gap: 12px; align-items: center; margin-top: -40px; }   /* centre the bigger wheel against the button stack; pulled up to close the gap under HOTCUES (Marcelo) */
/* Jog wheel — 40% bigger (190→266). The ring canvas + hub scale automatically with it. */
.cdj-jog { position: relative; flex: 0 0 auto; width: 266px; height: 266px; border-radius: 50%; cursor: grab; touch-action: none;
  background: radial-gradient(circle at 50% 40%, #2a2c38, #0c0d12 72%);
  box-shadow: inset 0 4px 14px rgba(0,0,0,.8), 0 4px 16px rgba(0,0,0,.5), 0 0 22px color-mix(in srgb, var(--cdj-accent) 32%, transparent); }
.cdj-jog:active, .cdj-jog.scratching { cursor: grabbing; }
.cdj-jog-face { position: absolute; inset: 11px; border-radius: 50%; will-change: transform; box-shadow: inset 0 0 20px rgba(0,0,0,.7);
  background: repeating-conic-gradient(from 0deg, color-mix(in srgb, var(--cdj-accent) 22%, #22242f) 0deg 4deg, #14151c 4deg 8deg); }
.cdj-jog-ring { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
/* Centre display — 20% bigger (hub + text) */
.cdj-jog-hub { position: absolute; inset: 33px; border-radius: 50%; background: radial-gradient(circle at 50% 35%, #12141d, #05060a 78%); border: 1px solid rgba(255,255,255,.1); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; pointer-events: none; text-align: center; }
.cdj-jh-deck { font-size: 19px; font-weight: 800; letter-spacing: .1em; color: #8f9ab5; }
.cdj-jh-time { font-size: 39px; font-weight: 800; color: #eaf0ff; font-variant-numeric: tabular-nums; }
.cdj-jh-stat { font-size: 16px; font-weight: 800; letter-spacing: .12em; color: var(--cdj-accent); }
.cdj-jh-stat.onair { color: #ff3b3b; text-shadow: 0 0 8px rgba(255,59,59,.75); }
/* ON AIR — the platter glows red (broadcast pulse) when the deck is live to the audience */
@keyframes cdjOnAir {
  0%, 100% { box-shadow: inset 0 4px 14px rgba(0,0,0,.8), 0 4px 16px rgba(0,0,0,.5), 0 0 20px 2px rgba(255,45,45,.55); }
  50%      { box-shadow: inset 0 4px 14px rgba(0,0,0,.8), 0 4px 16px rgba(0,0,0,.5), 0 0 34px 6px rgba(255,45,45,.92); }
}
.cdj-jog.onair { animation: cdjOnAir 1.6s ease-in-out infinite; }
.cdj-side { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.cdj-transport { display: flex; gap: 8px; position: relative; top: 61px; margin-bottom: 61px; }   /* separates it from the row above; margin-bottom reserves the space so it doesn't collide with VOL below (Marcelo) */
.cdj-cue, .cdj-play { flex: 1; min-height: 46px; border-radius: 10px; border: 1px solid rgba(255,255,255,.14); font-weight: 800; font-size: 15px; cursor: pointer; }
.cdj-cue { background: #14203a; color: #7ac0ff; }
.cdj-cue:active { background: #2a9bff; color: #fff; }
.cdj-play { background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; box-shadow: 0 0 14px rgba(177,59,255,.4); }
.cdj-play.on { background: linear-gradient(180deg, #ff2d6b, #b3164a); box-shadow: 0 0 18px rgba(255,45,107,.6); }
.cdj-volrow { display: flex; align-items: center; gap: 6px; margin-top: 2px; }
.cdj-vol { flex: 1; height: 22px; min-width: 0; cursor: pointer; accent-color: var(--cdj-accent); background: transparent; }
.cdj-volval { font-size: 9px; font-weight: 800; color: #9fb0c8; min-width: 26px; text-align: right; font-variant-numeric: tabular-nums; }
.cdj-lbl { font-size: 8px; font-weight: 800; letter-spacing: .12em; color: rgba(255,255,255,.92); text-shadow: 0 1px 2px rgba(0,0,0,.6); margin-top: 2px; }
.cdj-bl-row { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 4px; }
.cdj-mrow.cdj-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }
.cdj-jmode.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 10px rgba(45,212,255,.5); }
.cdj-tmode.on { background: linear-gradient(180deg, #ffcc4a, #d98f10); color: #1a1206; border-color: transparent; }
.cdj-srch:active { background: #2a9bff; color: #fff; }
.cdj-bl { min-height: 26px; min-width: 0; border-radius: 6px; border: 1px solid rgba(255,255,255,.12); background: #12131b; color: #b9c2d6; font-weight: 800; font-size: 11px; cursor: pointer; padding: 0 2px; }
.cdj-bl.on { background: linear-gradient(180deg, #39e75f, #17a83b); color: #06180b; border-color: transparent; box-shadow: 0 0 10px rgba(57,231,95,.6); }
.cdj-mrow { display: flex; gap: 4px; align-items: center; }
.cdj-mrow.cdj-3 > button { flex: 1; }
.cdj-mrow button { min-height: 28px; border-radius: 7px; border: 1px solid rgba(255,255,255,.14); background: #14151c; color: #c9d2e6; font-weight: 800; font-size: 9.5px; letter-spacing: .03em; cursor: pointer; white-space: nowrap; padding: 4px 6px; }
.cdj-mrow button:hover { filter: brightness(1.2); }
.cdj-lbl2 { font-size: 8px; font-weight: 800; letter-spacing: .1em; color: rgba(255,255,255,.92); text-shadow: 0 1px 2px rgba(0,0,0,.6); margin-right: 2px; white-space: nowrap; }
.cdj-bjrow .cdj-bj { flex: 0 0 auto; min-width: 44px; }
/* ── BEAT FX touch screen (replaces the POWER/FILTER pad) — DJM-style ── */
.dj-bfxs { --bfx-accent: #ff9d3d; display: flex; flex-direction: column; gap: 6px; height: 100%; min-height: 150px; }   /* --bfx-accent set inline per deck by the colour picker */
.dj-bfxs-pad { position: relative; flex: 1 1 auto; min-height: 118px; border-radius: 8px; border: 1px solid color-mix(in srgb, var(--bfx-accent) 45%, rgba(255,255,255,.12)); cursor: crosshair; overflow: hidden; touch-action: none;
  background-color: color-mix(in srgb, var(--bfx-accent) 34%, #0a0e16);   /* tinted so the colour shows (not black) */
  background-image: linear-gradient(rgba(255,255,255,.14) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.14) 1px, transparent 1px);
  background-size: 25% 25%; }
.dj-bfxs.live .dj-bfxs-pad { border-color: color-mix(in srgb, var(--bfx-accent) 80%, transparent); box-shadow: inset 0 0 28px color-mix(in srgb, var(--bfx-accent) 45%, transparent); }
.dj-bfxs-color { position: absolute; top: 5px; right: 6px; width: 24px; height: 17px; padding: 0; border: 1px solid rgba(255,255,255,.4); border-radius: 4px; background: none; cursor: pointer; z-index: 4; }
.dj-bfxs-color::-webkit-color-swatch-wrapper { padding: 1px; }
.dj-bfxs-color::-webkit-color-swatch { border: none; border-radius: 3px; }
.dj-bfxs-title { position: absolute; top: 6px; left: 8px; font-size: 10px; font-weight: 800; letter-spacing: .1em; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.7); pointer-events: none; }
.dj-bfxs-lpf, .dj-bfxs-hpf, .dj-bfxs-hi, .dj-bfxs-lo { position: absolute; font-size: 8px; font-weight: 800; letter-spacing: .08em; color: rgba(255,255,255,.75); text-shadow: 0 1px 2px rgba(0,0,0,.6); pointer-events: none; }
.dj-bfxs-lpf { left: 6px; top: 50%; transform: translateY(-50%); }
.dj-bfxs-hpf { right: 6px; top: 50%; transform: translateY(-50%); }
.dj-bfxs-hi { top: 22px; left: 50%; transform: translateX(-50%); }
.dj-bfxs-lo { bottom: 6px; left: 50%; transform: translateX(-50%); }
.dj-bfxs-off { position: absolute; left: 50%; top: 50%; transform: translate(-50%, 14px); font-size: 7px; font-weight: 800; letter-spacing: .14em; color: rgba(255,255,255,.4); pointer-events: none; }
.dj-bfxs-dot { position: absolute; left: 50%; top: 50%; width: 17px; height: 17px; border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; background: radial-gradient(circle at 50% 38%, #ffffff, var(--bfx-accent)); box-shadow: 0 0 16px 5px color-mix(in srgb, var(--bfx-accent) 85%, transparent); }
.dj-bfxs-bar { display: flex; align-items: center; gap: 4px; }
.dj-bfxs-fx { flex: 1 1 auto; min-width: 0; background: #12131b; color: #cdd6ea; border: 1px solid rgba(255,255,255,.15); border-radius: 6px; font-size: 10px; font-weight: 700; padding: 3px; cursor: pointer; }
.dj-bfxs-bar button { flex: 0 0 auto; min-height: 26px; padding: 0 6px; border-radius: 6px; border: 1px solid rgba(255,255,255,.15); background: #14151c; color: #cdd6ea; font-size: 10px; font-weight: 800; cursor: pointer; }
.dj-bfxs-bar button:hover { filter: brightness(1.2); }
.dj-bfxs-beatv { flex: 0 0 auto; font-size: 11px; font-weight: 800; color: #eaf0ff; min-width: 26px; text-align: center; }
.dj-bfxs-bpm { flex: 0 0 auto; font-size: 9px; font-weight: 800; color: #ff9d3d; min-width: 44px; text-align: center; }
.dj-bfxs-hold.on { background: linear-gradient(180deg, #ff9d3d, #d9660f); color: #201206; border-color: transparent; }
.dj-bfxs-quant.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; }
/* ── SAMPLER pad bank (fires sound FX into the live mix) ── */
.dj-sampler-panel { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 60; width: min(560px, 94vw); padding: 12px; border-radius: 14px; border: 1px solid rgba(255,90,170,.35); background: linear-gradient(180deg, #17131f, #0c0910); box-shadow: 0 14px 44px rgba(0,0,0,.6), inset 0 0 30px rgba(120,40,120,.12); }
.dj-sampler-panel.hidden { display: none; }
.dj-smp-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; color: #e7ecf7; }
.dj-smp-head b { font-size: 13px; letter-spacing: .08em; }
.dj-smp-hint { font-size: 10px; color: #8f9ab5; flex: 1; }
.dj-smp-close { background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18); color: #cfd6e6; border-radius: 8px; width: 26px; height: 26px; cursor: pointer; font-weight: 700; }
.dj-smp-close:hover { filter: brightness(1.3); }
.dj-smp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.dj-smp-pad { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 66px; border-radius: 10px; border: 1px solid rgba(255,255,255,.14); background: linear-gradient(180deg, #201826, #140e19); color: #dfe6f4; font-weight: 800; cursor: pointer; user-select: none; }
.dj-smp-pad:hover { filter: brightness(1.25); }
.dj-smp-pad:active { transform: scale(.97); }
.dj-smp-pad.hit { background: radial-gradient(circle at 50% 40%, #ff5aa0, #7a1fd0); color: #fff; box-shadow: 0 0 20px rgba(255,90,170,.75); }
.dj-smp-emoji { font-size: 22px; line-height: 1; pointer-events: none; }
.dj-smp-name { font-size: 10px; letter-spacing: .04em; pointer-events: none; }
.dj-smp-pad.custom { border-color: #39e75f; box-shadow: 0 0 10px rgba(57,231,95,.35); }
.dj-smp-pad.custom .dj-smp-name { color: #7bffa0; }
.dj-smp-x { position: absolute; top: 3px; right: 5px; font-size: 11px; color: #ff5a6a; opacity: 0; cursor: pointer; }
.dj-smp-pad.custom .dj-smp-x { opacity: 1; }
.dj-rocket.on, #dj-sampler-btn.on { background: linear-gradient(180deg, #ff5aa0, #b3164a); border-color: transparent; box-shadow: 0 0 12px rgba(255,90,170,.6); }
/* COLOR row — pick each deck's wheel colour + a 🌈 rainbow cycle with speed */
.cdj-colorrow { display: flex; align-items: center; gap: 6px; }
.cdj-color { flex: 0 0 auto; width: 40px; height: 28px; padding: 0; border: 1px solid rgba(255,255,255,.22); border-radius: 7px; background: none; cursor: pointer; }
.cdj-color::-webkit-color-swatch-wrapper { padding: 2px; }
.cdj-color::-webkit-color-swatch { border: none; border-radius: 4px; }
.cdj-canim { flex: 0 0 auto; min-width: 34px; font-size: 13px !important; }
.cdj-canim.on { background: linear-gradient(90deg, #ff4d4d, #ffd24d, #4dff88, #4dd2ff, #b64dff) !important; color: #fff !important; border-color: transparent !important; box-shadow: 0 0 10px rgba(255,255,255,.25); }
.cdj-cspeed { flex: 1; min-width: 0; height: 20px; cursor: pointer; accent-color: var(--cdj-accent); }
.cdj-sync.on { background: linear-gradient(180deg, #39e75f, #1c9c3c); color: #05230f; border-color: transparent; box-shadow: 0 0 10px rgba(57,231,95,.6); }
.cdj-master.on { background: linear-gradient(180deg, #ff9d3d, #d9660f); color: #201206; border-color: transparent; box-shadow: 0 0 10px rgba(255,157,61,.6); }
.cdj-keyl.on { background: linear-gradient(180deg, #4aa8ff, #1f5fcf); color: #05122a; border-color: transparent; box-shadow: 0 0 10px rgba(74,168,255,.6); }
.cdj-slip.on, .cdj-quant.on { background: linear-gradient(180deg, #ffcc4a, #d98f10); color: #1a1206; border-color: transparent; }
.cdj-acue.on { background: linear-gradient(180deg, #39e75f, #1c9c3c); color: #05230f; border-color: transparent; box-shadow: 0 0 10px rgba(57,231,95,.5); }
.cdj-instd.hit { background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; border-color: transparent; }
.cdj-rev.on { background: linear-gradient(180deg, #ff5a3c, #c9331a); color: #fff; border-color: transparent; }
/* headphone + load buttons in the transport row */
.cdj-hp, .cdj-load { flex: 0 0 44px; min-height: 46px; border-radius: 10px; border: 1px solid rgba(255,255,255,.14); background: #191b24; color: #cdd6ea; font-size: 17px; cursor: pointer; }
.cdj-hp:hover, .cdj-load:hover { filter: brightness(1.25); }
.cdj-hp.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 12px rgba(45,212,255,.6); }
/* beat-loop numbers: a touch of inset so they never sit flush against the deck edge (Marcelo) */
.cdj-bl-row { padding: 0 2px; }
.cdj-bl { padding: 0 3px; }
/* loop control row now has 5 buttons (IN / OUT / RELOOP / ½ / ×2) */
.cdj-mrow.cdj-loopctl { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; }
.cdj-lhalf, .cdj-ldbl { font-size: 12px; }
/* ── TEMPO fader column (pitch slider + range + reset) ── */
.cdj-tempo { flex: 0 0 76px; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.cdj-trange { display: flex; gap: 3px; }
.cdj-tr { min-height: 20px; border-radius: 5px; border: 1px solid rgba(255,255,255,.14); background: #14151c; color: #b9c2d6; font-size: 8px; font-weight: 800; cursor: pointer; padding: 2px 4px; }
.cdj-tr.on { background: linear-gradient(180deg, #ff9d3d, #d9660f); color: #201206; border-color: transparent; box-shadow: 0 0 8px rgba(255,157,61,.5); }
.cdj-tval { font-size: 12px; font-weight: 800; color: #ffd24a; font-variant-numeric: tabular-nums; }
.cdj-tfader { position: relative; flex: 1 1 auto; width: 26px; min-height: 120px; border-radius: 8px; cursor: ns-resize; touch-action: none;
  background: linear-gradient(90deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a); border: 1px solid rgba(255,255,255,.1); box-shadow: inset 0 2px 7px rgba(0,0,0,.7); }
.cdj-tt-mid { position: absolute; left: 2px; right: 2px; top: 50%; height: 2px; margin-top: -1px; background: rgba(255,255,255,.28); pointer-events: none; }
.cdj-tthumb { position: absolute; left: -6px; right: -6px; top: 50%; height: 18px; margin-top: -9px; border-radius: 5px; background: linear-gradient(180deg, #ececf2, #a9adba); box-shadow: 0 2px 6px rgba(0,0,0,.6); pointer-events: none; }
.cdj-tthumb::after { content: ''; position: absolute; left: 4px; right: 4px; top: 50%; height: 2px; margin-top: -1px; background: rgba(0,0,0,.55); }
.cdj-treset { min-height: 22px; border-radius: 6px; border: 1px solid rgba(255,255,255,.14); background: #14151c; color: #c9d2e6; font-size: 8.5px; font-weight: 800; cursor: pointer; padding: 3px 6px; }
.cdj-treset:hover { filter: brightness(1.25); }
.cdj-tlbl { font-size: 8px; font-weight: 800; letter-spacing: .12em; color: #7f8aa0; }
.dj-deck {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border-radius: 12px;
  background: linear-gradient(180deg, #1c1224, #150c1c);
  border: 1px solid rgba(255,255,255,.06);
}
.dj-screen {
  background: #0a0710; border: 1px solid rgba(255,120,180,.25);
  border-radius: 8px; padding: 8px 10px;
}
.dj-screen-top { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; }
.dj-title { color: #f3e6ff; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 70%; }
.dj-time { color: #b98cff; font-variant-numeric: tabular-nums; }
.dj-wave {
  margin-top: 8px; height: 56px; border-radius: 5px; cursor: pointer;
  background: #0a0610; position: relative; overflow: hidden;
}
/* real scrolling waveform (XDJ-style) — canvas fills the box; centre = playhead */
.dj-wf { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.dj-wf + .dj-wave-fill { display: none; }   /* the canvas replaces the old progress fill when present */
.dj-wave-fill { position: absolute; top: 0; left: 0; bottom: 0; width: 0%;
  background: linear-gradient(90deg, rgba(255,80,160,.55), rgba(255,140,60,.55)); pointer-events: none; }
.dj-eq { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.dj-eq label, .dj-vol, .dj-master { display: flex; flex-direction: column; gap: 4px; align-items: center;
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: #c9a8ff; }
.dj-vol { align-items: stretch; }
.dj-deck-btns { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
.dj-deck-btns button { border: 1px solid rgba(255,120,180,.3); background: #241430; color: #fff; font-weight: 800; cursor: pointer; }
.dj-deck-btns button:hover { filter: brightness(1.14); }
/* round glowing CUE (left) + LOAD (mid) + PLAY (right) like the reference */
.dj-deck-btns .dj-cue-btn { flex: 0 0 auto; width: 54px; height: 54px; border-radius: 50%; font-size: 13px; }
.dj-deck-btns .dj-load { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 14px; font-size: 18px; }
.dj-deck-btns .dj-play { flex: 0 0 auto; width: 62px; height: 62px; border-radius: 50%; font-size: 22px;
  background: linear-gradient(180deg, #b13bff, #7a1fd0); box-shadow: 0 0 16px rgba(177,59,255,.55); }
.dj-deck-btns .dj-play.playing { box-shadow: 0 0 20px 3px rgba(255,80,160,.7); }
/* SYNC row — BEAT SYNC · MASTER · KEY LOCK (Pioneer-style) */
.dj-sync-row { display: flex; gap: 5px; margin-top: 6px; }
.dj-sync-row button { flex: 1; min-height: 24px; border-radius: 7px; border: 1px solid rgba(255,255,255,.16);
  background: #14121c; color: #b9c2d6; font-weight: 800; font-size: 8.5px; letter-spacing: .03em; cursor: pointer; padding: 3px 2px; white-space: nowrap; }
.dj-sync-row button:hover { filter: brightness(1.18); }
.dj-bsync.on { background: linear-gradient(180deg, #39e75f, #1c9c3c); color: #05230f; border-color: transparent; box-shadow: 0 0 12px rgba(57,231,95,.7); }
.dj-smaster.on { background: linear-gradient(180deg, #ff9d3d, #d9660f); color: #201206; border-color: transparent; box-shadow: 0 0 12px rgba(255,157,61,.7); }
.dj-keylock.on { background: linear-gradient(180deg, #4aa8ff, #1f5fcf); color: #05122a; border-color: transparent; box-shadow: 0 0 12px rgba(74,168,255,.75); }
.dj-center {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 9px;   /* gaps −25% — tighter, more compact centre (Marcelo) */
  align-self: stretch;
  padding: 8px; border-radius: 12px;
  background: linear-gradient(180deg, #241430, #180d20);
  border: 1px solid rgba(255,140,60,.25);
}
.dj-brand { font-weight: 800; letter-spacing: .06em; color: #ffb36b; text-shadow: 0 0 10px rgba(255,140,60,.5); }
.dj-master { width: 100%; }
.dj-xfade-wrap { display: flex; align-items: center; gap: 8px; width: 100%; color: #ffb36b; font-weight: 700; }
.dj-xfade-wrap input { flex: 1; }
.dj-xfade-label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #9b6bff; }
.dj-mixer input[type="range"] { width: 100%; accent-color: #ff5aa0; height: 4px; }
.dj-master input[type="range"], #dj-xfade { accent-color: #ff8a3d; }

/* Tabs + switchable display panel */
/* deck main = ONE display box; the tab buttons sit INSIDE it as a vertical
   strip on the LEFT (Marcelo) — frees width so the deck columns can slim down */
.dj-deck-main { display: flex; gap: 8px; align-items: stretch; }
/* moved off the jog wheel — now a horizontal bar across the bottom of the deck (Marcelo) */
.dj-tabs { display: flex; flex-direction: row; gap: 6px; margin-top: 8px; }
.dj-tab { flex: 1; min-height: 0; border-radius: 7px; border: 1px solid rgba(255,255,255,.08);
  background: #1b1024; color: #c9a8ff; font-weight: 700; font-size: 11px; letter-spacing: .03em; cursor: pointer; padding: 8px 2px; }
.dj-tab.active { background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; box-shadow: 0 0 12px rgba(177,59,255,.5); }
.dj-panel { position: relative; flex: 1; height: auto; min-height: 388px; align-self: stretch;
  /* stretches to the pitch column's full height — no dead strip under the
     display/equalizer (Marcelo: "make the equalizer a bit more big") */
  background: #0a0710; border: 1px solid rgba(255,120,180,.18);
  border-radius: 8px; padding: 10px; overflow: hidden; display: flex; flex-direction: row; gap: 8px; }
.dj-view { display: none; min-height: 0; min-width: 0; }
.dj-view.active { display: block; flex: 1; }
.dj-view[data-view="jog"] { display: none; }
.dj-view[data-view="jog"].active { display: flex; align-items: center; justify-content: center; }
/* When the jog wheel is showing, drop the display box so the wheel pops on its own (Marcelo).
   #dj-mixer prefix needed to beat the skin's `#dj-mixer .dj-panel` background rule. */
#dj-mixer .dj-panel:has(.dj-view[data-view="jog"].active) { background: transparent; border-color: transparent; box-shadow: none; overflow: visible; }

/* ── Bottom switchable area — the tabs (💿/EQ/FX/PRO/▦) flip THIS, not the wheel.
   💿 = the POWER/FILTER pad (default); the jog wheel up top is always shown (Marcelo). ── */
.dj-bviews { position: relative; flex: 1 1 auto; display: flex; flex-direction: column; min-height: 150px; margin-top: 10px; }
.dj-bview { display: none; min-height: 0; min-width: 0; width: 100%; }
.dj-bview.active { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.dj-bview[data-view="sampler"].active { overflow-y: auto; padding-right: 2px; }
.dj-bview[data-view="sampler"] .dj-pads { height: auto; grid-auto-rows: minmax(52px, auto); }

/* EQ — vertical faders */
.dj-faders { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; height: 100%; }
.dj-faders label { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 8px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #c9a8ff; }
.vfader { writing-mode: vertical-lr; direction: rtl; width: 6px !important; height: 118px !important; accent-color: #ff5aa0; cursor: pointer; }

/* FX — selector + XY pad */
.dj-fx-head { display: flex; gap: 8px; margin-bottom: 8px; }
.dj-fx-select { flex: 1; background: #1b1024; color: #ffb36b; border: 1px solid rgba(255,140,60,.35); border-radius: 6px; padding: 6px; font-weight: 700; }
.dj-fx-toggle { width: 62px; border-radius: 6px; border: 1px solid rgba(255,140,60,.4); background: #241430; color: #ffb36b; font-weight: 700; cursor: pointer; text-transform: uppercase; }
.dj-fx-toggle.on { background: #ff8a3d; color: #201; box-shadow: 0 0 12px rgba(255,140,60,.6); }
.dj-xy { position: relative; height: 112px; border-radius: 8px; cursor: crosshair; overflow: hidden;
  background: linear-gradient(0deg, rgba(255,140,60,.06), transparent), #120a18;
  background-image: linear-gradient(rgba(255,140,60,.12) 1px, transparent 1px), linear-gradient(90deg, rgba(255,140,60,.12) 1px, transparent 1px);
  background-size: 25% 25%, 25% 25%; border: 1px solid rgba(255,140,60,.3); }
.dj-xy-dot { position: absolute; width: 16px; height: 16px; border-radius: 50%; left: 50%; top: 50%; transform: translate(-50%,-50%);
  background: #ff8a3d; box-shadow: 0 0 14px 4px rgba(255,140,60,.7); pointer-events: none; }
.dj-xy-lx { position: absolute; right: 6px; bottom: 4px; font-size: 10px; color: #ffb36b; letter-spacing: .08em; }
.dj-xy-ly { position: absolute; left: 18px; top: 4px; font-size: 10px; color: #ffb36b; letter-spacing: .08em; }
/* Always-on XY pad at the bottom of each deck — grows to fill the leftover space
   below the deck so the column runs the full height with no empty gap (Marcelo). */
.dj-xy-bottom { flex: 1 1 auto; height: auto; min-height: 96px; margin-top: 0; }
/* ── Channel TRIM (gain) knob + LED level meter, DJM-style (Marcelo) ── */
.dj-trim { width: 30px; height: 30px; border-radius: 50%; border: 1px solid rgba(255,255,255,.25); cursor: ns-resize; padding: 0;
  background: radial-gradient(circle at 50% 38%, #3a3346, #14101c 72%); position: relative; touch-action: none; }
.dj-trim-i { position: absolute; left: 50%; top: 3px; width: 2px; height: 11px; margin-left: -1px; background: #fff; border-radius: 1px;
  transform-origin: 50% 12px; transform: rotate(0deg); }
.dj-trim-lbl { font-size: 8px; font-weight: 800; letter-spacing: .1em; color: #9b8bd0; margin: 1px 0 3px; }
.dj-fadwrap { display: flex; align-items: center; gap: 13px; }   /* more air between the fader and its LED meter (Marcelo) */
/* Long LED level line — runs the FULL channel height (from TRIM at the top down to
   the fader bottom) on the right edge, thicker than before (Marcelo). */
.dj-chan-meter { position: absolute; top: 2px; right: 0; width: 9px; height: 408px; border-radius: 4px;
  background: #0a0610; border: 1px solid rgba(255,255,255,.1); display: block; }
/* EQ knobs (HI/MID/LOW) in the channel strip — same knob look as TRIM */
.dj-eqk { width: 28px; height: 28px; border-radius: 50%; border: 1px solid rgba(255,255,255,.22); cursor: ns-resize; padding: 0;
  background: radial-gradient(circle at 50% 38%, #33304a, #12101c 72%); position: relative; touch-action: none; margin-top: 2px; }
.dj-eqk .dj-trim-i { top: 3px; height: 10px; transform-origin: 50% 11px; }
/* CUE button per channel — pre-listen this channel in headphones */
.dj-chcue { margin-top: 6px; min-height: 22px; padding: 3px 12px; border-radius: 6px; border: 1px solid rgba(255,180,80,.5);
  background: #241a10; color: #ffd8a0; font-weight: 800; font-size: 10px; letter-spacing: .1em; cursor: pointer; }
.dj-chcue.on { background: linear-gradient(180deg, #ffb43d, #d98f10); color: #1a1206; border-color: transparent; box-shadow: 0 0 10px rgba(255,180,61,.6); }

/* PRO tools */
.dj-tempo { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.dj-tempo button, .dj-pro-row button, .dj-cue { border-radius: 6px; border: 1px solid rgba(255,255,255,.1);
  background: #1b1024; color: #e9d8ff; font-weight: 700; cursor: pointer; padding: 8px; }
.dj-tempo .dj-tempo-val { flex: 1; text-align: center; color: #ffb36b; font-variant-numeric: tabular-nums; }
.dj-tempo button { width: 40px; font-size: 18px; }
.dj-pro-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; margin-bottom: 8px; }
.dj-sync { background: #ff8a3d !important; color: #201 !important; }
.dj-sync.on { box-shadow: 0 0 12px rgba(255,140,60,.7); }
.dj-cues { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.dj-cue { font-size: 12px; }
.dj-cue.set { background: linear-gradient(180deg,#2a9bff,#1666d0); color: #fff; }

/* Sampler pads */
.dj-pads { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; height: 100%; }
/* Sampler view scrolls: all 9 pads, the ＋ add button and your own added
   pads are reachable — nothing is clipped by the 190px panel anymore. */
.dj-view[data-view="sampler"].active { overflow-y: auto; padding-right: 2px; }
.dj-view[data-view="sampler"] .dj-pads { height: auto; grid-auto-rows: minmax(52px, auto); }
.dj-pad { border-radius: 8px; border: 1px solid rgba(255,120,180,.25); background: #1b1024; color: #f3e6ff;
  font-size: 11px; font-weight: 700; cursor: pointer; padding: 4px; }
.dj-pad:active { background: #ff8a3d; color: #201; transform: scale(.96); }

/* Jog wheel */
.dj-jog { width: 366px; height: 366px; margin: 0 auto; border-radius: 50%; position: relative; cursor: grab; touch-action: none;   /* +30% then +20% bigger — pops without the box (Marcelo) */
  background: radial-gradient(circle at 50% 40%, #3a2650, #120a1c 72%);
  box-shadow: inset 0 5px 16px rgba(0,0,0,.8), inset 0 -4px 10px rgba(255,255,255,.05), 0 6px 20px rgba(0,0,0,.6); }
.dj-jog:active, .dj-jog.scratching { cursor: grabbing; }
.dj-jog.scratching { box-shadow: inset 0 4px 12px rgba(0,0,0,.8), 0 0 22px color-mix(in srgb, var(--dj-warm) 55%, transparent); }
/* the spinning platter: glossy top light + ridged/dimpled rim + smooth disc */
.dj-jog-platter { position: absolute; inset: 8px; border-radius: 50%; overflow: hidden; will-change: transform;
  background:
    radial-gradient(circle at 50% 34%, rgba(255,255,255,.16), transparent 42%),
    radial-gradient(circle at 50% 50%, #33234a 0 45%, rgba(0,0,0,0) 46%),
    repeating-conic-gradient(from 0deg, #2a1b3e 0deg 3deg, #150c22 3deg 6deg);
  box-shadow: inset 0 0 16px rgba(0,0,0,.65); }
.dj-jog-mark { position: absolute; top: 10px; left: 50%; width: 7px; height: 52px; border-radius: 3px; background: #ff8a3d;
  transform: translateX(-50%); box-shadow: 0 0 8px rgba(255,140,60,.85); }
/* glowing accent ring */
.dj-jog::after { content: ''; position: absolute; inset: 42px; border-radius: 50%; pointer-events: none;
  border: 2px solid rgba(255,140,60,.7); box-shadow: 0 0 12px rgba(255,140,60,.45), inset 0 0 10px rgba(255,140,60,.15); }
/* static centre hub = a live info SCREEN (Pioneer-style): DECK · BPM · spectrum · time */
.dj-jog-hub { position: absolute; inset: 59px; border-radius: 50%; pointer-events: none; overflow: hidden;   /* ~60% bigger display screen (Marcelo) */
  background: radial-gradient(circle at 50% 34%, #17131f, #05040a 78%);
  border: 1px solid rgba(255,255,255,.12); box-shadow: 0 3px 10px rgba(0,0,0,.6), inset 0 2px 8px rgba(255,255,255,.05);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; padding: 14px; text-align: center; }
.dj-jd-deck { font-size: 17px; font-weight: 800; letter-spacing: .12em; color: #8f86a8; }
.dj-jd-deck b { color: #fff; }
.dj-jd-bpm { font-size: 18px; font-weight: 700; color: #ffb36b; line-height: 1; }
.dj-jd-bpm b { font-size: 48px; font-weight: 800; color: #ffd24a; letter-spacing: -.5px; }
.dj-jd-viz { width: 78%; height: 53px; display: block; margin: 3px 0; }
.dj-jd-time { font-size: 36px; font-weight: 800; color: #e8f0ff; font-variant-numeric: tabular-nums; letter-spacing: .5px; }

/* CUE + beat-loop bottom controls */
.dj-cue-btn { flex: 1; padding: 10px 0; border-radius: 8px; border: 1px solid rgba(80,180,255,.4); background: #14203a; color: #7ac0ff; font-weight: 800; cursor: pointer; }
.dj-cue-btn:active { background: #2a9bff; color: #fff; }
.dj-loop-row { display: grid; grid-template-columns: 44px 1fr 44px; gap: 6px; }
/* One line at the bottom of each deck: TEMPO fader beside the equalizer */
.dj-deck-bottom { display: flex; align-items: stretch; gap: 10px; margin-top: 8px; }
.dj-deck-bottom-b { flex-direction: row-reverse; }   /* mirror: tempo on the outer edge */
/* grow to fill the deck column's leftover height (the grid stretches the deck to
   match the taller centre column) so there's no empty space below the equalizer */
.dj-deck-bottom { min-width: 0; flex: 0 0 auto; }   /* natural height — don't grow (stops the EQ runaway) */
.dj-deck-bottom .dj-tempofader { margin-top: 0; flex: 0 0 auto; justify-content: flex-start; align-self: flex-start; }   /* sit at the TOP, not floating in the middle (Marcelo) */
/* min-width:0 lets the canvas shrink below its intrinsic 360px so it never
   overflows its column into the centre when the window narrows */
.dj-deck-bottom { position: relative; }
/* the equalizer absolutely FILLS the row beside the fader — the fader sets the
   row height, the canvas matches it to the pixel: no gap, ever (Marcelo).
   Canvas quirk: any flex/height combo lets its intrinsic 230px leak back. */
.dj-deck-bottom .dj-eq-deck { position: absolute; top: 0; left: 74px; width: calc(100% - 74px); height: 100%; min-height: 0; margin-top: 0; }
.dj-deck-bottom-b .dj-eq-deck { left: 0; }
/* Per-deck TEMPO (pitch) fader — authentic vertical pitch slider (±8%) */
.dj-tempofader { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-top: 10px; }
.dj-tf-val { font-size: 11px; font-weight: 800; color: var(--dj-accent, #ff5aa0); font-variant-numeric: tabular-nums; }
.dj-tf-lbl { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: var(--dj-warm, #ff8a3d); }
.dj-tf-reset { margin: 4px auto 2px; display: block; min-height: 22px; border-radius: 6px; border: 1px solid rgba(255,255,255,.16); background: #14151c; color: #c9d2e6; font-size: 9px; font-weight: 800; letter-spacing: .06em; cursor: pointer; padding: 3px 12px; }
.dj-tf-reset:hover { filter: brightness(1.25); }
.dj-tf-reset:active { background: #2a9bff; color: #fff; }
.dj-tf-track {
  position: relative; width: 62px; height: 118px; border-radius: 8px; touch-action: none; cursor: ns-resize;
  background: linear-gradient(180deg, #16121d, #0c0910);
  border: 1px solid rgba(255,255,255,.08); box-shadow: inset 0 2px 6px rgba(0,0,0,.6);
}
/* the centre rail the cap rides on */
.dj-tf-track::before { content: ''; position: absolute; left: 50%; top: 8px; bottom: 8px; width: 4px; margin-left: -2px;
  border-radius: 3px; background: linear-gradient(180deg, #05040a, #0c0a12); box-shadow: inset 0 0 2px #000; }
/* tick marks down both sides */
.dj-tf-track::after { content: ''; position: absolute; left: 6px; right: 6px; top: 10px; bottom: 10px; pointer-events: none;
  background:
    repeating-linear-gradient(180deg, rgba(255,255,255,.28) 0 1px, transparent 1px 12px) left / 12px 100% no-repeat,
    repeating-linear-gradient(180deg, rgba(255,255,255,.28) 0 1px, transparent 1px 12px) right / 12px 100% no-repeat; }
.dj-tf-mk { position: absolute; left: 4px; font-size: 10px; font-weight: 800; color: #9fb0c4; pointer-events: none; }
.dj-tf-minus { top: 4px; }
.dj-tf-zero  { top: 50%; transform: translateY(-50%); color: #cdd8e6; }
.dj-tf-plus  { bottom: 4px; }
/* the chunky silver cap */
.dj-tf-thumb {
  position: absolute; left: 50%; top: 50%; width: 46px; height: 30px; margin-left: -23px; margin-top: -15px;
  border-radius: 6px; cursor: grab;
  background: linear-gradient(180deg, #3a3d45 0 42%, #15161a 42% 58%, #2b2d34 58% 100%);
  border: 1px solid #000; box-shadow: 0 3px 6px rgba(0,0,0,.6), inset 0 1px 1px rgba(255,255,255,.18);
}
/* the bright indicator line across the cap centre */
.dj-tf-thumb::after { content: ''; position: absolute; left: 4px; right: 4px; top: 50%; height: 3px; margin-top: -1.5px;
  border-radius: 2px; background: linear-gradient(180deg, #fff, #b9c2cf); box-shadow: 0 0 5px rgba(255,255,255,.5); }
.dj-tf-thumb:active { cursor: grabbing; }
.dj-loop-row button { padding: 8px 0; border-radius: 8px; border: 1px solid rgba(255,255,255,.1); background: #1b1024; color: #e9d8ff; font-weight: 700; cursor: pointer; }
.dj-loop-toggle.active { background: #ff8a3d; color: #201; box-shadow: 0 0 12px rgba(255,140,60,.6); }

/* Center console: REC + rocket + BPM */
.dj-console { display: flex; gap: 8px; width: 100%; }
.dj-rec, .dj-rocket { flex: 1; padding: 9px 0; border-radius: 8px; border: 1px solid rgba(255,60,60,.5); background: #3a1414; color: #ff8a8a; font-weight: 800; cursor: pointer; }
.dj-rec.on { background: #e11d48; color: #fff; box-shadow: 0 0 14px rgba(225,29,72,.7); animation: djRecPulse 1s infinite; }
.dj-rocket { border-color: rgba(255,140,60,.5); background: #3a2414; color: #ffb36b; flex: 0 0 46px; }
@keyframes djRecPulse { 50% { opacity: .55; } }
.dj-bpm { font-weight: 800; font-size: 18px; color: #ff8a3d; letter-spacing: .04em; text-shadow: 0 0 10px rgba(255,140,60,.5); font-variant-numeric: tabular-nums; }

/* ── Color themes: accents read 3 vars, base surfaces read --dj-bg ── */
#dj-mixer { --dj-accent: #ff5aa0; --dj-accent2: #b13bff; --dj-warm: #ff8a3d; --dj-bg: #14091c; }
#dj-mixer.dj-mixer {
  background: radial-gradient(120% 140% at 50% 0%, color-mix(in srgb, var(--dj-accent2) 16%, transparent), transparent 60%), var(--dj-bg);
  border-color: color-mix(in srgb, var(--dj-accent) 55%, transparent);
  box-shadow: 0 0 24px color-mix(in srgb, var(--dj-accent) 30%, transparent), inset 0 0 40px color-mix(in srgb, var(--dj-accent2) 12%, transparent); }
#dj-mixer .dj-deck { background: linear-gradient(180deg, color-mix(in srgb, var(--dj-bg) 86%, white 7%), var(--dj-bg)); }
#dj-mixer .dj-center { background: linear-gradient(180deg, color-mix(in srgb, var(--dj-bg) 80%, white 9%), var(--dj-bg)); }
#dj-mixer .dj-screen, #dj-mixer .dj-panel { background: color-mix(in srgb, var(--dj-bg) 76%, black); }
#dj-mixer .dj-tab.active { background: linear-gradient(180deg, var(--dj-accent2), color-mix(in srgb, var(--dj-accent2) 55%, black));
  box-shadow: 0 0 12px color-mix(in srgb, var(--dj-accent2) 50%, transparent); }
#dj-mixer .dj-deck-btns .dj-play { background: linear-gradient(180deg, var(--dj-accent2), color-mix(in srgb, var(--dj-accent2) 55%, black)); }
#dj-mixer .dj-deck-btns .dj-play.playing { background: linear-gradient(180deg, color-mix(in srgb, var(--dj-accent) 65%, white), var(--dj-accent));
  box-shadow: 0 0 14px color-mix(in srgb, var(--dj-accent) 55%, transparent); }
#dj-mixer input[type="range"] { accent-color: var(--dj-accent); }
#dj-mixer .dj-master input[type="range"], #dj-mixer #dj-xfade { accent-color: var(--dj-warm); }
#dj-mixer .dj-brand, #dj-mixer .dj-bpm { color: var(--dj-warm); text-shadow: 0 0 10px color-mix(in srgb, var(--dj-warm) 50%, transparent); }
#dj-mixer .dj-jog::after { border-color: color-mix(in srgb, var(--dj-warm) 70%, transparent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--dj-warm) 40%, transparent), inset 0 0 8px color-mix(in srgb, var(--dj-warm) 15%, transparent); }
#dj-mixer .dj-jog-mark { background: var(--dj-warm); box-shadow: 0 0 8px color-mix(in srgb, var(--dj-warm) 80%, transparent); }
#dj-mixer .dj-loop-toggle.active, #dj-mixer .dj-fx-toggle.on, #dj-mixer .dj-sync { background: var(--dj-warm) !important; color: #201 !important; }
#dj-mixer .dj-pad:active { background: var(--dj-warm); color: #201; }

/* Swatch row */
.dj-colors { display: flex; gap: 7px; justify-content: center; margin-top: 4px; flex-wrap: wrap; }
.dj-color { width: 18px; height: 18px; border-radius: 50%; border: 2px solid rgba(255,255,255,.25); cursor: pointer; padding: 0; }
.dj-color:hover { transform: scale(1.15); }
.dj-color.active { border-color: #fff; box-shadow: 0 0 8px rgba(255,255,255,.6); }

/* Random color button */
.dj-rand { width: 20px; height: 20px; border-radius: 50%; border: 2px dashed rgba(255,255,255,.35); background: #1b1024; cursor: pointer; font-size: 10px; line-height: 1; padding: 0; }
.dj-rand:hover { transform: scale(1.15); }

/* Full "skins" — accent + background bundles */
.dj-skins { display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: 6px; margin-top: 8px; }
.dj-skin { padding: 6px 4px; border-radius: 7px; border: 1px solid rgba(255,255,255,.12); background: #14091c; color: #e9d8ff; font-size: 11px; font-weight: 700; cursor: pointer; }
.dj-skin { position: relative; }
.dj-skin:hover { filter: brightness(1.2); border-color: var(--dj-accent); }
.dj-skin-del { position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; border-radius: 50%; background: #e11d48; color: #fff; font-size: 10px; line-height: 16px; text-align: center; display: none; }
.dj-skin:hover .dj-skin-del { display: block; }
.dj-save-style { width: 100%; margin-top: 8px; padding: 8px; border-radius: 8px; border: 1px solid var(--dj-accent); background: #14091c; color: #e9d8ff; font-weight: 700; font-size: 12px; cursor: pointer; }
.dj-save-style:hover { background: color-mix(in srgb, var(--dj-accent) 20%, #14091c); }
/* sampler upload */
.dj-pad-hint { font-size: 10px; color: #9b8bc0; text-align: center; margin-bottom: 6px; }
.dj-pad.custom { border-color: var(--dj-warm); color: var(--dj-warm); }
.dj-pad { position: relative; }
/* ✕ appears when hovering a pad (tidy grid, still discoverable); an ARMED ✕
   stays visible even if the mouse slips during the two-click confirm. */
.dj-pad-x { position: absolute; top: -5px; right: -5px; width: 15px; height: 15px; border-radius: 50%; background: #e11d48; color: #fff; font-size: 9px; line-height: 15px; text-align: center; display: none; cursor: pointer; }
.dj-pad:hover .dj-pad-x, .dj-pad-x.armed { display: block; }
/* Armed ✕ (first of the two safety clicks): orange, bigger, pulsing */
.dj-pad-x.armed, .dj-pad-userx.armed, .dj-rocket-x.armed {
  background: #ff9d1c; transform: scale(1.5); animation: libRecPulse 0.9s ease-in-out infinite;
}
.dj-pad-restore { display: block; width: 100%; margin-top: 6px; padding: 8px; border-radius: 10px;
  border: 1px solid rgba(255,255,255,.25); background: rgba(255,255,255,.06); color: #cfd6e4; font-weight: 700; cursor: pointer; }
.dj-pad-restore:hover { background: rgba(255,255,255,.14); }
.dj-pad-restore.hidden { display: none; }
/* Your own added pads: red ✕ badge appears on hover (armed stays visible) */
.dj-pad-userx { position: absolute; top: -5px; right: -5px; width: 15px; height: 15px; border-radius: 50%; background: #e11d48; color: #fff; font-size: 9px; line-height: 15px; text-align: center; cursor: pointer; display: none; }
.dj-pad-user:hover .dj-pad-userx, .dj-pad-userx.armed { display: block; }
/* ＋ Add your own sound — dashed green, same invitation as the SFX board */
.dj-pad-add { display: block; width: 100%; margin-top: 8px; padding: 10px; border-radius: 10px;
  border: 2px dashed #7ee787; background: rgba(126,231,135,.06); color: #7ee787; font-weight: 800; cursor: pointer; }
.dj-pad-add:hover { background: rgba(126,231,135,.14); }
.dj-tap { transition: background .1s; }
.dj-tap.on { background: var(--dj-warm) !important; color: #201 !important; }

/* Custom color pickers (accent + background) */
.dj-custom { display: flex; gap: 10px; justify-content: center; margin-top: 8px; }
.dj-cc { display: flex; flex-direction: column; align-items: center; gap: 3px; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; color: #c9a8ff; cursor: pointer; }
.dj-cc input[type="color"] { width: 30px; height: 22px; padding: 0; border: 1px solid rgba(255,255,255,.25); border-radius: 6px; background: none; cursor: pointer; }

/* ── Traditional center: BPM box, channel faders, chunky crossfader ── */
.dj-bpm-box { display: flex; align-items: center; gap: 8px; justify-content: center; width: 100%;
  padding: 8px 12px; border-radius: 8px; background: #0a0710; border: 1px solid color-mix(in srgb, var(--dj-warm) 32%, transparent); }
.dj-bpm-box .dj-bpm { text-shadow: none; }
.dj-bpm-ico { color: var(--dj-warm); font-weight: 900; letter-spacing: -2px; }
/* ── Channel faders — tall hardware-style vertical faders (deck A / B) ── */
.dj-chans { display: flex; gap: 34px; justify-content: center; align-items: flex-end; padding: 8px 0 2px; }
/* ── 4-deck mode: A|C · B|D toggles on each turntable + C/D faders in the centre.
   Hidden until the 🎛 4 DECKS switch turns the mode on. ── */
.dj-mixer:not(.four-decks) .dj-chan-cd { display: none; }
.dj-mixer.four-decks .dj-chans { gap: 56px; }   /* four faders spread across the 338px centre — real daylight between the caps */
/* 6-deck mode: E/F toggles + E/F faders appear, second mini card unlocks, faders tighten to fit six */
.dj-mixer:not(.six-decks) .dj-chan-ef { display: none; }
.dj-mixer:not(.six-decks) .dj-dsel-ef { display: none; }
/* 4-/6-deck: strips SHARE the centre box equally (flex) and the COLOR slider
   flexes to fit — so the faders never spill outside the middle box (Marcelo). */
.dj-mixer.four-decks .dj-chan { flex: 1 1 0; min-width: 0; }
.dj-mixer.four-decks .dj-chans { width: 100%; box-sizing: border-box; gap: 16px; padding-left: 22px; }   /* nudge the whole fader group a touch right (Marcelo) */
.dj-mixer.six-decks .dj-chans { gap: 6px; padding-left: 8px; }   /* tighter so the bigger glowing caps still fit (Marcelo) */
.dj-mixer.six-decks .dj-fadwrap { gap: 8px; }   /* slim the fader→meter gap only in 6-deck to make room for bigger caps */
.dj-mixer.six-decks .dj-chan-meter { width: 8px; }
.dj-mixer.four-decks:not(.six-decks) .dj-minideck[data-slot="2"] { display: none; }
.dj-deck-sel { display: none; align-items: center; gap: 6px; justify-content: flex-end; margin-bottom: 2px; }
.dj-mixer.four-decks .dj-deck-sel { display: flex; }
.dj-dsel-lbl { font-size: 10px; font-weight: 800; letter-spacing: .1em; color: #9b8bd0; margin-right: 2px; }
.dj-dsel { min-width: 36px; height: 24px; border-radius: 12px; border: 1px solid rgba(255,255,255,.25);
  background: rgba(255,255,255,.06); color: #d9d2ee; font-weight: 800; font-size: 12px; cursor: pointer; }
.dj-dsel:hover { filter: brightness(1.2); }
.dj-dsel.active { background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; border-color: transparent; box-shadow: 0 0 10px rgba(177,59,255,.55); }
/* Mini deck — the OTHER deck on this side, always visible with its own spinning
   wheel while 4-deck mode is on. Tap anywhere on it to swap it to the front. */
.dj-minideck { display: none; }
.dj-mixer.four-decks .dj-minideck { display: flex; align-items: center; gap: 10px; margin-top: 2px;
  padding: 8px 10px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.10); cursor: pointer; }
.dj-mixer.four-decks .dj-minideck:hover { border-color: rgba(177,59,255,.55); }
.dj-mixer.four-decks .dj-minideck[data-slot="1"] { margin-top: -4px; }   /* shifts the whole DECK C/E pair up another ~5% (Marcelo) */
.dj-mixer.four-decks .dj-minideck[data-slot="2"] { margin-top: -4px; margin-bottom: 32px; }   /* pulls DECK E up away from the CUE/headphone/play row below it (Marcelo) */
.dj-mini-jog { width: 56px; height: 56px; flex: 0 0 56px; border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, #2a2438, #0d0a14 72%); border: 2px solid rgba(255,255,255,.18);
  display: grid; place-items: center; }
.dj-mini-platter { width: 44px; height: 44px; border-radius: 50%; position: relative;
  background: repeating-conic-gradient(rgba(255,255,255,.10) 0 6deg, transparent 6deg 12deg), #171126; }
.dj-mini-mark { position: absolute; left: 50%; top: 2px; width: 3px; height: 12px; margin-left: -1.5px; border-radius: 2px; background: #ff8a3d; }
.dj-mini-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; text-align: left; }
.dj-mini-name { font-size: 12px; font-weight: 800; letter-spacing: .03em; color: #e6ddf6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dj-mini-time { font-size: 11px; opacity: .65; font-variant-numeric: tabular-nums; }
.dj-mini-play { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: 0; cursor: pointer;
  background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; font-size: 15px; font-weight: 800; }
.dj-mini-play.playing { box-shadow: 0 0 14px rgba(255,80,160,.75); }
/* ── Performance pads (Pioneer-style): mode row + 2×4 glowing pads per turntable ── */
.dj-padsec { margin-top: 8px; }
.dj-padmode { display: flex; gap: 4px; margin-bottom: 6px; }
.dj-pm { flex: 1; min-height: 22px; border-radius: 6px; border: 1px solid rgba(255,255,255,.14);
  background: #1b1024; color: #c9a8ff; font-weight: 800; font-size: 9px; letter-spacing: .04em; cursor: pointer; padding: 3px 2px; white-space: nowrap; }
.dj-pm.active { background: linear-gradient(180deg, #ff8a3d, #d95f10); color: #180d20; border-color: transparent; box-shadow: 0 0 8px rgba(255,138,61,.5); }
.dj-perfpads { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.dj-ppad-wrap { position: relative; display: block; }
.dj-ppad { --pad-glow: #2dd4ff; width: 100%; height: 60px; border-radius: 9px; cursor: pointer; font-weight: 800; font-size: 12px; color: #0b0714;
  background: linear-gradient(180deg, color-mix(in srgb, var(--pad-glow) 88%, #fff), color-mix(in srgb, var(--pad-glow) 72%, #000));
  border: 2px solid color-mix(in srgb, var(--pad-glow) 55%, #000);
  box-shadow: 0 0 12px color-mix(in srgb, var(--pad-glow) 45%, transparent), inset 0 1px 6px rgba(255,255,255,.25);
  transition: transform .08s, box-shadow .12s; padding: 2px; }
.dj-ppad.lit { border-color: #fff;
  background: linear-gradient(180deg, #fff -40%, var(--pad-glow) 45%, color-mix(in srgb, var(--pad-glow) 70%, #000));
  box-shadow: 0 0 22px var(--pad-glow), inset 0 1px 8px rgba(255,255,255,.4); }
.dj-ppad.hit, .dj-ppad:active { transform: scale(.94); }
/* per-pad colour swatch (native picker) tucked in the corner — like the fader swatches */
.dj-ppad-sw { position: absolute; top: 5px; right: 5px; width: 15px; height: 15px; padding: 0; -webkit-appearance: none; appearance: none;
  border: 1px solid rgba(255,255,255,.55); border-radius: 4px; background: none; cursor: pointer; z-index: 2; opacity: .8; }
.dj-ppad-sw:hover { opacity: 1; }
.dj-ppad-sw::-webkit-color-swatch-wrapper { padding: 0; }
.dj-ppad-sw::-webkit-color-swatch { border: none; border-radius: 3px; }
.dj-ppad-sw::-moz-color-swatch { border: none; border-radius: 3px; }
/* ── CDJ-style manual loop row: IN · OUT · RELOOP + SLIP / QUANTIZE (red, like the hardware) ── */
.dj-mloop-row { display: flex; gap: 4px; margin-top: 6px; }
.dj-mloop-row button { flex: 1; min-height: 24px; border-radius: 6px; border: 1px solid rgba(255,255,255,.16);
  background: #1b1024; color: #d9d2ee; font-weight: 800; font-size: 9px; letter-spacing: .05em; cursor: pointer; padding: 4px 2px; white-space: nowrap; }
.dj-mloop-row button:hover { filter: brightness(1.25); }
.dj-lin.set { background: #ffb020; color: #180d20; border-color: transparent; }
.dj-reloop.on { background: linear-gradient(180deg, #39e75f, #17a83b); color: #06180b; border-color: transparent; box-shadow: 0 0 10px rgba(57,231,95,.6); }
.dj-slip, .dj-quant { border-color: rgba(255,80,80,.5); color: #ff9d9d; }
.dj-slip.on, .dj-quant.on { background: linear-gradient(180deg, #ff5050, #c22323); color: #fff; border-color: transparent; box-shadow: 0 0 10px rgba(255,80,80,.6); }
.dj-rev { border-color: rgba(45,212,255,.5); color: #9fe3ff; }
.dj-rev.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 12px rgba(45,212,255,.75); }
/* ── SOUND COLOR FX: effect selector in the centre + a COLOR slider per channel ── */
/* PARAMETER knob sits in the MIDDLE of the 6 buttons (3 cols: buttons | knob | buttons);
   buttons a touch shorter so it fits perfectly, tight spacing (Marcelo). */
.dj-colorfx-btns { display: grid; grid-template-columns: 1fr auto 1fr; gap: 4px 11px; width: 100%; align-items: center; }
.dj-cfx { min-height: 21px; border-radius: 6px; border: 1px solid rgba(255,255,255,.16);
  background: #1b1024; color: #c9a8ff; font-weight: 800; font-size: 10px; letter-spacing: .05em; cursor: pointer; padding: 2px 2px; white-space: nowrap; }
.dj-cfx.active { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 8px rgba(45,212,255,.5); }
.dj-cfx[data-cfx="space"]  { grid-area: 1 / 1; }
.dj-cfx[data-cfx="crush"]  { grid-area: 2 / 1; }
.dj-cfx[data-cfx="noise"]  { grid-area: 3 / 1; }
.dj-cfx[data-cfx="echo"]   { grid-area: 1 / 3; }
.dj-cfx[data-cfx="sweep"]  { grid-area: 2 / 3; }
.dj-cfx[data-cfx="filter"] { grid-area: 3 / 3; }
/* SOUND COLOR FX — global PARAMETER knob (MIN↔MAX), centred among the buttons */
.dj-cfx-param { grid-area: 1 / 2 / 4 / 3; display: flex; flex-direction: column; align-items: center; gap: 1px; margin-top: 0; width: auto; }
.dj-cfx-param-lbl { font-size: 8.3px; font-weight: 800; letter-spacing: .1em; color: #9b8bd0; white-space: nowrap; }
.dj-cfxk { width: 46px; height: 46px; border-radius: 50%; border: 1px solid rgba(255,255,255,.3); cursor: ns-resize; padding: 0;
  background: radial-gradient(circle at 50% 34%, #6d6f7e, #2b2b33 60%, #101015 82%); position: relative; touch-action: none;
  box-shadow: inset 0 1px 2px rgba(255,255,255,.28), 0 1px 3px rgba(0,0,0,.5); }
.dj-cfxk .dj-trim-i { top: 4px; height: 18px; transform-origin: 50% 19px; background: #fff; }
.dj-cfx-ends { display: flex; justify-content: space-between; width: 62px; font-size: 7.7px; font-weight: 800; color: #8a7bc0; letter-spacing: .04em; }
/* COLOR — a rotary knob (DJM-style) in its own framed compartment: COLOR on top,
   metallic knob, LOW↔HI at the bottom corners (Marcelo — match the pro reference) */
.dj-colk-box { display: flex; flex-direction: column; align-items: center; gap: 2px; margin-top: 4px;
  padding: 5px 6px 4px; border: 1px solid rgba(255,255,255,.14); border-radius: 7px;
  background: rgba(255,255,255,.03); width: 100%; box-sizing: border-box; }
.dj-colk-lbl { font-size: 8px; font-weight: 800; letter-spacing: .12em; color: #9b8bd0; line-height: 1; }
.dj-colk { width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255,255,255,.3); cursor: ns-resize; padding: 0;
  background: radial-gradient(circle at 50% 34%, #6d6f7e, #2b2b33 60%, #101015 82%); position: relative; touch-action: none;
  box-shadow: inset 0 1px 2px rgba(255,255,255,.28), 0 1px 3px rgba(0,0,0,.5); }
.dj-colk .dj-trim-i { top: 3px; height: 13px; transform-origin: 50% 14px; background: #fff; }
.dj-colk-ends { display: flex; justify-content: space-between; width: 100%; font-size: 7px; font-weight: 800;
  color: #8a7bc0; letter-spacing: .05em; margin-top: 1px; padding: 0 1px; box-sizing: border-box; }
/* shrink the knob when more decks squeeze the channel width */
.dj-mixer.six-decks .dj-colk { width: 26px; height: 26px; }
.dj-mixer.six-decks .dj-colk .dj-trim-i { height: 10px; transform-origin: 50% 11px; }
.dj-mixer.six-decks .dj-colk-box { padding: 3px 3px 3px; gap: 1px; }
/* ── BEAT FX unit in the centre column ── */
.dj-bfx-types { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; width: 100%; }
.dj-bft, .dj-bfreq { min-height: 20px; border-radius: 6px; border: 1px solid rgba(255,255,255,.16);
  background: #1b1024; color: #c9a8ff; font-weight: 800; font-size: 8.5px; letter-spacing: .04em; cursor: pointer; padding: 3px 1px; white-space: nowrap; }
.dj-bft.active { background: linear-gradient(180deg, #ffcc4a, #d98f10); color: #1a1206; border-color: transparent; box-shadow: 0 0 8px rgba(255,204,74,.5); }
.dj-bfreq { flex: 0 0 auto; min-width: 32px; }
.dj-bfreq.active { background: linear-gradient(180deg, #b9c3d6, #8b95a8); color: #10131a; border-color: transparent; }
.dj-bfx-row { display: flex; gap: 4px; width: 100%; align-items: center; }
.dj-bfx-beat { flex: 1; text-align: center; font-size: 10px; font-weight: 800; color: #ffb36b; white-space: nowrap; }
.dj-bfx-beat-dn, .dj-bfx-beat-up { min-width: 26px; min-height: 20px; border-radius: 6px; border: 1px solid rgba(255,255,255,.16);
  background: #1b1024; color: #d9d2ee; cursor: pointer; font-size: 10px; }
.dj-bfx-ch { flex: 0 0 auto; background: #1b1024; color: #d9d2ee; border: 1px solid rgba(255,255,255,.16); border-radius: 6px;
  font-size: 10px; font-weight: 700; padding: 3px; }
.dj-mixer .dj-bfx-level { flex: 1; width: auto; accent-color: #ffcc4a; }
.dj-bfx-on { flex: 0 0 52px; min-height: 26px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #1b1024; color: #d9d2ee; font-weight: 800; font-size: 10px; cursor: pointer; }
.dj-bfx-on.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 14px rgba(45,212,255,.8); }
/* ── BEAT FX: X-PAD touch strip + AUTO/TAP · TAP · FX QUANTIZE row (DJM-style) ── */
.dj-xpad-lbl { font-size: 8px; font-weight: 800; letter-spacing: .14em; color: #7fb0ff; text-align: center; margin: 2px 0 3px; }
.dj-xpad { display: block; width: 100%; height: 34px; border-radius: 7px; cursor: crosshair; touch-action: none;
  background: linear-gradient(180deg, #05060c, #0b0f1c); border: 1px solid rgba(90,170,255,.35);
  box-shadow: inset 0 2px 8px rgba(0,0,0,.8), 0 0 6px rgba(90,170,255,.15); }
.dj-xpad.touch { border-color: rgba(120,190,255,.8); box-shadow: inset 0 2px 8px rgba(0,0,0,.8), 0 0 16px rgba(90,170,255,.6); }
.dj-bfx-tap-row { display: flex; gap: 4px; width: 100%; align-items: stretch; margin-top: 2px; }
.dj-bfx-tapbtn { flex: 1; min-height: 26px; border-radius: 7px; border: 1px solid rgba(255,255,255,.16);
  background: #12131b; color: #aeb8cc; font-weight: 800; font-size: 8.5px; letter-spacing: .04em; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 4px; white-space: nowrap; padding: 2px; }
.dj-bfx-tapbtn.on { background: linear-gradient(180deg, #2dd4ff, #147fb8); color: #06121f; border-color: transparent; box-shadow: 0 0 12px rgba(45,212,255,.7); }
.dj-bfx-tap { flex: 0 0 58px; min-height: 26px; border-radius: 999px; cursor: pointer; font-weight: 900; font-size: 11px; letter-spacing: .06em;
  color: #d7ffe0; border: 1px solid rgba(57,231,95,.5); background: radial-gradient(circle at 50% 40%, #123a1c, #071a0d);
  box-shadow: 0 0 10px rgba(57,231,95,.35), inset 0 1px 0 rgba(255,255,255,.15); }
.dj-bfx-tap:hover { box-shadow: 0 0 16px rgba(57,231,95,.6), inset 0 1px 0 rgba(255,255,255,.2); }
.dj-bfx-tap.hit { background: radial-gradient(circle at 50% 40%, #39e75f, #1c9c3c); color: #06210f; box-shadow: 0 0 22px rgba(57,231,95,.95); }
.dj-bfx-qled { width: 7px; height: 7px; border-radius: 50%; background: #3a0f10; box-shadow: inset 0 0 2px rgba(0,0,0,.8); flex: 0 0 auto; }
.dj-bfx-quant.on .dj-bfx-qled { background: #ff3b3b; box-shadow: 0 0 8px rgba(255,59,59,.9); }
/* ── LED master level meter beside the channel faders ── */
.dj-meter { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.dj-meter canvas { border-radius: 4px; background: rgba(0,0,0,.35); }
.dj-meter-lbl { font-size: 9px; font-weight: 800; letter-spacing: .08em; color: #9b8bd0; }
/* ── BROWSE (AlphaTheta-style): track list + LOAD-to-any-deck buttons ── */
.dj-browse { width: 100%; display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
.dj-browse-search { width: 100%; background: #0d0a14; color: #e6ddf6; border: 1px solid rgba(255,255,255,.16);
  border-radius: 7px; padding: 5px 8px; font-size: 12px; }
.dj-browse-list { max-height: 108px; overflow-y: auto; background: rgba(0,0,0,.28); border: 1px solid rgba(255,255,255,.1);
  border-radius: 8px; padding: 4px; display: flex; flex-direction: column; gap: 3px; }
.dj-browse-row { flex: 0 0 auto; min-height: 30px; line-height: 18px; padding: 6px 8px; border-radius: 6px; font-size: 12px;
  color: #d9d2ee; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dj-browse-row:hover { background: rgba(255,255,255,.06); }
.dj-browse-row.sel { background: linear-gradient(180deg, #b13bff, #7a1fd0); color: #fff; font-weight: 700; }
.dj-browse-empty, .dj-browse-hint { font-size: 11px; color: var(--muted, #9b8bd0); text-align: center; padding: 8px 4px; }
.dj-browse-load { display: flex; flex-wrap: wrap; gap: 5px; }
.dj-browse-loadbtn { flex: 1 1 60px; min-height: 26px; border-radius: 7px; border: 1px solid rgba(45,212,255,.45);
  background: #0d0a14; color: #9fe3ff; font-weight: 800; font-size: 11px; letter-spacing: .04em; cursor: pointer; }
.dj-browse-loadbtn b { color: #fff; }
.dj-browse-loadbtn:hover { background: rgba(45,212,255,.15); }
.dj-browse-loadbtn.done { background: linear-gradient(180deg, #39e75f, #17a83b); color: #06180b; border-color: transparent; }
#dj-4decks.on { background: linear-gradient(180deg, #b13bff, #7a1fd0) !important; color: #fff !important; border-color: transparent !important; box-shadow: 0 0 10px rgba(177,59,255,.5); }
.dj-chan { display: flex; flex-direction: column; align-items: center; gap: 8px; font-size: 12px; font-weight: 800; color: var(--dj-warm); }
.cfader { -webkit-appearance: none; appearance: none; writing-mode: vertical-lr; direction: rtl;
  width: 24px !important; height: 172px !important; border-radius: 11px; cursor: pointer;
  /* recessed slot with a bright groove running down the centre — thicker & longer (Marcelo) */
  background:
    linear-gradient(90deg, transparent 42%, rgba(255,255,255,.24) 46%, rgba(255,255,255,.24) 54%, transparent 58%),
    linear-gradient(90deg, #04030a, #1c1228 44%, #1c1228 56%, #04030a);
  border: 1px solid rgba(255,255,255,.10); box-shadow: inset 0 2px 7px rgba(0,0,0,.75); }
/* chunky cap: a crisp bright indicator line set in a subtle groove + soft glow.
   Colour = per-fader --fader-cap. */
.cfader::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 72px; height: 35px; border-radius: 9px;
  background:
    linear-gradient(180deg, transparent 42%, rgba(0,0,0,.4) 44%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 56%, transparent 58%),
    linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #b7abd8) 42%, #fff), var(--fader-cap, #b7abd8) 54%, color-mix(in srgb, var(--fader-cap, #b7abd8) 52%, #000));
  border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 4px 10px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.8), 0 0 14px var(--fader-glow, rgba(150,190,255,.55)); cursor: grab; }
.cfader::-moz-range-thumb { width: 72px; height: 35px; border-radius: 9px;
  background: linear-gradient(180deg, transparent 42%, rgba(0,0,0,.4) 44%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 56%, transparent 58%), linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #b7abd8) 42%, #fff), var(--fader-cap, #b7abd8) 54%, color-mix(in srgb, var(--fader-cap, #b7abd8) 52%, #000));
  border: 1px solid rgba(0,0,0,.55); box-shadow: 0 4px 10px rgba(0,0,0,.6), 0 0 14px var(--fader-glow, rgba(150,190,255,.55)); }
/* brighter glow while dragging */
.cfader:active::-webkit-slider-thumb { box-shadow: 0 3px 10px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.8), 0 0 22px var(--fader-glow, rgba(150,190,255,.9)); }
.cfader:active::-moz-range-thumb { box-shadow: 0 3px 10px rgba(0,0,0,.6), 0 0 22px var(--fader-glow, rgba(150,190,255,.9)); }
/* the handle cap sits IN FRONT of the equalizer meter (meter tucks behind it) for depth.
   (meter keeps position:absolute from its base rule — just set the stacking order here) */
.dj-fadwrap { position: relative; }
.dj-chan-meter { z-index: 1; }
.dj-fadwrap .cfader { position: relative; z-index: 2; }
/* per-fader colour swatch — small round box that pops the native picker */
.dj-capsw { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; padding: 0; border: 1px solid rgba(255,255,255,.35);
  border-radius: 5px; cursor: pointer; background: transparent; overflow: hidden; }
.dj-capsw::-webkit-color-swatch-wrapper { padding: 0; }
.dj-capsw::-webkit-color-swatch { border: none; border-radius: 4px; }
.dj-capsw::-moz-color-swatch { border: none; border-radius: 4px; }
/* 4-/6-deck: slimmer fader caps so they sit cleanly inside each channel — no more
   caps overlapping into each other ("mixed together"). (Marcelo) */
.dj-mixer.four-decks .cfader::-webkit-slider-thumb { width: 68px; height: 36px; }
.dj-mixer.four-decks .cfader::-moz-range-thumb { width: 68px; height: 36px; }
.dj-mixer.six-decks .cfader::-webkit-slider-thumb { width: 49px; height: 36px; }
.dj-mixer.six-decks .cfader::-moz-range-thumb { width: 49px; height: 36px; }

/* ── Crossfader — beefy glowing cap with a bright centre line on a grooved track ── */
#dj-xfade { -webkit-appearance: none; appearance: none; height: 20px; border-radius: 11px;
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10); box-shadow: inset 0 2px 7px rgba(0,0,0,.75); }
#dj-xfade::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 35px; height: 58px; border-radius: 9px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 48%, rgba(255,255,255,.9) 52%, transparent 60%),
    linear-gradient(180deg, var(--dj-warm), color-mix(in srgb, var(--dj-warm) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 18px color-mix(in srgb, var(--dj-warm) 80%, transparent), 0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45); cursor: grab; }
#dj-xfade::-moz-range-thumb { width: 35px; height: 58px; border-radius: 9px;
  background: linear-gradient(180deg, var(--dj-warm), color-mix(in srgb, var(--dj-warm) 42%, black));
  border: 1px solid rgba(0,0,0,.6); box-shadow: 0 0 18px color-mix(in srgb, var(--dj-warm) 80%, transparent); }
/* ── Authentic fader detailing on the DEFAULT mixer look ──────────────── */
/* tick-mark scales flanking the A/B channel faders */
#dj-mixer .dj-chan { position: relative; }
#dj-mixer .dj-chan::before, #dj-mixer .dj-chan::after {
  content: ''; position: absolute; top: 8px; height: 118px; width: 10px; pointer-events: none;
  background: repeating-linear-gradient(180deg, rgba(255,255,255,.26) 0 1px, transparent 1px 11px); }
#dj-mixer .dj-chan::before { left: calc(50% - 27px); }
#dj-mixer .dj-chan::after  { right: calc(50% - 27px); }
/* ruler under the crossfader */
#dj-mixer .dj-xfade-wrap { position: relative; }
#dj-mixer .dj-xfade-wrap::after {
  content: ''; position: absolute; left: 24px; right: 24px; top: calc(100% + 3px); height: 9px; pointer-events: none;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.24) 0 1px, transparent 1px 15px); }
/* MASTER — chunky themed cap with a bright centre line on a grooved track */
#dj-mixer #dj-master { -webkit-appearance: none; appearance: none; height: 13px; border-radius: 8px;
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10); box-shadow: inset 0 2px 6px rgba(0,0,0,.7); }
#dj-mixer #dj-master::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 24px; height: 32px; border-radius: 6px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.85) 48%, rgba(255,255,255,.85) 52%, transparent 60%),
    linear-gradient(180deg, var(--dj-warm), color-mix(in srgb, var(--dj-warm) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 12px color-mix(in srgb, var(--dj-warm) 65%, transparent), 0 3px 6px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.4); cursor: grab; }
#dj-mixer #dj-master::-moz-range-thumb { width: 24px; height: 32px; border-radius: 6px;
  background: linear-gradient(180deg, var(--dj-warm), color-mix(in srgb, var(--dj-warm) 42%, black)); border: 1px solid rgba(0,0,0,.6); }

/* Solid REC + rocket like the reference */
#dj-mixer .dj-rec { background: linear-gradient(180deg, #f0531f, #b83012); color: #fff; border-color: rgba(255,120,60,.6); }
/* While recording, override the themed orange with a clear pulsing red so it's
   obvious the mix is being captured (the live timer counts up in the label). */
#dj-mixer .dj-rec.on { background: #e11d48; border-color: rgba(255,120,140,.8); box-shadow: 0 0 16px rgba(225,29,72,.75); }
#dj-mixer .dj-rocket { background: linear-gradient(180deg, var(--dj-warm), color-mix(in srgb, var(--dj-warm) 52%, black)); color: #fff; border-color: color-mix(in srgb, var(--dj-warm) 55%, transparent); }

/* Pro / Classic style switch */
/* Equalizer colour-theme swatches */
.dj-eq-themes { display: flex; gap: 6px; flex-wrap: wrap; }
.dj-eqc { width: 30px; height: 24px; border-radius: 6px; border: 2px solid transparent;
  cursor: pointer; padding: 0; box-shadow: 0 1px 3px rgba(0,0,0,.5); }
.dj-eqc:hover { transform: scale(1.08); }
.dj-eqc.active { border-color: #fff; box-shadow: 0 0 8px rgba(255,255,255,.6); }
/* Custom EQ colour pickers */
.dj-eq-custom { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.dj-eq-custom.active { outline: 1px solid rgba(255,255,255,.4); outline-offset: 3px; border-radius: 6px; }
.dj-eq-custom-lbl { font-size: 11px; font-weight: 700; color: #c9a8ff; margin-right: 2px; }
.dj-eqcc { width: 28px; height: 24px; padding: 0; border: 1px solid rgba(255,255,255,.25);
  border-radius: 6px; background: transparent; cursor: pointer; }
.dj-eqcc::-webkit-color-swatch { border: none; border-radius: 4px; }
.dj-eqcc::-webkit-color-swatch-wrapper { padding: 2px; }
/* Live colourful equalizer/visualiser */
.dj-eq-viz { width: 100%; height: 66px; display: block; border-radius: 10px;   /* slimmer & more compact (Marcelo) */
  background: linear-gradient(180deg, #0a0616, #05040c);
  border: 1px solid rgba(255,255,255,.08); box-shadow: inset 0 0 18px rgba(0,0,0,.6); }
/* Per-deck EQ windows — a tidy fixed height matched to the tempo fader beside it */
.dj-eq-deck { margin-top: 10px; flex: 0 0 auto; height: 118px; min-height: 0; }
/* Centre EQ = the combined master mix — compact fixed height */
.dj-eq-center { flex: 0 0 auto; height: 76px; min-height: 0; margin-top: 0; align-self: stretch; }
/* Panel option: which equalizer windows are shown */
#dj-mixer.eq-only-master .dj-eq-deck { display: none; }
#dj-mixer.eq-off .dj-eq-viz,
#dj-mixer.eq-off .dj-eq-center-lbl { display: none; }
/* Stage equalizer — a movable/resizable window on the stage. The host drags the
   box + edges/corners; the box (x,y,w,h) syncs to the broadcast. */
.stage-eq-box { position: absolute; left: 0; bottom: 0; width: 100%; height: 34%;
  z-index: 6; cursor: move; }
.stage-eq-box.hidden { display: none; }
.stage-eq-box > canvas { width: 100%; height: 100%; display: block; pointer-events: none; }
.stage-eq-box:hover { outline: 1px dashed rgba(255,255,255,.45); outline-offset: -1px; }
.seqb-h { position: absolute; background: rgba(255,255,255,.9); border: 1px solid rgba(0,0,0,.5);
  border-radius: 3px; opacity: 0; transition: opacity .15s; }
.stage-eq-box:hover .seqb-h { opacity: .95; }
.seqb-n, .seqb-s { left: 50%; width: 30px; height: 9px; margin-left: -15px; cursor: ns-resize; }
.seqb-n { top: -5px; } .seqb-s { bottom: -5px; }
.seqb-e, .seqb-w { top: 50%; width: 9px; height: 30px; margin-top: -15px; cursor: ew-resize; }
.seqb-e { right: -5px; } .seqb-w { left: -5px; }
.seqb-ne, .seqb-nw, .seqb-se, .seqb-sw { width: 13px; height: 13px; }
.seqb-ne { top: -7px; right: -7px; cursor: nesw-resize; }
.seqb-nw { top: -7px; left: -7px; cursor: nwse-resize; }
.seqb-se { bottom: -7px; right: -7px; cursor: nwse-resize; }
.seqb-sw { bottom: -7px; left: -7px; cursor: nesw-resize; }
.stage-eq-shape-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
#stage-eq-btn.active { background: var(--accent); color: #fff; }
.dj-cue-device { width: 100%; padding: 7px 8px; border-radius: 8px; background: #0a0710;
  color: #e7dcff; border: 1px solid rgba(255,255,255,.16); font-size: 12px; cursor: pointer; }
/* 🎧 Headphone cue (pre-listen) button on each deck */
.dj-hp { padding: 9px 0; border-radius: 10px; border: 1px solid rgba(255,255,255,.12);
  background: #1a1020; color: #d9d2ee; font-size: 16px; cursor: pointer; transition: background .12s; }
.dj-hp:hover { background: #241533; }
.dj-hp.on { background: var(--dj-warm, #ff8a3d); color: #201; border-color: var(--dj-warm, #ff8a3d);
  box-shadow: 0 0 12px color-mix(in srgb, var(--dj-warm, #ff8a3d) 60%, transparent); }
/* DJ deck video layer — VJ video from the decks, behind the people tiles */
#dj-stage-video { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
#dj-stage-video video { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity .18s linear; }
.dj-eq-shape-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
.eq-mini { flex: 0 0 auto; min-width: 34px; padding: 6px 8px; border-radius: 7px;
  border: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.05);
  color: #d9d2ee; font-size: 13px; font-weight: 700; cursor: pointer; }
.eq-mini.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.eq-row-lbl { font-size: 11px; font-weight: 700; color: #9b8bd0; margin-right: 2px; }
/* Stage-EQ control block that sits in the empty space under each deck */
.stage-eq-ctl { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.stage-eq-btn.active { background: var(--accent); color: #fff; }
.stage-eq-seg { display: flex; gap: 5px; justify-content: center; }
.stage-eq-seg .eq-mini { flex: 1 1 0; text-align: center; }
/* 🌈/🎨 colour row + 🔅…🔆 brightness row for the stage equalizer */
.stage-eq-seg[data-eqseg="bright"], .stage-eq-seg[data-eqseg="cyclespeed"] { align-items: center; gap: 6px; }
.eq-bright { flex: 1; accent-color: #ffd24a; min-width: 0; }
.eq-cyclespeed { flex: 1; accent-color: #b16bff; min-width: 0; }
.eq-bright-lbl { font-size: 13px; flex: 0 0 auto; }
.dj-eq-center-lbl { font-size: 10px; font-weight: 800; letter-spacing: .08em; text-align: center;
  color: #9b8bd0; margin: 2px 0 2px; }   /* tighter — borrow the extra gap for the fader spacing above (Marcelo) */
.dj-seg-label { font-size: 11px; font-weight: 700; color: #c9a8ff; letter-spacing: .03em; margin: 0 0 5px 2px; }
.dj-style-seg { display: flex; gap: 4px; width: 100%; background: #0a0710; border-radius: 8px; padding: 3px; }
.dj-style-seg button { flex: 1; padding: 5px 0; border: 0; border-radius: 6px; background: transparent; color: #c9a8ff; font-size: 11px; font-weight: 700; letter-spacing: .04em; cursor: pointer; }
.dj-style-seg button.active { background: var(--dj-accent2); color: #fff; }

/* CLASSIC layout: channel volumes + crossfader as simple horizontal sliders */
#dj-mixer.classic .dj-chans { flex-direction: column; gap: 12px; align-items: stretch; padding: 6px 8px; }
#dj-mixer.classic .dj-chan { flex-direction: row; gap: 10px; align-items: center; }
#dj-mixer.classic .cfader { writing-mode: horizontal-tb; direction: ltr; width: 100% !important; height: 6px !important; border-radius: 5px;
  background: linear-gradient(90deg, #05030a, #241430, #05030a); }
#dj-mixer.classic .cfader::-webkit-slider-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--dj-accent); }
#dj-mixer.classic .cfader::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--dj-accent); }
#dj-mixer.classic #dj-xfade { height: 6px; }
#dj-mixer.classic #dj-xfade::-webkit-slider-thumb { width: 16px; height: 16px; border-radius: 50%; }
#dj-mixer.classic #dj-xfade::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; }

/* ══ "Hardware" skin — dark studio-metal panel with tick-marked faders ══
   A second look users can pick (⚙️ → Hardware). Only recolors the shell +
   adds fader tick marks; all controls work exactly the same. */
#dj-mixer.skin-hardware.dj-mixer {
  background: radial-gradient(120% 140% at 50% 0%, rgba(120,130,150,.14), transparent 60%), #0e0f13;
  border-color: rgba(255,255,255,.14);
  box-shadow: 0 0 24px rgba(0,0,0,.55), inset 0 0 44px rgba(0,0,0,.5);
}
#dj-mixer.skin-hardware .dj-deck { background: linear-gradient(180deg, #1b1d23, #101216); border-color: rgba(255,255,255,.06); }
#dj-mixer.skin-hardware .dj-center { background: linear-gradient(180deg, #1b1d23, #101216); }
#dj-mixer.skin-hardware .dj-screen,
#dj-mixer.skin-hardware .dj-panel { background: #0a0b0e; }
/* darker, metal channel caps (light strip on top, like the reference) */
/* Hardware skin ONLY: tall vertical faders (picture 1). Pro/Classic keep their
   own compact controls (picture 2) — this height is scoped to skin-hardware. */
#dj-mixer.skin-hardware .cfader { height: 150px !important; }
#dj-mixer.skin-hardware .cfader::-webkit-slider-thumb {
  width: 52px !important; height: 30px;
  background:
    linear-gradient(180deg, #eef1f7 0 14%, #c2c7d4 14% 38%, #3a3e47 40%, #191b21 100%);
  border: 1px solid rgba(0,0,0,.75); border-radius: 5px;
  box-shadow: 0 4px 9px rgba(0,0,0,.75), inset 0 1px 0 rgba(255,255,255,.7);
}
#dj-mixer.skin-hardware .cfader::-moz-range-thumb {
  width: 52px; height: 30px; border-radius: 5px;
  background: linear-gradient(180deg, #eef1f7 0 14%, #c2c7d4 14% 38%, #3a3e47 40%, #191b21 100%); border: 1px solid rgba(0,0,0,.75);
}
/* tick-mark ladders flanking each channel fader */
#dj-mixer.skin-hardware .dj-chan { position: relative; }
#dj-mixer.skin-hardware .dj-chan::before,
#dj-mixer.skin-hardware .dj-chan::after {
  content: ''; position: absolute; top: 4px; height: 150px; width: 12px; pointer-events: none;
  background: repeating-linear-gradient(180deg, rgba(255,255,255,.32) 0 2px, transparent 2px 15px);
}
#dj-mixer.skin-hardware .dj-chan::before { left: calc(50% - 30px); }
#dj-mixer.skin-hardware .dj-chan::after  { right: calc(50% - 30px); }
/* tick-mark ruler under the crossfader */
#dj-mixer.skin-hardware .dj-xfade-wrap { position: relative; }
#dj-mixer.skin-hardware .dj-xfade-wrap::after {
  content: ''; position: absolute; left: 26px; right: 26px; top: calc(100% + 3px); height: 11px; pointer-events: none;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.32) 0 2px, transparent 2px 17px);
}
#dj-mixer.skin-hardware .dj-xfade-label { color: #aeb6c6; }
#dj-mixer.skin-hardware .dj-master { color: #aeb6c6; }
/* red REC + rocket buttons, like the reference hardware */
#dj-mixer.skin-hardware .dj-rec,
#dj-mixer.skin-hardware .dj-rocket {
  background: linear-gradient(180deg, #e6402c, #b3271a); border-color: rgba(255,120,90,.55); color: #fff;
}
#dj-mixer.skin-hardware .dj-rec.on { background: #e11d48; border-color: rgba(255,120,140,.8); }
/* white BPM readout on the dark screen (the orange bars stay) */
#dj-mixer.skin-hardware .dj-bpm { color: #f4f6fb; text-shadow: none; }
/* twin centre rail running between the two channel faders */
#dj-mixer.skin-hardware .dj-chans { position: relative; }
#dj-mixer.skin-hardware .dj-chans::before {
  content: ''; position: absolute; left: 50%; top: -3px; transform: translateX(-50%);
  width: 14px; height: 162px; pointer-events: none; border-radius: 4px;
  background:
    linear-gradient(90deg, transparent 0 18%, rgba(210,216,230,.55) 20% 34%, transparent 36% 64%, rgba(210,216,230,.55) 66% 80%, transparent 82%);
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.6));
}
/* Hardware forces its OWN fixed colours so the chosen accent theme (pink, blue…)
   never tints it — it always looks like the reference: orange crossfader,
   orange BPM bars, steel A/B labels. */
#dj-mixer.skin-hardware #dj-xfade::-webkit-slider-thumb {
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.92) 48%, rgba(255,255,255,.92) 52%, transparent 60%),
    linear-gradient(180deg, #f7942f, #af520c);
  box-shadow: 0 0 18px rgba(247,148,47,.85), 0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45);
}
#dj-mixer.skin-hardware #dj-xfade::-moz-range-thumb {
  background: linear-gradient(180deg, #f7942f, #af520c); box-shadow: 0 0 18px rgba(247,148,47,.85);
}
#dj-mixer.skin-hardware .dj-bpm-ico { color: #f7942f; }
#dj-mixer.skin-hardware .dj-xfade-wrap { color: #f7942f; }
#dj-mixer.skin-hardware .dj-chan { color: #cfd4de; }
#dj-mixer.skin-hardware #dj-master { accent-color: #f7942f; }

/* Profile badge + settings gear */
.dj-profile { display: flex; align-items: center; gap: 6px; width: 100%; padding: 4px 6px; border-radius: 999px;
  background: #0a0710; border: 1px solid color-mix(in srgb, var(--dj-accent) 40%, transparent); }
.dj-avatar { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 40% 35%, var(--dj-accent2), #120a1c); font-size: 13px; }
.dj-xp { flex: 1; text-align: center; font-weight: 800; font-size: 12px; color: var(--dj-warm); letter-spacing: .06em; }
.dj-gear { width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%;
  border: 1px solid rgba(255,255,255,.28); background: rgba(255,255,255,.10);
  color: #e7dcff; font-size: 15px; cursor: pointer; transition: transform .15s; }
.dj-gear:hover { transform: rotate(35deg); background: rgba(255,255,255,.18); }
.dj-gear.on { color: var(--dj-warm); border-color: var(--dj-warm); }
.dj-settings { width: 100%; padding: 8px; border-radius: 10px; background: #0a0710; border: 1px solid rgba(255,255,255,.1); margin-top: 2px; }
/* Skin/settings panel now sits on its OWN full-width row of the mixer grid (not the
   skinny 150px center column), so opening it no longer stretches the decks into a
   tall strip of empty space. Its four groups flow into responsive columns. */
/* Masonry-style column flow: each group packs up to the TOP of its column and
   fills the gaps, so short sections (like the Equalizer) squeeze up into the
   empty space instead of being stranded at the bottom of the tall Skin column. */
#dj-mixer > .dj-settings:not(.hidden) {
  grid-column: 1 / -1;
  display: block;
  column-width: 250px;
  column-gap: 26px;
  column-rule: 1px solid rgba(255,255,255,.07);
  padding: 14px;
  margin-top: 4px;
}
.dj-set-group {
  break-inside: avoid; -webkit-column-break-inside: avoid;
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
  margin-bottom: 16px;
}
.dj-set-group:last-child { margin-bottom: 0; }
@media (max-width: 520px) {
  #dj-mixer > .dj-settings:not(.hidden) { column-width: auto; column-count: 1; column-rule: 0; }
}
.dj-help { width: 24px; height: 24px; border-radius: 50%; border: 1px solid rgba(255,255,255,.22); background: transparent; color: #c9a8ff; font-weight: 800; font-size: 13px; cursor: pointer; }
.dj-help:hover { color: var(--dj-warm); border-color: var(--dj-warm); }

/* First-time tips overlay */
/* Tips popup: a centered floating card pinned to the viewport, so pressing "?"
   always shows it in view — even when the mixer is scaled up taller than the
   screen (an absolute inset:0 overlay would center off-screen in a tall mixer). */
.dj-tips { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 400;
  width: min(520px, calc(100vw - 40px)); max-height: 82vh; overflow: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 22px; text-align: center; border-radius: 16px;
  background: rgba(14,9,20,.98); border: 1px solid rgba(255,120,180,.35);
  box-shadow: 0 24px 70px rgba(0,0,0,.6); backdrop-filter: blur(3px); }
.dj-tips > b { font-size: 16px; color: var(--dj-warm); }
.dj-tips ul { list-style: none; padding: 0; margin: 6px 0; display: grid; gap: 7px; max-width: 460px; text-align: left; color: #e9d8ff; font-size: 13px; line-height: 1.35; }
.dj-tips-ok { margin-top: 6px; padding: 9px 26px; border-radius: 8px; border: 0; background: var(--dj-accent2); color: #fff; font-weight: 800; cursor: pointer; }
.dj-tips-ok:hover { filter: brightness(1.12); }
/* 🎓 DJ School — paged beginner lessons inside the tips dialog */
.dj-school-card { max-width: 440px; text-align: center; margin: 10px auto 4px; }
.dj-school-card b { display: block; font-size: 15px; color: var(--dj-warm); margin-bottom: 7px; }
.dj-school-card p { margin: 0; color: #e9d8ff; font-size: 13.5px; line-height: 1.55; min-height: 84px; }
.dj-school-nav { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 10px 0 4px; }
.dj-school-nav button { padding: 7px 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,.25); background: rgba(255,255,255,.08); color: #fff; font-weight: 700; cursor: pointer; }
.dj-school-nav button:hover { background: rgba(255,255,255,.16); }
.dj-school-dots { display: flex; gap: 6px; }
.dj-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.25); cursor: pointer; }
.dj-dot.on { background: var(--dj-warm); }

/* 📚 LIBRARY — recordings with per-plan storage */
.lib-usage { margin: 8px 0 10px; }
.lib-usage-bar { height: 8px; border-radius: 6px; background: rgba(255,255,255,.12); overflow: hidden; }
#lib-usage-fill { height: 100%; width: 0%; border-radius: 6px; background: linear-gradient(90deg, #7c5cff, #b14aff); transition: width .3s; }
#lib-usage-fill.warn { background: linear-gradient(90deg, #ff9d1c, #ff2d6b); }
/* Full: the bar is clamped at 100% but the colour says you're out of room. */
#lib-usage-fill.over, #home-storage-fill.over { background: linear-gradient(90deg, #ff2d6b, #ff2d6b); }
.lib-over { color: #ffb3c8; }
.lib-player-box { margin: 10px 0; }
.lib-player-box video { width: 100%; border-radius: 10px; background: #000; max-height: 220px; }
.lib-player-box .ctrl { margin-top: 6px; }
.lib-list { display: grid; gap: 8px; margin-top: 10px; }
/* 📼 The recordings slider: 341px = five and a half of its 62px rows, so the cut is a peek that
   says "there is more" rather than an ending — the same rule as the studio
   and project lists. A library of forty recordings no longer stretches the
   page; it scrolls in its own window. */
.lib-list-slider { max-height: 341px; overflow-y: auto; overscroll-behavior: contain;
  padding-inline-end: 4px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent; }
.lib-list-slider::-webkit-scrollbar { width: 8px; }
.lib-list-slider::-webkit-scrollbar-track { background: transparent; }
.lib-list-slider::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); border-radius: 99px;
  border: 2px solid transparent; background-clip: content-box; }
.lib-list-slider::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); background-clip: content-box; }
/* the monitor's own controls, under the picture */
.lib-player-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
/* min-width: 0 — without it the row keeps its natural 453px inside a 230px
   list and simply hangs out of the panel, taking ▶ ✏️ ⬇ 🗑 off the screen with
   it. And wrap, so when the title and the controls genuinely cannot share one
   line the controls drop to their own line instead of being squeezed to
   nothing: they are what the row is for. */
.lib-item { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px; border-radius: 10px; background: rgba(255,255,255,.06); min-width: 0; }
.lib-item:hover { background: rgba(255,255,255,.1); }
.lib-thumb { flex: 0 0 46px; height: 46px; border-radius: 8px; background: rgba(124,92,255,.25); display: flex; align-items: center; justify-content: center; font-size: 20px; cursor: pointer; }
.lib-meta { flex: 1 1 130px; min-width: 0; }
.lib-title { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lib-sub { font-size: 11px; opacity: .65; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The controls are what the row is FOR — they hold their size and the title
   gives way, never the other way round. */
.lib-actions { display: flex; gap: 4px; flex: 0 0 auto; margin-left: auto; }
.lib-actions button { width: 28px; height: 28px; border-radius: 7px; border: 0; background: rgba(255,255,255,.1); color: #fff; cursor: pointer; font-size: 13px; }
.lib-actions button:hover { background: rgba(255,255,255,.2); }
.lib-actions .lib-del:hover { background: rgba(255,60,80,.5); }
#lib-record.recording { background: rgba(255,45,80,.35); border-color: rgba(255,45,80,.7); animation: libRecPulse 1.2s ease-in-out infinite; }
.golive-top.recording { background: linear-gradient(90deg, #ff2d50, #c81e3c) !important; animation: libRecPulse 1.2s ease-in-out infinite; }
.golive-top.record-mode { background: linear-gradient(90deg, #ff5a3c, #ff2d6b) !important; }
/* Header quality picker — compact, sits left of Go live / Record */
.hdr-res { height: 32px; padding: 0 6px; border-radius: 8px; border: 1px solid rgba(255,255,255,.25);
  background: rgba(255,255,255,.08); color: #fff; font-size: 12px; font-weight: 700; cursor: pointer; }
.hdr-res:hover { background: rgba(255,255,255,.16); }
/* 📺 DJ booth monitor: while the mixer is open, the live stage shrinks into a
   floating mini-monitor pinned top-right, so the DJ always sees the show. */
@media (min-width: 901px) {
  body.dj-monitor #stage {
    position: fixed; right: 14px; bottom: 160px;
    width: 322px; height: 181px; min-height: 181px; z-index: 500;
    border-radius: 10px; overflow: hidden;
    border: 2px solid rgba(255,255,255,.3);
    box-shadow: 0 8px 28px rgba(0,0,0,.65);
  }
  /* 🪑 Backstage mini-panel — docked right under the stage monitor */
  body.dj-monitor .backstage {
    position: fixed; right: 14px; bottom: 14px;
    width: 322px; height: 132px; min-height: 132px; z-index: 500;
    border-radius: 10px; overflow: auto;
    border: 2px solid rgba(255,255,255,.3);
    box-shadow: 0 8px 28px rgba(0,0,0,.65);
    background: var(--panel);
  }
  /* 📱 Short mode: the booth monitor is phone-shaped (the stage IS vertical) */
  body.dj-monitor #stage.vertical {
    width: 181px; height: 322px; min-height: 322px;
  }
  body.dj-monitor #stage::after {
    content: '📺 STAGE — live'; position: absolute; left: 6px; bottom: 4px;
    font-size: 10px; font-weight: 800; letter-spacing: .5px; color: #fff;
    background: rgba(0,0,0,.55); padding: 2px 7px; border-radius: 6px; pointer-events: none;
  }
  /* Drag bar (top strip) + 4 corner resize handles — only exist in monitor mode */
  body.dj-monitor .djmon-drag { position: absolute; left: 0; top: 0; right: 0; height: 26px; cursor: move; z-index: 60; }
  body.dj-monitor .djmon-drag:hover { background: linear-gradient(180deg, rgba(255,255,255,.18), transparent); }
  body.dj-monitor .djmon-rs { position: absolute; width: 16px; height: 16px; z-index: 61; }
  body.dj-monitor .djmon-rs:hover { background: rgba(255,255,255,.3); border-radius: 4px; }
  body.dj-monitor .djmon-nw { left: 0; top: 0; cursor: nwse-resize; }
  body.dj-monitor .djmon-ne { right: 0; top: 0; cursor: nesw-resize; }
  body.dj-monitor .djmon-sw { left: 0; bottom: 0; cursor: nesw-resize; }
  body.dj-monitor .djmon-se { right: 0; bottom: 0; cursor: nwse-resize; }
}
/* 📺+📱 BOTH mode: a glowing 9:16 window on the wide monitor — the DJ (and
   the host arranging the stage) always sees what phone viewers would get.
   Becomes a true second live monitor when dual output (Premium) arrives. */
/* Both mode's phone window used to be two dashed lines run straight through
   the picture — on the booth monitor they read as "something broke", not
   "this is the Shorts slice" (Marcelo). The camera-style fix was already
   scaffolded here with the 2000px shadow at alpha 0: DIM what's outside the
   window instead. The middle now visibly IS the phone view, the edges stay
   readable underneath, and .stage's overflow:hidden clips the shadow. */
#djmon-crop { display: none; position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  aspect-ratio: 9 / 16; height: 100%; border: 2px solid rgba(255, 90, 160, .9); border-radius: 8px;
  pointer-events: none; z-index: 55; box-shadow: 0 0 0 2000px rgba(0, 0, 0, .42); }
/* The chip is a real BUTTON now: tapping it flips the dim to full black, so
   the window shows EXACTLY what the 9:16 audience gets — the pixel-identical
   answer to "does my Shorts view look right?" a Host streaming to both
   platforms needs (Marcelo). Tap again to bring the context back. */
#djmon-crop .djmc-btn { position: absolute; top: 4px; left: 50%; transform: translateX(-50%);
  font-size: 10px; font-weight: 800; letter-spacing: .4px; color: #fff; border: 0; cursor: pointer;
  background: rgba(0, 0, 0, .6); padding: 2px 7px; border-radius: 6px; pointer-events: auto; }
#djmon-crop .djmc-btn:hover { background: rgba(255, 90, 160, .55); }
#djmon-crop.peek { box-shadow: 0 0 0 2000px rgba(0, 0, 0, 1); border-color: rgba(255, 90, 160, .45); }
#djmon-crop.on { display: block; }
/* 📱 Both mode's floating phone monitor (both-monitor.js) — a second, live
   monitor showing exactly the 9:16 slice. Exists ONLY while the broadcast
   shape is Both; drag/resize chrome is the DJ floats' own. Default spot:
   top-right under the header, clear of the booth monitor's bottom-right. */
#both-monitor { position: fixed; right: 14px; top: 64px; width: 181px; height: 322px; min-height: 322px;
  z-index: 520; border-radius: 10px; overflow: hidden; background: #000;
  border: 2px solid rgba(255, 90, 160, .55); box-shadow: 0 8px 28px rgba(0, 0, 0, .65); }
#both-monitor .bm-holder { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
#both-monitor .bm-stage { position: absolute; left: 0; top: 0; pointer-events: none !important; }
#both-monitor::after { content: '📱 9:16'; position: absolute; left: 50%; bottom: 4px; transform: translateX(-50%);
  font-size: 10px; font-weight: 800; letter-spacing: .4px; color: #fff;
  background: rgba(0, 0, 0, .55); padding: 2px 7px; border-radius: 6px; pointer-events: none; }
/* Only inside the studio — at boot applyStageAR runs while the Host is still
   on Home, and a Both-mode account got a frozen phone monitor floating over
   the Home page (Marcelo's screenshot). */
#both-monitor { display: none; }
body.studio-open #both-monitor { display: block; }
/* Its drag bar and corner handles work in EVERY mode. The generic rules gate
   BOTH visibility AND geometry behind body.dj-monitor — so in studio mode the
   bar was visible but ZERO-HEIGHT: nothing to grab, "it's stuck in the
   corner" (Marcelo). Full self-sufficient chrome, dj-monitor or not: */
#both-monitor .djmon-drag { display: block; position: absolute; left: 0; top: 0; right: 0; height: 26px; cursor: move; z-index: 60; }
#both-monitor .djmon-drag:hover { background: linear-gradient(180deg, rgba(255,255,255,.18), transparent); }
#both-monitor .djmon-rs { display: block; position: absolute; width: 16px; height: 16px; z-index: 61; }
#both-monitor .djmon-rs:hover { background: rgba(255,255,255,.3); border-radius: 4px; }
#both-monitor .djmon-nw { left: 0; top: 0; cursor: nwse-resize; }
#both-monitor .djmon-ne { right: 0; top: 0; cursor: nesw-resize; }
#both-monitor .djmon-sw { left: 0; bottom: 0; cursor: nesw-resize; }
#both-monitor .djmon-se { right: 0; bottom: 0; cursor: nwse-resize; }
/* Outside monitor mode the helper elements are inert and invisible. */
.djmon-drag, .djmon-rs { display: none; }
body.dj-monitor .djmon-drag, body.dj-monitor .djmon-rs { display: block; }
/* ── 🎹 GROOVEBOX — Circuit-style step sequencer under the DJ mixer ── */
.groovebox { --gb-accent: #b14aff; --gb-drum: #e553c0; --gb-note: #4a9bff; --gb-warm: #ffb347; --gb-bg: #0c0c10;
  --gb-jewel: var(--gb-accent);   /* 💎 jewels follow the theme unless the gem picker overrides */
  /* 🧊 FIXED-SHAPE groovebox: laid out at one design width, zoom-scaled as a
     unit (fitGroove) — same law as the DJ mixer and the SS-1 (Marcelo). */
  width: 1700px; margin-left: auto; margin-right: auto;
  margin-top: 10px; padding: 14px; border-radius: 14px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--gb-bg) 84%, #ffffff 7%), var(--gb-bg));
  border: 1px solid rgba(255,255,255,.14); box-shadow: 0 10px 30px rgba(0,0,0,.5) inset, 0 4px 18px rgba(0,0,0,.45); }
.groovebox.hidden { display: none; }
.gb-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.gb-logo { font-weight: 900; letter-spacing: 2px; font-size: 13px; color: #e7e9f0; }
.gb-play { width: 44px; height: 34px; border-radius: 9px; border: 1px solid rgba(255,255,255,.25);
  background: #17181d; color: #7ee787; font-size: 15px; font-weight: 900; cursor: pointer; }
.gb-play.on { background: #7ee787; color: #0c0c10; box-shadow: 0 0 14px rgba(126,231,135,.5); }
.gb-clear { padding: 6px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #17181d; color: #ff7b8a; font-weight: 700; cursor: pointer; font-size: 11px; }
/* Pattern banks A/B/C/D — Circuit-style song sections */
.gb-banks-wrap { display: flex; align-items: center; gap: 6px; }
.gb-banks-lbl { font-size: 11px; color: #9aa3b5; font-weight: 700; }
.gb-banks { display: flex; gap: 4px; }
.gb-bank { width: 30px; height: 30px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #17181d; color: #cfd6e4; font-weight: 900; font-size: 13px; cursor: pointer; }
.gb-bank:hover { background: #23252c; }
.gb-bank.empty { opacity: .4; }
.gb-bank.on { background: var(--gb-accent); color: #fff; border-color: var(--gb-accent); box-shadow: 0 0 12px var(--gb-accent); opacity: 1; }
.gb-copy { padding: 6px 9px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #17181d; color: #cfd6e4; font-weight: 700; cursor: pointer; font-size: 11px; }
.gb-copy:hover { background: #23252c; }
/* Mixer / Sidechain / Lock tool buttons */
.gb-tools { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.gb-scale-wrap { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #9aa3b5; }
.gb-scale { background: #17181d; color: #cfd6e4; border: 1px solid rgba(255,255,255,.2); border-radius: 8px; padding: 5px 7px; font-size: 11px; font-weight: 700; cursor: pointer; }
.gb-oct { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #9aa3b5; font-weight: 700; }
.gb-oct button { width: 22px; height: 24px; border-radius: 6px; border: 1px solid rgba(255,255,255,.2); background: #17181d; color: #cfd6e4; cursor: pointer; font-size: 10px; }
.gb-oct button:hover { background: #23252c; }
.gb-oct b { color: var(--gb-warm); min-width: 16px; text-align: center; }
.gb-tool { padding: 6px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #17181d; color: #cfd6e4; font-weight: 700; cursor: pointer; font-size: 11px; }
.gb-tool:hover { background: #23252c; }
.gb-tool.on { background: var(--gb-accent); color: #fff; border-color: var(--gb-accent); box-shadow: 0 0 10px var(--gb-accent); }
/* Color theme swatches */
.gb-themes { display: inline-flex; gap: 5px; align-items: center; }
.gb-swatch { width: 20px; height: 20px; border-radius: 50%; border: 2px solid rgba(255,255,255,.2); cursor: pointer; padding: 0; }
.gb-swatch:hover { transform: scale(1.15); }
.gb-swatch.on { border-color: #fff; box-shadow: 0 0 8px rgba(255,255,255,.5); }
/* Custom colour picker swatch — shows a rainbow ring, opens the native picker */
.gb-swatch-custom { -webkit-appearance: none; appearance: none; padding: 0; cursor: pointer;
  background: conic-gradient(red, #ff0, lime, cyan, blue, magenta, red); }
.gb-swatch-custom::-webkit-color-swatch-wrapper { padding: 2px; }
.gb-swatch-custom::-webkit-color-swatch { border: none; border-radius: 50%; }
.gb-swatch-custom::-moz-color-swatch { border: none; border-radius: 50%; }
/* Background colour picker — a rounded-square swatch labelled BG */
.gb-bg-lbl { font-size: 10px; font-weight: 800; color: #9aa3b5; margin-left: 4px; letter-spacing: .5px; }
.gb-swatch-bg { -webkit-appearance: none; appearance: none; padding: 0; cursor: pointer; border-radius: 6px !important; background: none; }
.gb-swatch-bg::-webkit-color-swatch-wrapper { padding: 0; }
.gb-swatch-bg::-webkit-color-swatch { border: none; border-radius: 4px; }
.gb-swatch-bg::-moz-color-swatch { border: none; border-radius: 4px; }
/* Projects: prev/next nav + save/load panel */
.gb-proj-nav { display: inline-flex; align-items: center; gap: 8px; }
.gb-proj-panel { margin: 8px 0; padding: 10px; border-radius: 10px; background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); }
.gb-proj-panel.hidden { display: none; }
.gb-proj-save-btn { display: block; width: 100%; padding: 8px; border-radius: 8px; border: 2px dashed #7ee787; background: rgba(126,231,135,.06); color: #7ee787; font-weight: 800; cursor: pointer; margin-bottom: 8px; }
.gb-proj-save-btn:hover { background: rgba(126,231,135,.14); }
.gb-proj-list { display: grid; gap: 5px; }
.gb-proj-empty { font-size: 12px; color: #7c8598; text-align: center; padding: 4px; }
.gb-proj-row { display: flex; gap: 6px; }
.gb-proj-row.on .gb-proj-load { border-color: var(--gb-accent); color: #fff; box-shadow: 0 0 8px var(--gb-accent); }
.gb-proj-load { flex: 1; text-align: left; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,.15); background: #17181d; color: #cfd6e4; font-weight: 700; cursor: pointer; }
.gb-proj-load:hover { background: #23252c; }
.gb-proj-del { width: 34px; border-radius: 8px; border: 1px solid rgba(255,255,255,.15); background: #17181d; color: #ff7b8a; cursor: pointer; }
.gb-proj-del:hover { background: rgba(255,60,80,.4); }
/* Per-track mixer panel */
.gb-mixer { display: flex; flex-wrap: wrap; gap: 16px; row-gap: 14px; justify-content: center; padding: 10px 8px; margin: 8px 0;
  background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); border-radius: 10px; }
.gb-mixer.hidden { display: none; }
.gb-mix-col { display: flex; flex-direction: column; align-items: center; gap: 5px; }
/* The channel strip wears the same fader as the parameters above it and the
   SS-1 next door: recessed slot, chunky cap, its own colour. Blue is the
   instrument default, pink the user-SFX one — the two families the strip has
   always used, now as cap colours rather than accent-color. */
.gb-mix-fader { -webkit-appearance: none; appearance: none; writing-mode: vertical-lr; direction: rtl;
  width: 12px !important; height: 78px !important; border-radius: 7px; cursor: pointer; touch-action: none;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.20) 45%, rgba(255,255,255,.20) 55%, transparent 60%),
    linear-gradient(90deg, #04030a, #1b1220 44%, #1b1220 56%, #04030a);
  border: 1px solid rgba(255,255,255,.10); box-shadow: inset 0 2px 6px rgba(0,0,0,.75); }
.gb-mix-fader::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: 30px; height: 15px; border-radius: 5px;
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.4) 43%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 57%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #4f8cff) 45%, #fff), var(--fader-cap, #4f8cff) 54%, color-mix(in srgb, var(--fader-cap, #4f8cff) 55%, #000));
  border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 3px 7px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.75), 0 0 10px color-mix(in srgb, var(--fader-cap, #4f8cff) 60%, transparent);
  cursor: grab; }
.gb-mix-fader::-moz-range-thumb { width: 30px; height: 15px; border-radius: 5px; border: 1px solid rgba(0,0,0,.55);
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.4) 43%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 57%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #4f8cff) 45%, #fff), var(--fader-cap, #4f8cff) 54%, color-mix(in srgb, var(--fader-cap, #4f8cff) 55%, #000));
  box-shadow: 0 3px 7px rgba(0,0,0,.6), 0 0 10px color-mix(in srgb, var(--fader-cap, #4f8cff) 60%, transparent); }
.gb-mix-fader:active::-webkit-slider-thumb { box-shadow: 0 2px 6px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.85), 0 0 16px color-mix(in srgb, var(--fader-cap, #4f8cff) 85%, transparent); }
.gb-mix-custom .gb-mix-fader { --fader-cap: #ff5aa0; }
/* An empty channel has nothing to mix — dim the cap rather than pretend. */
.gb-mix-custom:not(.loaded) .gb-mix-fader { --fader-cap: #4a4a58; }
.gb-mix-capcol { margin-top: 1px; }
.gb-mix-lab { font-size: 15px; }
/* Docked mixer: always-visible per-track faders on a dark screen, filling the
   empty space to the right of the master VOL fader. */
.gb-mixer.gb-mixer-dock { flex: 1 1 100%; align-self: stretch; justify-content: space-between; align-items: center;
  /* ONE line for every channel slot — no second row, no brown pockets
     (Marcelo: "spread it up, make it one line") */
  flex-wrap: nowrap; margin: 0; min-height: 108px; gap: 8px;
  background: linear-gradient(180deg, #0c0c14, #050507); border: 1px solid rgba(255,255,255,.20);
  box-shadow: inset 0 0 20px rgba(0,0,0,.55), 0 1px 0 rgba(255,255,255,.05); }
.gb-mixer.gb-mixer-dock .gb-mix-fader { height: 88px; }
.gb-mixer.gb-mixer-dock .gb-mix-lab { font-size: 18px; }
/* Divider between the 6 fixed tracks and the 6 custom SFX channels. */
.gb-mix-divider { width: 1px; align-self: stretch; background: rgba(255,255,255,.18); margin: 6px 2px; flex: 0 0 auto; }
/* Custom SFX channels — warm-coloured faders; empty ones dimmed with a ＋. */

.gb-mix-custom:not(.loaded) .gb-mix-fader { opacity: .5; }
.gb-sfx-add { width: 24px; height: 24px; border-radius: 6px; border: 1px dashed rgba(255,255,255,.45);
  background: transparent; color: #cfd3e0; font-size: 16px; line-height: 1; cursor: pointer; padding: 0; }
.gb-sfx-add:hover { border-color: var(--gb-warm, #ffb347); color: var(--gb-warm, #ffb347); }
.gb-sfx-labwrap { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.gb-sfx-play { background: transparent; border: 0; cursor: pointer; font-size: 18px; padding: 0; line-height: 1; }
.gb-sfx-play:hover { transform: scale(1.15); }
.gb-sfx-x { background: transparent; border: 0; color: #8a93a6; font-size: 9px; cursor: pointer; padding: 0; line-height: 1; }
.gb-sfx-x:hover { color: #ff5a5a; }
/* SFX picker popup */
.gb-sfx-picker { position: fixed; z-index: 100000; width: 224px; max-height: 300px; overflow-y: auto;
  background: #14101c; border: 1px solid var(--gb-accent, #7c3bff); border-radius: 12px; padding: 6px;
  box-shadow: 0 12px 40px rgba(0,0,0,.6); }
.gb-sfx-picker-head { font-size: 11px; font-weight: 800; color: #c9b6ff; padding: 4px 8px 6px; }
.gb-sfx-picker button { display: block; width: 100%; text-align: left; background: transparent; border: 0;
  color: #ece7f6; font-size: 13px; padding: 7px 8px; border-radius: 7px; cursor: pointer; }
.gb-sfx-picker button:hover { background: rgba(139,90,255,.22); }
.gb-sfx-picker-empty { font-size: 12px; color: #9aa3b5; padding: 8px; }
/* Spectrum visualiser relocated to its own full-width row below the console —
   a fixed-height strip so it never stretches to fill the column. */
.gb-viz-wrap.gb-viz-below { flex: 0 0 auto; width: 100%; align-self: stretch; height: 84px; min-height: 0; margin: 8px 0 0; }
/* 1-16 / 17-32 page toggle */
.gb-pages { display: inline-flex; gap: 3px; }
.gb-page { padding: 6px 9px; border-radius: 8px; border: 1px solid rgba(255,255,255,.2);
  background: #17181d; color: #9aa3b5; font-weight: 700; cursor: pointer; font-size: 11px; }
.gb-page:hover { background: #23252c; }
.gb-page.on { background: var(--gb-note); color: #fff; border-color: var(--gb-note); box-shadow: 0 0 10px var(--gb-note); }
.gb-sync { padding: 6px 11px; border-radius: 8px; border: 1px solid rgba(126,231,135,.4);
  background: #17181d; color: #7ee787; font-weight: 800; cursor: pointer; font-size: 11px; letter-spacing: .5px; }
.gb-sync:hover { background: #23252c; }
.gb-sync.on { background: #7ee787; color: #0c0c10; border-color: #7ee787; box-shadow: 0 0 12px rgba(126,231,135,.55); }
/* Rotary knobs — Circuit-style top row */
.gb-knobs { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; flex: 1 1 100%; }
.gb-knob { display: flex; flex-direction: column; align-items: center; gap: 2px; user-select: none; }
.gb-knob label { font-size: 10px; color: #9aa3b5; font-weight: 700; letter-spacing: .3px; }
.gb-knob .gb-kval { font-size: 10px; color: var(--gb-warm); font-weight: 800; }
.gb-dial { --a: -135deg; position: relative; width: 40px; height: 40px; border-radius: 50%;
  background: radial-gradient(circle at 50% 38%, #2b2d36, #121319 72%);
  border: 1px solid rgba(255,255,255,.14); cursor: ns-resize;
  box-shadow: 0 2px 5px rgba(0,0,0,.5), inset 0 1px 2px rgba(255,255,255,.06); touch-action: none; }
.gb-dial::before { content: ''; position: absolute; inset: -3px; border-radius: 50%;
  background: conic-gradient(from -135deg, var(--gb-accent) calc((var(--a) + 135deg)), transparent 0); opacity: .35;
  -webkit-mask: radial-gradient(circle, transparent 20px, #000 21px); mask: radial-gradient(circle, transparent 20px, #000 21px); }
.gb-ptr { position: absolute; left: 50%; top: 50%; width: 2px; height: 15px; margin-left: -1px;
  background: var(--gb-accent); border-radius: 2px; transform-origin: 50% 100%; filter: brightness(1.5);
  transform: translateY(-100%) rotate(var(--a)); box-shadow: 0 0 6px rgba(177,74,255,.9); }
/* Master Filter — the big performance knob (amber, like the Circuit) */
.gb-knob-big .gb-dial { width: 54px; height: 54px; border-color: rgba(255,179,71,.4); }
.gb-knob-big .gb-dial::before { background: conic-gradient(from -135deg, #ffb347 calc((var(--a) + 135deg)), transparent 0);
  -webkit-mask: radial-gradient(circle, transparent 27px, #000 28px); mask: radial-gradient(circle, transparent 27px, #000 28px); }
.gb-knob-big .gb-ptr { height: 21px; background: #ffd08a; box-shadow: 0 0 7px rgba(255,179,71,.95); }
.gb-knob-big label { color: #ffb347; }
/* Live spectrum visualiser — grows into the space beside the Master knob */
/* System master-volume fader — vertical hardware-style slider with tick marks. */
.gb-mvol { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 0 6px; align-self: center; }
.gb-mvol-val { font-size: 11px; font-weight: 800; color: var(--gb-warm, #ffb347); min-width: 22px; text-align: center; }
.gb-mvol-lbl { font-size: 10px; font-weight: 800; letter-spacing: .05em; color: #9aa3b5; white-space: nowrap; }
.gb-mvol-track { position: relative; width: 36px; height: 100px; display: flex; justify-content: center; }
.gb-mvol-track::before, .gb-mvol-track::after { content: ""; position: absolute; top: 8px; bottom: 8px; width: 7px;
  background-image: repeating-linear-gradient(rgba(255,255,255,.45) 0 2px, transparent 2px 11px); pointer-events: none; }
.gb-mvol-track::before { left: 1px; } .gb-mvol-track::after { right: 1px; }
.gb-mvol-slider { -webkit-appearance: none; appearance: none; writing-mode: vertical-rl; direction: rtl;
  width: 22px; height: 100px; background: transparent; cursor: pointer; margin: 0; position: relative; z-index: 1; }
.gb-mvol-slider::-webkit-slider-runnable-track { width: 6px; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg,#050506,#2a2a32,#050506); box-shadow: inset 0 0 3px #000; }
.gb-mvol-slider::-moz-range-track { width: 6px; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg,#050506,#2a2a32,#050506); box-shadow: inset 0 0 3px #000; }
.gb-mvol-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 34px; height: 17px;
  border-radius: 4px; border: 1px solid #000;
  background: linear-gradient(180deg,#52525c,#2c2c33 55%,#141418);
  box-shadow: 0 2px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.5); }
.gb-mvol-slider::-moz-range-thumb { width: 34px; height: 17px; border-radius: 4px; border: 1px solid #000;
  background: linear-gradient(180deg,#52525c,#2c2c33 55%,#141418);
  box-shadow: 0 2px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.3); }

/* Compact vertical fader (Octave, Project) — a mini version of the VOL fader. */
.gb-mini-fader { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; padding: 0 4px; }
.gb-mini-val { font-size: 10px; font-weight: 800; color: var(--gb-warm, #ffb347); min-width: 18px; text-align: center; line-height: 1; }
.gb-mini-lbl { font-size: 9px; font-weight: 800; letter-spacing: .04em; color: #9aa3b5; }
.gb-mini-track { position: relative; width: 30px; height: 58px; display: flex; justify-content: center; }
.gb-mini-track::before, .gb-mini-track::after { content: ""; position: absolute; top: 4px; bottom: 4px; width: 6px;
  background-image: repeating-linear-gradient(rgba(255,255,255,.4) 0 1.5px, transparent 1.5px 8px); pointer-events: none; }
.gb-mini-track::before { left: 1px; } .gb-mini-track::after { right: 1px; }
.gb-mini-slider { -webkit-appearance: none; appearance: none; writing-mode: vertical-rl; direction: rtl;
  width: 18px; height: 58px; background: transparent; cursor: pointer; margin: 0; }
.gb-mini-slider::-webkit-slider-runnable-track { width: 5px; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg,#050506,#26262e,#050506); box-shadow: inset 0 0 2px #000; }
.gb-mini-slider::-moz-range-track { width: 5px; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg,#050506,#26262e,#050506); }
.gb-mini-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 28px; height: 14px;
  border-radius: 3px; border: 1px solid #000; background: linear-gradient(180deg,#52525c,#2c2c33 55%,#141418);
  box-shadow: 0 2px 4px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.3); }
.gb-mini-slider::-moz-range-thumb { width: 28px; height: 14px; border-radius: 3px; border: 1px solid #000;
  background: linear-gradient(180deg,#52525c,#141418); }
.gb-mini-slider:disabled { opacity: .4; cursor: not-allowed; }

/* Resizable spectrum display: the wrapper carries the flex role; the canvas fills it.
   By default it GROWS hard to fill the empty space in the console (full height +
   all remaining width) so it never looks like a small stranded box. */
.gb-viz-wrap { position: relative; flex: 1 1 100%; width: 100%; min-width: 200px; align-self: stretch;
  height: 128px; min-height: 108px; margin-top: 6px; }
  /* the equalizer takes its WHOLE row by default (Marcelo: "look how much
     space you have — spread it up"); dragging its handles still overrides. */
.gb-viz-wrap.gb-viz-sized { flex: 0 0 auto; align-self: flex-start; min-height: 0; }
/* 🔒 the equalizer's WIDTH is locked to the full row — always (Marcelo:
   "make it longer to fill up the gap and keep it fixed like this").
   Height stays draggable via the handles; width overrides everything,
   including previously saved drag-sizes. */
.groovebox .gb-viz-wrap, .groovebox .gb-viz-wrap.gb-viz-sized { width: 100% !important; flex-basis: 100% !important; align-self: stretch; height: 128px !important; }
.groovebox .gb-viz-rh { display: none !important; }   /* the shape is FINAL — no resize handles */
/* 💎 header jewels: beat clock · XY pad · scenes (fill the top-row gap) */
.gb-jewels { display: inline-flex; align-items: center; gap: 14px; margin-left: 10px; }
.gb-clock { display: inline-flex; align-items: center; gap: 3px; padding: 8px 10px; border-radius: 10px;
  background: rgba(0,0,0,.35); border: 1px solid rgba(255,255,255,.14); }
.gb-clock-led { width: 9px; height: 9px; border-radius: 50%; background: rgba(255,255,255,.12); }
.gb-clock-led.q { background: rgba(255,255,255,.22); }
.gb-clock-led.on { background: var(--gb-jewel); box-shadow: 0 0 8px var(--gb-jewel); }
.gb-clock-led.on.pg2 { background: var(--gb-warm); box-shadow: 0 0 8px var(--gb-warm); }
.gb-clock-val { min-width: 34px; text-align: center; margin-left: 6px; font-size: 15px; color: #fff; opacity: .85; }
.gb-clock-val.pulse { color: var(--gb-jewel); text-shadow: 0 0 10px var(--gb-jewel); }
.gb-xy { position: relative; width: 150px; height: 84px; border-radius: 10px; cursor: crosshair;
  /* size restored — bigger broke the header row onto a new line (Marcelo: "put it back") */
  /* 💎 the square wears the jewel colour too — frame, grid, inner glow (Marcelo) */
  border: 1px solid color-mix(in srgb, var(--gb-jewel) 60%, rgba(255,255,255,.2));
  background-color: color-mix(in srgb, var(--gb-jewel) 12%, rgba(0,0,0,.45));
  background-image: linear-gradient(color-mix(in srgb, var(--gb-jewel) 24%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--gb-jewel) 24%, transparent) 1px, transparent 1px);
  background-size: 25% 50%;
  box-shadow: inset 0 0 16px color-mix(in srgb, var(--gb-jewel) 22%, rgba(0,0,0,.4));
  touch-action: none; }
.gb-xy-dot { position: absolute; width: 14px; height: 14px; border-radius: 50%; transform: translate(-50%, -50%);
  left: 100%; top: 100%; background: var(--gb-jewel); box-shadow: 0 0 10px var(--gb-jewel); pointer-events: none; }
.gb-xy i { position: absolute; font-style: normal; font-size: 9px; letter-spacing: .08em; opacity: .85; color: color-mix(in srgb, var(--gb-jewel) 70%, #fff); pointer-events: none; }
.gb-xy .gb-xy-lx { right: 5px; bottom: 3px; }
.gb-xy .gb-xy-ly { left: 4px; top: 3px; }
.gb-scenes { display: inline-flex; gap: 5px; }
.gb-jewel-pick { width: 26px; height: 26px; padding: 0; border: 2px solid rgba(255,255,255,.35);
  border-radius: 50%; background: none; cursor: pointer; }
.gb-scene { width: 34px; height: 30px; border-radius: 8px; border: 1px dashed rgba(255,255,255,.3);
  background: rgba(0,0,0,.3); color: #cfd4e6; font-weight: 800; font-size: 11px; cursor: pointer; opacity: .55; }
.gb-scene.set { opacity: 1; border-style: solid; border-color: var(--gb-jewel);
  box-shadow: 0 0 8px color-mix(in srgb, var(--gb-jewel) 55%, transparent); color: #fff; }
.gb-viz { width: 100%; height: 100%; display: block; min-width: 0;
  border-radius: 8px;
  /* A visible dark "screen" so the equalizer reads as a real display panel on
     ANY groovebox background — even when the groove isn't playing. */
  background: linear-gradient(180deg, #0c0c14, #050507);
  border: 1px solid rgba(255,255,255,.20);
  box-shadow: inset 0 0 22px rgba(0,0,0,.65), 0 1px 0 rgba(255,255,255,.05); }
/* 8 drag handles — 4 edges + 4 corners; faint until you hover the display */
.gb-viz-rh { position: absolute; z-index: 6; opacity: 0; transition: opacity .12s;
  background: var(--gb-accent, #7c3bff); box-shadow: 0 0 6px rgba(0,0,0,.4); }
.gb-viz-wrap:hover .gb-viz-rh { opacity: .30; }
.gb-viz-rh:hover { opacity: .9 !important; }
.gb-viz-rh-t { top: -3px; left: 12px; right: 12px; height: 6px; cursor: ns-resize; border-radius: 3px; }
.gb-viz-rh-b { bottom: -3px; left: 12px; right: 12px; height: 6px; cursor: ns-resize; border-radius: 3px; }
.gb-viz-rh-l { left: -3px; top: 12px; bottom: 12px; width: 6px; cursor: ew-resize; border-radius: 3px; }
.gb-viz-rh-r { right: -3px; top: 12px; bottom: 12px; width: 6px; cursor: ew-resize; border-radius: 3px; }
.gb-viz-rh-tl { top: -4px; left: -4px; width: 12px; height: 12px; cursor: nwse-resize; border-radius: 50%; }
.gb-viz-rh-tr { top: -4px; right: -4px; width: 12px; height: 12px; cursor: nesw-resize; border-radius: 50%; }
.gb-viz-rh-bl { bottom: -4px; left: -4px; width: 12px; height: 12px; cursor: nesw-resize; border-radius: 50%; }
.gb-viz-rh-br { bottom: -4px; right: -4px; width: 12px; height: 12px; cursor: nwse-resize; border-radius: 50%; }
.gb-ctl { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #9aa3b5; font-weight: 700; }
.gb-ctl input[type="range"] { width: 90px; accent-color: var(--gb-accent); height: 4px; }
.gb-ctl b { color: var(--gb-warm); min-width: 26px; text-align: right; }
.gb-grid { display: grid; gap: 5px; }
.gb-row { display: grid; grid-template-columns: 86px repeat(16, 1fr); gap: 5px; align-items: stretch; }
.gb-track { border: 0; border-radius: 7px; background: #17181d; color: #cfd6e4; font-size: 11px;
  font-weight: 700; cursor: pointer; padding: 0 4px; text-align: left; white-space: nowrap; overflow: hidden; }
.gb-track.muted { opacity: .35; text-decoration: line-through; }
.gb-cell { height: 30px; border-radius: 7px; border: 1px solid rgba(255,255,255,.07);
  background: #17181d; cursor: pointer; padding: 0; transition: background .08s, box-shadow .08s; }
.gb-cell.beat { border-color: rgba(255,255,255,.18); }   /* beats 1·5·9·13 */
/* drums glow pink/purple (like the Circuit's lower pads) */
.gb-drum .gb-cell[data-val="1"] { background: var(--gb-drum); box-shadow: 0 0 10px var(--gb-drum); }
/* custom SFX rows — warm glow to match their orange mixer faders */
.gb-sfxrow .gb-cell[data-val="1"] { background: var(--gb-warm, #ffb347); box-shadow: 0 0 10px var(--gb-warm, #ffb347); }
.gb-sfxtrack { font-size: 16px; }
/* note tracks glow blue, brighter per scale step */
.gb-note .gb-cell[data-val="1"] { background: var(--gb-note); filter: brightness(.65); box-shadow: 0 0 9px var(--gb-note); }
.gb-note .gb-cell[data-val="2"] { background: var(--gb-note); filter: brightness(.85); box-shadow: 0 0 10px var(--gb-note); }
.gb-note .gb-cell[data-val="3"] { background: var(--gb-note); filter: brightness(1.1); box-shadow: 0 0 11px var(--gb-note); }
.gb-note .gb-cell[data-val="4"] { background: var(--gb-note); filter: brightness(1.4); box-shadow: 0 0 13px var(--gb-note); }
.gb-cell.now { outline: 2px solid rgba(255,255,255,.85); outline-offset: -2px; }
.gb-hint { margin: 8px 0 0; font-size: 11px; color: #7c8598; text-align: center; }
#gb-toggle.on { box-shadow: 0 0 0 2px #7ee787 inset; }
@media (max-width: 900px) { .gb-row { grid-template-columns: 64px repeat(16, 1fr); } .gb-cell { height: 24px; } }

/* 🚀 with a custom user sound loaded — golden ring like the ★ pads */
.dj-rocket { position: relative; }
.dj-rocket.custom { box-shadow: 0 0 0 2px #ffd24a inset; }
.dj-rocket-x { position: absolute; top: -5px; right: -5px; width: 15px; height: 15px; border-radius: 50%;
  background: #e11d48; color: #fff; font-size: 9px; line-height: 15px; text-align: center; cursor: pointer; display: none; }
.dj-rocket.custom:hover .dj-rocket-x, .dj-rocket-x.armed { display: block; }
@keyframes libRecPulse { 0%,100% { box-shadow: 0 0 0 0 rgba(255,45,80,.35); } 50% { box-shadow: 0 0 0 6px rgba(255,45,80,0); } }

@media (max-width: 760px) {
  .dj-mixer { grid-template-columns: 1fr; }
}

/* (Backstage keeps its normal full size when the DJ mixer is open — the
   mixer sits above it and the backstage is left untouched.) */

/* DJ deck 📁 source picker — choose a studio track or upload from disk. */
.dj-source-menu {
  z-index: 9999;
  min-width: 210px; max-width: 250px; max-height: 320px; overflow-y: auto;
  background: #1b1030; border: 1px solid #6d28d9; border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .55); padding: 6px;
}
.dj-source-head {
  font-size: 12px; font-weight: 700; color: #c4b5fd;
  padding: 6px 8px 4px; letter-spacing: .02em;
}
.dj-source-empty { font-size: 12px; color: #9ca3af; padding: 4px 8px 8px; }
.dj-source-item {
  display: block; width: 100%; text-align: left;
  background: transparent; border: 0; color: #ede9fe;
  font-size: 13px; padding: 8px 10px; border-radius: 8px; cursor: pointer;
}
.dj-source-item:hover { background: #2e1d52; }
.dj-source-upload { color: #a3e635; font-weight: 600; }
.dj-source-sep { height: 1px; background: #3b2a5e; margin: 5px 4px; }
.dj-source-row { display: flex; align-items: center; gap: 2px; }
.dj-source-row .dj-source-item { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dj-source-del { flex: 0 0 auto; background: transparent; border: 0; color: #f4a0b6;
  font-size: 13px; line-height: 1; padding: 8px 10px; border-radius: 8px; cursor: pointer; }
.dj-source-del:hover { background: #4a1730; color: #fff; }

/* ─── 📺 DJ gear ON the live stage (stage-gear.js) ─────────────────── */
#stage-gear-layer { position: absolute; inset: 0; pointer-events: none; z-index: 30; }
.sg-tile { position: absolute; pointer-events: auto; overflow: hidden; border-radius: 10px; isolation: isolate; }
.sg-holder { position: absolute; inset: 0; overflow: hidden; }
/* ── A mirrored gear must stay LAID OUT while its own panel is closed ──────
   Marcelo, 2026-08-11: with the mixer on stage, its equalizers, channel
   meters and waveforms froze — but the same mixer animated fine in its own
   panel. Cause: closing the 🎧 door puts `.hidden` (display:none) on
   #dj-mixer, and every canvas draw in app-gear-wiring.js opens with
   `if (cvs.offsetParent === null) return;` — drawOneEq, drawJogViz,
   drawXpad, drawChannelMeters — while drawScrollWave bails on a 0-width
   parent. display:none makes all of those true at once, so the SOURCE
   canvases stop being painted. stage-gear.js keeps re-cloning at 10fps, so
   the mirror is alive; it is copying dead pixels.

   Relaxing those guards alone would not do: several of the draws read
   clientWidth/clientHeight, which display:none also zeroes. Keeping the
   panel laid out fixes every one of them at once.

   visibility:hidden (NOT display:none) is the point — it leaves offsetParent
   non-null and clientWidth real. position:absolute takes it out of flow so
   the backstage layout is untouched, and the fixed offset keeps it from
   growing the page. `position:fixed` would have been wrong here: it nulls
   offsetParent, which is the very thing being preserved. */
body.sg-live-dj  #dj-mixer.hidden,
body.sg-live-gb  #groovebox.hidden,
body.sg-live-sy  #streamsynth.hidden {
  display: grid !important;        /* beat .hidden's display:none !important */
  visibility: hidden !important;   /* unpainted, but still laid out */
  position: absolute !important;
  left: 0 !important; top: 0 !important;
  pointer-events: none !important;
  z-index: -1 !important;
}
body.sg-live-gb #groovebox.hidden,
body.sg-live-sy #streamsynth.hidden { display: block !important; }

/* The on-stage copy is a DISPLAY only — never intercepts clicks. */
.sg-mirror { pointer-events: none !important; }
.sg-mirror.dj-mixer { display: grid !important; }
.sg-mirror.groovebox { display: block !important; }
/* The WHOLE tile is a move surface — grab anywhere to drag it around the stage.
   The 8 resize handles sit on top (higher z-index) so the edges/corners resize. */
.sg-drag { position: absolute; inset: 0; cursor: move; z-index: 6; }
.sg-tile:hover { box-shadow: 0 0 0 2px var(--accent, #6c8cff) inset; }
.sg-tile:hover .sg-drag { background: rgba(108,140,255,.08); }
.sg-h { position: absolute; z-index: 7; opacity: 0; background: rgba(255,255,255,.5); transition: opacity .12s; }
.sg-tile:hover .sg-h { opacity: .4; }
.sg-h:hover { opacity: .95 !important; }
.sg-n { top: -3px; left: 12px; right: 12px; height: 6px; cursor: ns-resize; border-radius: 3px; }
.sg-s { bottom: -3px; left: 12px; right: 12px; height: 6px; cursor: ns-resize; border-radius: 3px; }
.sg-e { right: -3px; top: 12px; bottom: 12px; width: 6px; cursor: ew-resize; border-radius: 3px; }
.sg-w { left: -3px; top: 12px; bottom: 12px; width: 6px; cursor: ew-resize; border-radius: 3px; }
.sg-ne { top: -4px; right: -4px; width: 13px; height: 13px; cursor: nesw-resize; border-radius: 50%; }
.sg-nw { top: -4px; left: -4px; width: 13px; height: 13px; cursor: nwse-resize; border-radius: 50%; }
.sg-se { bottom: -4px; right: -4px; width: 13px; height: 13px; cursor: nwse-resize; border-radius: 50%; }
.sg-sw { bottom: -4px; left: -4px; width: 13px; height: 13px; cursor: nesw-resize; border-radius: 50%; }
/* the "📺 Stage" toggle button (injected into the DJ console + Groovebox header) */
.sg-toggle { white-space: nowrap; }
.sg-toggle.on { background: var(--accent) !important; border-color: var(--accent) !important; color: #fff !important; box-shadow: 0 0 10px var(--accent); }
/* DJ: a clear full-width button right under the SPLIT switch */
#dj-mixer .sg-toggle { display: block; width: calc(100% - 8px); margin: 6px auto 0; font-size: 11px; font-weight: 700; padding: 5px 10px; }
/* Groovebox: a compact button right beside the master VOL fader (top-right of the knob row) */
#gb-knobs .sg-toggle { align-self: center; flex: 0 0 auto; font-size: 11px; font-weight: 700; padding: 6px 10px; margin-left: 2px; }
/* In the synth header the Stage button is a compact chip, not a full-width bar */
#streamsynth .sg-toggle { flex: 0 0 auto; width: auto; font-size: 11px; font-weight: 700; padding: 6px 12px; }
#streamsynth .sg-anim { flex: 0 0 auto; }
/* 🌈 colour-cycle animation for the on-stage display, + its speed fader */
@keyframes sg-huecycle { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }
/* 🎨 body colour: a saturated overlay blended onto the chassis, hue-rotated through the rainbow */
.sg-body-tint { position: absolute; inset: 0; z-index: 2; pointer-events: none; background: hsl(0, 90%, 50%); mix-blend-mode: color; border-radius: inherit; }
/* two-row stack: 🌈 🎨 buttons on top, the (bigger) speed fader on its own row below */
.sg-anim { display: inline-flex; flex-direction: column; gap: 6px; margin-left: 6px; }
.sg-anim.hidden { display: none; }
.sg-anim-top { display: flex; align-items: center; justify-content: center; gap: 10px; }
.sg-anim-bot { display: flex; align-items: center; width: 100%; }
.sg-anim-btn { flex: 0 0 auto !important; width: auto !important; font-size: 15px; padding: 6px 12px; line-height: 1; }
.sg-anim-btn.on { background: var(--accent, #6c8cff) !important; border-color: var(--accent, #6c8cff) !important; box-shadow: 0 0 10px var(--accent, #6c8cff); }
/* a clearly-visible fader, now full-width on its own row. The DJ mixer forces
   `input[type=range]{width:100%;height:4px}` on ALL sliders — override it hard. */
.sg-anim-speed { -webkit-appearance: none !important; appearance: none !important; flex: 1 1 auto; width: 100% !important; height: 8px !important; border-radius: 5px; background: rgba(255,255,255,.25) !important; cursor: pointer; }
.sg-anim-speed::-webkit-slider-runnable-track { height: 8px; border-radius: 5px; background: transparent; }
.sg-anim-speed::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; margin-top: -6px; border-radius: 50%; background: var(--accent, #6c8cff); border: 2px solid rgba(255,255,255,.9); cursor: pointer; }
.sg-anim-speed::-moz-range-track { height: 8px; border-radius: 5px; background: rgba(255,255,255,.25); }
.sg-anim-speed::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: var(--accent, #6c8cff); border: 2px solid rgba(255,255,255,.9); cursor: pointer; }
.sg-anim-lbl { font-size: 9px; font-weight: 800; letter-spacing: .04em; color: #9aa3b5; white-space: nowrap; }
/* DJ console: the stacked control, full box width under the 📺 Stage button */
#dj-mixer .sg-anim { display: flex; flex-direction: column; gap: 6px; width: calc(100% - 8px); box-sizing: border-box; margin: 6px auto 0; }
#dj-mixer .sg-anim.hidden { display: none; }
/* Groovebox: inline, right after the 📺 Stage button beside the VOL fader */
#gb-knobs .sg-anim { align-self: center; flex: 0 0 auto; }

/* ── 🎭 Live voice changer panel ─────────────────────────────────── */
.vc-modal { max-width: 460px; width: 92vw; max-height: 88vh; overflow-y: auto; }
.vc-status {
  font-size: 13px; padding: 8px 10px; border-radius: 9px; margin: 6px 0 10px;
  background: var(--panel-2); border: 1px solid var(--border); color: var(--muted, #9aa);
}
.vc-status.vc-on { border-color: #2ecc71; color: #2ecc71; background: rgba(46, 204, 113, 0.08); }
.vc-status.vc-warn { border-color: #e5a839; color: #e5a839; background: rgba(229, 168, 57, 0.08); }
.vc-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
@media (min-width: 480px) { .vc-grid { grid-template-columns: repeat(3, 1fr); } }
.vc-grid .device-btn { font-size: 13px; padding: 9px 8px; text-align: center; }
.vc-sliders input[type="range"] { width: 100%; }
.vc-val { float: right; color: var(--text); font-variant-numeric: tabular-nums; }
.vc-clip { color: #e53950; font-size: 13px; margin: 4px 0 0; }
.vc-actions { display: flex; gap: 8px; margin-top: 12px; }

/* A required field the user left empty — pulse red so it can't be missed */
@keyframes fieldMissing {
  0%, 100% { border-color: #ff4d5e; box-shadow: 0 0 0 3px rgba(255,77,94,.35); }
  50%      { border-color: #ff4d5e; box-shadow: 0 0 0 8px rgba(255,77,94,.12); }
}
.field-missing { animation: fieldMissing .9s ease-in-out 2; border-color: #ff4d5e !important; }

/* ── 🎛️ SS-1 SYNTH — our own polysynth under the DJ mixer ─────────────── */
.streamsynth { --sy-accent: #2fe0d0; --sy-body: #0a0e10; --sy-keys: #eef1f3;
  margin-top: 10px; padding: 14px; border-radius: 14px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--sy-body) 78%, #ffffff 10%), color-mix(in srgb, var(--sy-body) 92%, #000));
  border: 1px solid color-mix(in srgb, var(--sy-accent) 35%, rgba(255,255,255,.14));
  box-shadow: 0 10px 30px rgba(0,0,0,.5) inset, 0 4px 18px rgba(0,0,0,.45); }
.streamsynth.hidden { display: none; }
.sy-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.sy-logo { font-weight: 800; letter-spacing: .08em; color: var(--sy-accent); }
.sy-midi { font-size: 11.5px; color: #9fb3ae; margin-left: auto; }
.sy-oct { min-height: 26px; padding: 3px 10px; border-radius: 7px; border: 1px solid rgba(255,255,255,.18);
  background: #10181a; color: #cfe8e4; font-weight: 800; font-size: 12px; cursor: pointer; }
.sy-oct:hover { filter: brightness(1.3); }
.sy-octlbl { font-size: 12px; font-weight: 800; color: #cfe8e4; min-width: 52px; text-align: center; }
.sy-dice { font-size: 16px; padding: 3px 9px; }
.sy-dice:hover { transform: rotate(20deg); }
.sy-tap { font-weight: 800; letter-spacing: .04em; }
.sy-leds { display: inline-flex; gap: 4px; align-items: center; margin-left: 4px; }
.sy-led { width: 10px; height: 10px; border-radius: 50%; background: rgba(0,0,0,.4);
  border: 1px solid rgba(255,255,255,.18); transition: box-shadow .1s, background .1s; }
.sy-led.on { background: var(--sy-accent); box-shadow: 0 0 8px var(--sy-accent); border-color: transparent; }
/* 🎬 SFX KEYS picker — chips choose what the SFX Board keys play */
.sy-sfxpick { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; max-width: 640px; }
.sy-sfxchip { font-size: 10px; font-weight: 700; padding: 3px 8px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.18); background: rgba(0,0,0,.28); color: inherit;
  cursor: pointer; opacity: .42; line-height: 1.2; }
.sy-sfxchip.on { opacity: 1; border-color: var(--sy-accent); box-shadow: 0 0 6px color-mix(in srgb, var(--sy-accent) 55%, transparent); }
.sy-sfxchip.sy-sfxreset { opacity: .9; border-style: dashed; }
.sy-minispec { width: 340px; height: 36px; border-radius: 8px; background: #05080a;
  border: 1px solid rgba(255,255,255,.14); box-shadow: inset 0 2px 6px rgba(0,0,0,.6); }
/* 📊 DJ global-bar flanks: EQ controls on top, master VU meter below */
.dj-gb-flank { display: flex; flex-direction: column; align-items: center; gap: 10px; justify-content: flex-start; }
.dj-vu { width: 30px; height: 200px; border-radius: 8px; background: #05080a;
  border: 1px solid rgba(255,255,255,.14); box-shadow: inset 0 2px 8px rgba(0,0,0,.6); }
/* The instrument/preset menu is the synth's most important control — make it
   big, bold and accent-framed so nobody hunts for the guitars and drums. */
.streamsynth .sy-sel.sy-pre { min-width: 230px; font-size: 15px; font-weight: 800; padding: 9px 12px;
  border: 2px solid var(--sy-accent); border-radius: 10px;
  box-shadow: 0 0 8px color-mix(in srgb, var(--sy-accent) 40%, transparent); }
.sy-sel { background: #10181a; color: #e6f4f1; border: 1px solid rgba(255,255,255,.18); border-radius: 7px; padding: 2px 5px; font-size: 11px; }
/* selects sit at their NATURAL size — the old width:100% + grow combo made
   Safari resolve the percentage against the wrong box and blow the wrapper
   wide open (the mystery holes beside every dropdown, Marcelo). */
.sy-s .sy-sel { flex: 0 1 auto; width: auto; min-width: 0; }
.sy-sect { min-width: 128px; overflow: hidden; }
.sy-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: stretch; }
.sy-sect { flex: 1 1 130px; min-width: 128px; padding: 8px 10px; border-radius: 10px;
  background: rgba(255,255,255,.045); border: 1px solid rgba(255,255,255,.09); }
.sy-h { font-size: 10.5px; font-weight: 800; letter-spacing: .1em; color: var(--sy-accent); margin-bottom: 6px; }
.sy-s { display: flex; align-items: center; gap: 6px; margin: 5px 0; min-width: 0; }
.sy-lbl { font-size: 11px; color: #a9bcb7; flex: 0 1 44px; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sy-val { font-size: 10.5px; color: #e6f4f1; min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
/* the slider is the part that gives way — labels and numbers always fit whole */
.sy-s input[type="range"] { flex: 1 1 0; min-width: 24px; accent-color: var(--sy-accent); }
/* keyboard */
.sy-kb { position: relative; height: 96px; margin-top: 12px; border-radius: 10px; overflow: hidden;
  background: #05070a; border: 1px solid rgba(255,255,255,.12); display: flex; }
.sy-kb + .sy-kb { margin-top: 8px; }   /* double deck: the two manuals sit close */

/* ── SS-1 JN-80 face-lift (Marcelo): every slider stands upright like the
   mixer's channel faders — label on top, chunky lit cap, value underneath.
   Sections hug their content (no stretch-to-tallest → no empty holes). */
.streamsynth .sy-rows { display: flex; flex-direction: column; gap: 3px; }
.streamsynth .sy-row { align-items: flex-start; gap: 3px; flex-wrap: nowrap; }
/* flex-basis auto + tight metrics → the whole face fits in ONE strip
   (Marcelo's rule; fitSynth folds only on phone widths). */
.streamsynth .sy-sect { flex: 0 0 auto; min-width: 0; padding: 18px 6px 5px; position: relative; }
.sy-sect { display: flex; flex-wrap: nowrap; align-items: flex-end;
  justify-content: flex-start; column-gap: 4px; }
/* the title floats above the single control line instead of being a flex item */
.sy-sect .sy-h { position: absolute; top: 4px; left: 8px; white-space: nowrap; }
.sy-sect .sy-s:has(> select) { flex: 0 1 auto; min-width: 0; margin: 3px 0 0; gap: 4px; }
/* labels exactly as wide as their text — no reserved 44px basis in sections */
.sy-sect .sy-s .sy-lbl { flex: 0 0 auto; width: auto; }
/* 🔀 SWITCH SHELF — every dropdown/button lives on a slim row under the
   fader strip (Marcelo: "move them somewhere else"), grouped per section. */
.sy-shelf { display: flex; align-items: center; gap: 6px; margin-top: 3px;
  padding: 3px 8px; border-radius: 8px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.08);
  flex-wrap: wrap; }
.sy-shelfgrp { display: inline-flex; align-items: center; gap: 5px; padding: 2px 6px;
  border-radius: 6px; background: rgba(255,255,255,.05); white-space: nowrap; }
.sy-shelflbl { font-size: 8px; font-weight: 800; letter-spacing: .06em; color: var(--sy-accent); }
.sy-shelf .sy-s { margin: 0; flex: 0 0 auto; }
.sy-shelf .sy-sel, .sy-shelf .sy-fmdisp { height: auto; }
.sy-shelf .sy-fmdisp { font-size: 12px; padding: 3px 6px; min-width: 34px; }
/* the Song Builder rides the shelf's last line and soaks up its empty tail */
.sy-shelf .sy-build { flex: 1 1 340px; min-width: 0; margin: 0; padding: 2px 6px;
  background: rgba(255,255,255,.05); border: none; border-radius: 6px; }
.sy-shelf .sy-build .sy-sb-status { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; }
.sy-s:has(> input[type="range"]) {
  flex: 0 1 auto; flex-direction: column; align-items: center; gap: 2px;
  min-width: 28px; margin: 3px 0 0;
}
.sy-s:has(> input[type="range"]) .sy-val { min-width: 0; }
.sy-s:has(> input[type="range"]) .sy-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; white-space: nowrap;
  flex: 0 0 auto; }   /* the base rule's 44px flex-basis becomes HEIGHT in a column — kill it */
.sy-s:has(> input[type="range"]) .sy-val { font-size: 10px; }
.sy-capcol { width: 16px; height: 12px; padding: 0; border: 1px solid rgba(0,0,0,.5); border-radius: 4px;
  background: none; cursor: pointer; margin-top: 2px; }
/* ── PROGRAMMER strip (JN-80): LCD · WRITE · slots · BENDER · LFO TRIG ── */
.sy-prog { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px;
  padding: 6px 10px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.08); }
.sy-lcd { min-width: 190px; padding: 5px 10px; border-radius: 5px; font: 700 12px/1.4 ui-monospace, monospace;
  letter-spacing: .08em; color: #9fd8ff; background: #05122b; border: 1px solid #0a2a55;
  box-shadow: inset 0 0 10px rgba(40,120,255,.35); text-shadow: 0 0 6px rgba(90,170,255,.8); white-space: nowrap; }
.sy-write { color: #ffb36b; }
.sy-slot { min-width: 30px; opacity: .55; }
.sy-slot.set { opacity: 1; border-color: var(--sy-accent); color: var(--sy-accent); }
.sy-bendwrap { display: flex; align-items: center; gap: 6px; margin-left: auto; }
/* trims + MORPH pack left on the header line; the MIDI block keeps the right */
.sy-head .sy-bendwrap, .sy-head .sy-mixwrap { margin-left: 0; }
.sy-bender { width: 140px !important; height: 22px !important; accent-color: var(--sy-accent); cursor: grab; }
.sy-trig.on { background: var(--sy-accent); color: #06211d; }
/* the bender is horizontal — undo the vertical-fader styling for it */
.sy-s input[type="range"].sy-bender { writing-mode: horizontal-tb; direction: ltr; }
.sy-bendwrap .sy-lbl { font-size: 9px; text-transform: uppercase; letter-spacing: .05em; }
/* ── SEQ strip: transport + 16 step cells ── */
.sy-seq { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px;
  padding: 6px 10px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.08); }
.sy-seq > .sy-lbl { font-size: 10px; font-weight: 700; letter-spacing: .08em; color: var(--sy-accent); }
.sy-seq .sy-oct.on { background: var(--sy-accent); color: #06211d; }
.sy-cells { display: flex; gap: 3px; flex: 1 1 auto; min-width: 0; }
.sy-cell { flex: 1 1 0; min-width: 22px; padding: 4px 0; border-radius: 5px; font: 700 9px/1.2 ui-monospace, monospace;
  background: rgba(0,0,0,.4); color: #667; border: 1px solid rgba(255,255,255,.1); cursor: pointer; }
.sy-cell.on { color: var(--sy-accent); border-color: color-mix(in srgb, var(--sy-accent) 55%, transparent); }
.sy-cell.play { background: color-mix(in srgb, var(--sy-accent) 35%, #000); color: #fff; }
.sy-cell.rec { border-color: #ff4d63; box-shadow: 0 0 6px rgba(255,77,99,.6); }
/* ── Groovebox pro upgrade: accent glow, chance dash, chain strip, 📁 ── */
.gb-cell[data-acc="1"] { filter: brightness(1.5); box-shadow: 0 0 8px var(--gb-warm, #ffb347), inset 0 0 4px rgba(255,255,255,.5); }
.gb-cell[data-chance="1"] { opacity: .55; border-style: dashed !important; border-color: var(--gb-accent, #b14aff); }
.gb-chainstrip { display: inline-flex; align-items: center; gap: 4px; margin-left: 8px; flex-wrap: wrap; }
.gb-chainadd { min-width: 26px; padding: 3px 6px; border-radius: 6px; font-size: 10px; font-weight: 700; cursor: pointer;
  background: rgba(0,0,0,.35); color: var(--gb-accent, #b14aff); border: 1px solid rgba(255,255,255,.15); }
.gb-chaintoggle { padding: 3px 8px; border-radius: 6px; font-size: 10px; font-weight: 800; cursor: pointer; letter-spacing: .04em;
  background: rgba(0,0,0,.35); color: #cbd; border: 1px solid rgba(255,255,255,.18); }
.gb-chaintoggle.on { background: var(--gb-accent, #b14aff); color: #fff; box-shadow: 0 0 10px var(--gb-accent, #b14aff); }
.gb-chainchips { display: inline-flex; gap: 3px; flex-wrap: wrap; }
.gb-chainchip { min-width: 18px; text-align: center; padding: 2px 5px; border-radius: 5px; font-size: 10px; font-weight: 800; cursor: pointer;
  background: rgba(255,255,255,.1); color: #ddd; border: 1px solid rgba(255,255,255,.15); }
.gb-chainchip.on { background: var(--gb-warm, #ffb347); color: #201000; box-shadow: 0 0 8px var(--gb-warm, #ffb347); }
.gb-drumfile { width: 22px; padding: 2px 0; margin-left: 3px; border-radius: 5px; font-size: 10px; cursor: pointer;
  background: rgba(0,0,0,.3); color: #9ab; border: 1px solid rgba(255,255,255,.12); }
.gb-drumfile.set { color: var(--gb-warm, #ffb347); border-color: var(--gb-warm, #ffb347); }
/* ── 🎛 Groovebox MC-707 pack: LCD + motion + scatter + note pads (spec: groovebox-mc707-pack.md) ── */
.gb-performrow, .gb-playrow { display: flex; align-items: stretch; gap: 10px; margin-top: 8px; }
/* 📟 the LCD — jewel-monochrome screen with a faint scanline sheen */
.gb-screenwrap { position: relative; flex: 0 0 auto; border-radius: 8px; overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--gb-jewel) 45%, rgba(255,255,255,.2));
  background: linear-gradient(180deg, #0c0c14, #050507);
  box-shadow: inset 0 0 18px rgba(0,0,0,.7), 0 1px 0 rgba(255,255,255,.05); }
.gb-screen { display: block; width: 520px; height: 64px; }
.gb-screenwrap::after { content: ''; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0,0,0,.16) 2px 3px); }
.gb-motioncluster { display: inline-flex; align-items: center; gap: 6px; }
.gb-perfrec.on { background: #ff4d63 !important; border-color: transparent !important; color: #fff !important;
  box-shadow: 0 0 12px rgba(255,77,99,.8); }
.gb-motion.on { background: var(--gb-jewel) !important; border-color: transparent !important; color: #06211d !important;
  box-shadow: 0 0 12px var(--gb-jewel); }
.gb-moterase { min-width: 26px; }
/* a knob that carries a recorded lane wears a little jewel */
.gb-knob { position: relative; }
.gb-knob.has-motion::after { content: ''; position: absolute; top: 2px; right: 4px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--gb-jewel); box-shadow: 0 0 6px var(--gb-jewel); }
/* SCATTER + note pads — jewel chrome, .gb-cell sizing */
.gb-scatcluster { display: inline-flex; align-items: center; gap: 5px; margin-left: auto; }
.gb-scatlbl { font-style: normal; font-size: 9px; font-weight: 800; letter-spacing: .12em;
  color: color-mix(in srgb, var(--gb-jewel) 70%, #fff); opacity: .8; margin-right: 2px; }
.gb-perfpad { height: 34px; min-width: 44px; border-radius: 8px; cursor: pointer; font-size: 12px; font-weight: 800;
  background: color-mix(in srgb, var(--gb-jewel) 10%, rgba(0,0,0,.5));
  color: color-mix(in srgb, var(--gb-jewel) 75%, #fff);
  border: 1px solid color-mix(in srgb, var(--gb-jewel) 45%, rgba(255,255,255,.16));
  transition: background .06s, box-shadow .06s; touch-action: none; }
.gb-perfpad.on, .gb-perfpad.hit { background: var(--gb-jewel); color: #081208;
  box-shadow: 0 0 14px var(--gb-jewel), inset 0 0 6px rgba(255,255,255,.4); }
.gb-padchips { display: inline-flex; align-items: center; gap: 5px; }
.gb-padchip.on { background: var(--gb-jewel) !important; color: #06211d !important; border-color: transparent !important;
  box-shadow: 0 0 10px var(--gb-jewel); }
.gb-notepads { display: flex; gap: 5px; flex: 1 1 auto; }
.gb-notepad { flex: 1 1 0; min-width: 0; }
.gb-notepad.gb-octedge { border-left-width: 3px;
  border-left-color: color-mix(in srgb, var(--gb-jewel) 80%, #fff); }
/* captured performances show in the grid: ▸ octave tick, ⠿ chord double-dot */
.gb-cell { position: relative; }
.gb-cell[data-oct="1"]::before { content: ''; position: absolute; top: 2px; right: 2px;
  border: 3px solid transparent; border-top-color: color-mix(in srgb, var(--gb-jewel) 85%, #fff);
  border-right-color: color-mix(in srgb, var(--gb-jewel) 85%, #fff); border-radius: 1px; }
.gb-cell[data-chord="1"]::after { content: ''; position: absolute; left: 3px; bottom: 2px; width: 4px; height: 4px;
  border-radius: 50%; background: color-mix(in srgb, var(--gb-jewel) 85%, #fff);
  box-shadow: 7px 0 0 color-mix(in srgb, var(--gb-jewel) 85%, #fff); }
/* ── 🎛 C1–C4 + VALUE cluster (spec: groovebox-c1c4-value.md) ── */
.gb-ccluster { display: inline-flex; align-items: center; gap: 10px; }
.gb-ccluster .gb-knob label { max-width: 56px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gb-cknob::before { content: attr(data-c); position: absolute; top: -2px; left: 1px; z-index: 2;
  font-size: 8px; font-weight: 900; letter-spacing: .04em;
  color: var(--gb-jewel); text-shadow: 0 0 6px var(--gb-jewel); }
.gb-cdim { opacity: .38; }
.gb-valueknob .gb-dial { border: 2px solid color-mix(in srgb, var(--gb-jewel) 75%, transparent);
  box-shadow: 0 0 14px color-mix(in srgb, var(--gb-jewel) 65%, transparent), inset 0 0 10px color-mix(in srgb, var(--gb-jewel) 25%, transparent); }
.gb-vunbound { opacity: .45; }
/* ── 🎛 FX cluster (spec: groovebox-fx-and-glide.md) ── */
.gb-fxcluster { display: inline-flex; align-items: center; gap: 8px; }
.gb-fxsels { display: inline-flex; flex-direction: column; gap: 4px; }
.gb-fxsel { width: 58px; padding: 3px 0; border-radius: 6px; font-size: 9px; font-weight: 800; letter-spacing: .04em;
  cursor: pointer; background: rgba(0,0,0,.35); color: color-mix(in srgb, var(--gb-jewel) 65%, #fff);
  border: 1px solid color-mix(in srgb, var(--gb-jewel) 35%, rgba(255,255,255,.15)); }
.gb-fxsel.on { background: var(--gb-jewel); color: #081208; border-color: transparent;
  box-shadow: 0 0 10px var(--gb-jewel); }
.gb-lane-erase-row { color: #ff9d9d !important; }
.sy-ab.set { border-color: var(--sy-accent); color: var(--sy-accent); }
.sy-ab.on { background: #ff4d63; color: #fff; }
.sy-capcol::-webkit-color-swatch-wrapper { padding: 0; }
.sy-capcol::-webkit-color-swatch { border: none; border-radius: 3px; }
.sy-s input[type="range"] {
  writing-mode: vertical-lr; direction: rtl;
  -webkit-appearance: none; appearance: none;
  width: 9px !important; height: 78px !important; flex: 0 0 auto;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.22) 46%, rgba(255,255,255,.22) 54%, transparent 60%),
    linear-gradient(90deg, #04030a, #151a20 44%, #151a20 56%, #04030a);
  border: 1px solid rgba(255,255,255,.12); border-radius: 5px;
  box-shadow: inset 0 2px 6px rgba(0,0,0,.7); cursor: pointer;
}
.sy-s input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 30px; height: 15px; border-radius: 5px;
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.45) 44%, #fff 47%, #fff 53%, rgba(0,0,0,.45) 56%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 45%, #fff), var(--sy-cap, var(--sy-accent)) 55%, color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 50%, #000));
  border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 2px 6px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.7), 0 0 10px color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 55%, transparent);
  cursor: grab;
}
.sy-s input[type="range"]::-moz-range-thumb {
  width: 30px; height: 15px; border-radius: 5px;
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.45) 44%, #fff 47%, #fff 53%, rgba(0,0,0,.45) 56%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 45%, #fff), var(--sy-cap, var(--sy-accent)) 55%, color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 50%, #000));
  border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 2px 6px rgba(0,0,0,.6), 0 0 10px color-mix(in srgb, var(--sy-cap, var(--sy-accent)) 55%, transparent);
}
.sy-key { user-select: none; -webkit-user-select: none; touch-action: none; cursor: pointer; }
.sy-white { flex: 1; border-right: 1px solid rgba(0,0,0,.25); border-radius: 0 0 6px 6px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--sy-keys) 96%, #fff), color-mix(in srgb, var(--sy-keys) 82%, #566)); }
.sy-white.down { background: linear-gradient(180deg, var(--sy-accent), color-mix(in srgb, var(--sy-accent) 55%, #000)); }
.sy-black { position: absolute; top: 0; width: calc(100% / 15 * .62); height: 62%; z-index: 2;
  background: linear-gradient(180deg, color-mix(in srgb, var(--sy-keys) 22%, #262b30), color-mix(in srgb, var(--sy-keys) 10%, #0d1013)); border: 1px solid #000; border-radius: 0 0 5px 5px;
  box-shadow: 0 3px 6px rgba(0,0,0,.5); }
.sy-black.down { background: linear-gradient(180deg, var(--sy-accent), color-mix(in srgb, var(--sy-accent) 45%, #000)); }
.sy-hint { margin: 8px 0 0; font-size: 11.5px; color: #8fa39e; }
/* SS-1 live oscilloscope strip */
.sy-scope { display: block; width: 100%; height: 56px; margin-bottom: 10px; border-radius: 10px;
  background: #05080a; border: 1px solid color-mix(in srgb, var(--sy-accent) 30%, transparent); }

/* ── 👯 TWIN strip — dual-layer mode + EDIT A/B (spec: ss1-twin.md) ── */
.sy-twin { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px;
  padding: 6px 10px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.08); }
.sy-twin > .sy-lbl { flex: 0 0 auto; width: auto; overflow: visible; font-size: 10px; font-weight: 700;
  letter-spacing: .08em; color: var(--sy-accent); }
.sy-twin .sy-oct.on, .sy-prog .sy-oct.on { background: var(--sy-accent); color: #06211d; }
.sy-twin .sy-oct:disabled, .sy-prog .sy-oct:disabled { opacity: .4; cursor: default; }
.sy-twinhint { margin-left: auto; font-size: 10px; color: #8fa39e; }
.sy-mixwrap { margin-left: 4px; }
.sy-mixwrap .sy-lbl { flex: 0 0 auto; width: auto; overflow: visible; }
.sy-bender.sy-mixfader { width: 64px !important; height: 18px !important; }

/* FM ALGORITHM display — the sy-lcd glow, condensed to two digits */
.sy-fmdisp { min-width: 46px; padding: 6px 8px; border-radius: 5px; cursor: pointer;
  font: 800 16px/1.1 ui-monospace, monospace; letter-spacing: .12em; text-align: center;
  color: #9fd8ff; background: #05122b; border: 1px solid #0a2a55;
  box-shadow: inset 0 0 10px rgba(40,120,255,.35); text-shadow: 0 0 6px rgba(90,170,255,.8); }
.sy-fmdisp.dim { opacity: .45; }

/* ribbon 🅰/🅰🅱/🅱 performance-target block */
.sy-ribhold + .sy-ribtarget { margin-left: 8px; }
.sy-ribtarget:disabled { opacity: .4; cursor: default; }

/* ── 〰 RIBBON — Gemini-style touch strip above the keys (spec: ss1-ribbon.md) ── */
.sy-ribbonwrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 2px 0 8px;
  padding: 6px 10px; border-radius: 10px; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.08); }
.sy-riblbl { flex: 0 0 auto; width: auto; overflow: visible; font-size: 10px; font-weight: 700; letter-spacing: .08em; color: var(--sy-accent); }
.sy-ribmodes { display: inline-flex; gap: 3px; }
.sy-ribmode { padding: 4px 8px; border-radius: 5px; font: 700 9px/1.2 ui-monospace, monospace; letter-spacing: .05em;
  background: rgba(0,0,0,.4); color: #9ab; border: 1px solid rgba(255,255,255,.12); cursor: pointer; }
.sy-ribmode.on { background: var(--sy-accent); color: #06211d; border-color: transparent; }
.sy-ribhold { margin-left: 6px; }
.sy-ribhold.on { background: #ffb36b; color: #201000; border-color: transparent; box-shadow: 0 0 8px rgba(255,179,107,.6); }
.sy-ribbon { position: relative; flex: 1 1 240px; min-width: 200px; height: 28px; border-radius: 14px;
  cursor: pointer; touch-action: none; user-select: none; -webkit-user-select: none;
  /* printed tick rows top + bottom over a graphite bar, like the hardware */
  background:
    linear-gradient(90deg, rgba(255,255,255,.16) 0 1px, transparent 1px) 0 3px / 5% 5px repeat-x,
    linear-gradient(90deg, rgba(255,255,255,.16) 0 1px, transparent 1px) 0 calc(100% - 3px) / 5% 5px repeat-x,
    linear-gradient(180deg, #272b2e, #101315 55%, #1d2124);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: inset 0 2px 8px rgba(0,0,0,.65), 0 1px 0 rgba(255,255,255,.06); }
.sy-ribbon::before, .sy-ribbon::after { content: ''; position: absolute; top: -2px; bottom: -2px; width: 14px;
  background: linear-gradient(180deg, #2c3134, #0c0f11); border: 1px solid rgba(255,255,255,.18); pointer-events: none; }
.sy-ribbon::before { left: -3px; border-radius: 9px 4px 4px 9px; }
.sy-ribbon::after { right: -3px; border-radius: 4px 9px 9px 4px; }
.sy-ribmark { position: absolute; left: 50%; top: 2px; transform: translateX(-50%); width: 0; height: 0; pointer-events: none;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid color-mix(in srgb, var(--sy-accent) 80%, #fff); opacity: .9; }
.sy-ribdot { position: absolute; top: 50%; left: 50%; width: 14px; height: 14px; border-radius: 50%;
  transform: translate(-50%, -50%); pointer-events: none; opacity: .95;
  background: radial-gradient(circle at 35% 35%, #fff, var(--sy-accent) 55%, color-mix(in srgb, var(--sy-accent) 55%, #000));
  box-shadow: 0 0 10px var(--sy-accent); }
.sy-ribdot.ease { transition: left .18s ease-out; }
.sy-ribbon.touch .sy-ribdot { width: 18px; height: 18px;
  box-shadow: 0 0 16px var(--sy-accent), 0 0 30px color-mix(in srgb, var(--sy-accent) 45%, transparent); }

/* SS-1 body-colour picker */
.sy-colwrap { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.sy-col { width: 30px; height: 24px; padding: 0; border: 1px solid rgba(255,255,255,.35); border-radius: 6px; background: none; cursor: pointer; }
.sy-col::-webkit-color-swatch-wrapper { padding: 1px; }
.sy-col::-webkit-color-swatch { border: none; border-radius: 4px; }

/* SS-1 ⏺ REC — records just the synth into Background Music */
.sy-rec { min-height: 26px; padding: 3px 12px; border-radius: 7px; border: 1px solid rgba(255,90,90,.55);
  background: #1a0f11; color: #ff9d9d; font-weight: 800; font-size: 12px; cursor: pointer; }
.sy-rec.on { background: linear-gradient(180deg, #ff5050, #c22323); color: #fff; border-color: transparent;
  box-shadow: 0 0 10px rgba(255,80,80,.6); }

/* SS-1 quick-skin circles */
.sy-skins { display: inline-flex; gap: 5px; align-items: center; }
.sy-skin { width: 20px; height: 20px; padding: 0; border-radius: 50%; border: 2px solid rgba(255,255,255,.35); cursor: pointer; }
.sy-skin:hover { transform: scale(1.15); }
.sy-skin.active { border-color: #fff; box-shadow: 0 0 6px rgba(255,255,255,.6); }

/* SS-1 ⌨️ computer-keyboard switch */
.sy-kbd { min-height: 26px; padding: 3px 10px; border-radius: 7px; border: 1px solid rgba(255,255,255,.25);
  background: #101416; color: #93a6a1; font-weight: 800; font-size: 12px; cursor: pointer; }
.sy-kbd.on { background: var(--sy-accent); color: #06211d; border-color: transparent;
  box-shadow: 0 0 8px color-mix(in srgb, var(--sy-accent) 55%, transparent); }

/* 🎹 + 🎛️ — the INSTRUMENT buttons. They open whole instruments (Groovebox &
   SS-1 Synth), so they stand apart from the console row: bigger, teal-violet,
   glowing. Placed with maximum specificity to beat the theme's rocket rule. */
#dj-mixer #gb-toggle.dj-rocket, #dj-mixer #synth-toggle.dj-rocket {
  flex: 0 0 84px; font-size: 30px; padding: 12px 0; border-radius: 13px;
  background: linear-gradient(135deg, #2fe0d0, #7b5cff);
  border: 2px solid rgba(255,255,255,.35); color: #fff;
  box-shadow: 0 0 14px rgba(47,224,208,.45), 0 0 22px rgba(123,92,255,.3);
}
#dj-mixer #gb-toggle.dj-rocket:hover, #dj-mixer #synth-toggle.dj-rocket:hover { filter: brightness(1.2); transform: translateY(-1px); }
#dj-mixer #gb-toggle.dj-rocket.on, #dj-mixer #synth-toggle.dj-rocket.on {
  background: linear-gradient(135deg, #7b5cff, #2fe0d0);
  border-color: #fff; box-shadow: 0 0 18px rgba(123,92,255,.7), 0 0 26px rgba(47,224,208,.5);
}

/* SAMPLER ＋ ADD SOUND tile */
.dj-smp-add { border-style: dashed !important; opacity: .85; }
.dj-smp-add:hover { opacity: 1; border-color: var(--dj-accent, #ff8a3d) !important; }

/* SAMPLER sound chooser — studio tracks first, upload last */
.dj-smp-choose { position: absolute; z-index: 20; min-width: 220px; max-width: 300px; max-height: 300px; overflow-y: auto;
  background: #16121f; border: 1px solid rgba(255,255,255,.2); border-radius: 10px; padding: 6px; box-shadow: 0 10px 30px rgba(0,0,0,.6); }
.dj-smp-ch-h { font-size: 10.5px; font-weight: 800; letter-spacing: .08em; color: #b9a6e8; padding: 4px 8px; }
.dj-smp-ch-item { display: block; width: 100%; text-align: left; padding: 7px 9px; border: none; border-radius: 7px;
  background: none; color: #e9e2f7; font-size: 12.5px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dj-smp-ch-item:hover { background: rgba(255,255,255,.1); }
.dj-smp-ch-up { margin-top: 4px; border-top: 1px solid rgba(255,255,255,.12); border-radius: 0 0 7px 7px; color: #ffd28a; }
.dj-smp-ch-empty { padding: 6px 9px; font-size: 12px; color: #8d84a5; }

/* 🎧 THE DJ BUTTON — it opens the whole music booth (mixer, decks, Groovebox,
   SS-1), so it gets the corner seat: bigger, teal→violet like its instrument
   family, glowing. Absolute in the controls bar's free left corner. */
.controls #dj-btn.round {
  position: absolute; inset-inline-start: 14px; top: 50%; transform: translateY(-50%);
  /* 49px, from 50. This button is the dock's TALLEST child, so it — not the
     38px round ones — is what sets the bar's height: 49 + 10 padding + 2 border
     = 61px, exactly the 10% off 68 that Marcelo asked for. */
  width: 49px; height: 49px; font-size: 21px;
  /* 🎨 The Host picks both ends of the gradient (Style tab → 🎧 DJ button).
     The glow is mixed FROM those two colours rather than hard-coded, so a
     recolour takes the halo with it instead of leaving a teal ring around a
     red button. Defaults are the original teal → purple. */
  background: linear-gradient(135deg, var(--djbtn-a, #2fe0d0), var(--djbtn-b, #7b5cff));
  border: 2px solid rgba(255,255,255,.4);
  box-shadow: 0 0 16px color-mix(in srgb, var(--djbtn-a, #2fe0d0) 50%, transparent),
              0 0 26px color-mix(in srgb, var(--djbtn-b, #7b5cff) 35%, transparent);
}
/* 🎨 The two swatches that colour the button, parked right beside it. Above
   1100px the button is absolutely positioned at left:14px (49px wide), so these
   sit just past it; below that the button drops into the flex flow and so do
   they. Small on purpose — they are a setting, not a control you use mid-show. */
.djbtn-tint { display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; }
.djbtn-tint input[type="color"] {
  width: 16px; height: 16px; padding: 0; margin: 0; cursor: pointer;
  border: 1px solid rgba(255,255,255,.28); border-radius: 4px; background: none;
}
.djbtn-tint input[type="color"]:hover { border-color: rgba(255,255,255,.6); }
@media (min-width: 1101px) {
  .djbtn-tint { position: absolute; inset-inline-start: 68px; top: 50%; transform: translateY(-50%); }
}
.controls #dj-btn.round:hover { filter: brightness(1.15); transform: translateY(-50%) scale(1.05); }
.controls #dj-btn.round.active {
  background: linear-gradient(135deg, var(--djbtn-b, #7b5cff), var(--djbtn-a, #2fe0d0));
  border-color: #fff;
  box-shadow: 0 0 20px color-mix(in srgb, var(--djbtn-b, #7b5cff) 80%, transparent),
              0 0 30px color-mix(in srgb, var(--djbtn-a, #2fe0d0) 55%, transparent);
}
/* `:has`, not a bare `.controls`: guest.html carries the same class and loads the
   same stylesheet, but has no .controls-golive and no 🎧 — an unconditional
   reserve there doubled the guest dock's height on a phone and pushed its
   buttons into a single-file column. Reserve the lane only where the box that
   occupies it exists.

   Only the inline-END side needs it. The 🎧 button and its swatches are out of
   flow on the other side above 1100px, but the centred row never reaches that
   far: across ten widths in both directions the probe only ever found the
   stream-key box covering something, never the headphones. A start reserve as
   well cost 96px of the row's width for nothing and made a 1px step across the
   breakpoint jump the whole row sideways. */
.controls:has(.controls-golive) { padding-inline-end: 168px; }
@media (max-width: 1100px) { .controls #dj-btn.round { position: static; transform: none; flex: 0 0 auto; } .controls #dj-btn.round:hover { transform: scale(1.05); } }

/* 🧱 SS-1 Song Builder bar */
.sy-build { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 2px 0 10px;
  padding: 7px 10px; border-radius: 10px; background: rgba(0,0,0,.22); border: 1px dashed color-mix(in srgb, var(--sy-accent) 45%, transparent); }
.sy-sb-lbl { font-size: 11px; font-weight: 800; letter-spacing: .09em; color: var(--sy-accent); }
.sy-sb-status { font-size: 12px; color: #d6e5e1; opacity: .85; }

/* DJ keyboard-mode chip sits under the 💿 DECKS button */
#dj-kbd-btn { display: block; margin: 8px auto 0; min-height: 40px; font-size: 15px; font-weight: 800;
  padding: 9px 22px; border-radius: 14px; border-width: 2px; letter-spacing: .03em; }

/* Groovebox keyboard-mode chip */
#gb-kbd-btn { min-height: 34px; font-size: 13px; padding: 6px 14px; margin-left: 6px; }

/* ─── 🏠 HOME DASHBOARD — StreamYard-style landing screen (see
   docs/specs/home-dashboard.md). Left rail (account, nav, storage) + a
   5-view main column, swapped by showHomeView() in app.js. Reuses the dark
   look everywhere it can (.rail-btn colors, .seg/.seg-btn tabs, .stream-card
   rows, the .lib-usage-bar recipe) — StreamYard's shape, not its white
   styling and not its pixels. ───────────────────────────────────────────── */
#home { padding: 26px clamp(16px, 4vw, 40px) 60px; max-width: 1180px; margin: 0 auto; }
.home-layout { display: flex; gap: 26px; align-items: flex-start; }

/* Left rail: account chip, Home/Library/Destinations/Members nav, storage meter */
.home-rail {
  flex: 0 0 240px;
  display: flex; flex-direction: column; gap: 18px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 16px;
  padding: 18px 14px;
  position: sticky; top: 18px;
}
.home-acct-chip {
  display: flex; align-items: center; gap: 10px; text-align: left;
  width: 100%; padding: 8px; border-radius: 12px;
  background: transparent; border: 1px solid transparent;
}
.home-acct-chip:hover { background: var(--panel-2); border-color: var(--border); }
.home-avatar {
  flex: none; width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--grad); color: #fff; font-weight: 800; font-size: 15px;
}
.home-acct-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.home-acct-email {
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-acct-plan {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.home-nav { display: flex; flex-direction: column; gap: 2px; }
/* Same look as the studio side panel's .rail-btn (colors, hover, active) —
   laid out as a row instead of an icon-on-top stack, since this is a full
   sidebar link, not a compact tab. */
.home-nav-btn {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 10px 12px; border-radius: 10px;
  border: none; background: transparent; color: var(--muted);
  font-size: 14px; font-weight: 600; text-align: left;
}
.home-nav-icon { flex: none; width: 20px; font-size: 18px; line-height: 1; text-align: center; }
.home-nav-btn:hover { background: var(--panel-2); color: var(--text); }
.home-nav-btn.active { background: var(--panel-2); color: var(--accent); }

.home-rail-bottom { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border); }
.home-rail-bottom > .hint { margin: 0 0 4px; }
/* Same 2-line recipe as the studio side panel's .lib-usage-bar/#lib-usage-fill. */
.home-storage-bar { height: 8px; border-radius: 6px; background: rgba(255,255,255,.12); overflow: hidden; }
#home-storage-fill { height: 100%; width: 0%; border-radius: 6px; background: linear-gradient(90deg, #7c5cff, #b14aff); transition: width .3s; }
#home-storage-fill.warn { background: linear-gradient(90deg, #ff9d1c, #ff2d6b); }
#home-storage-text { display: block; margin: 6px 0 10px; }
.home-rail-bottom .ctrl { width: 100%; }

/* Main column: Home / Library / Destinations / Members / Account views —
   #home-view-* visibility is a plain .hidden toggle (global rule above). */
.home-main { flex: 1; min-width: 0; }
.home-section-h { font-size: 18px; margin: 0 0 14px; }

/* "Create" row — two cards, wrap instead of a second breakpoint */
.home-create-row { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 28px; }
.home-create-card {
  flex: 1 1 220px; display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  padding: 18px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--border); text-align: left;
  transition: transform .1s, border-color .15s;
}
.home-create-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* The hero sets the row's height; the other cards centre their content in
   it so the extra room reads as breathing room, never as a hole. */
.home-create-card { justify-content: center; }
/* 🔴 Live stream is the hero of the row — this is a live-streaming service
   first (owner: "much more dominant, a different colour to pop"), but
   dominance comes from WIDTH and COLOUR, never from empty air ("use the
   space nicely, don't create empty spaces… make it compact"). So the card
   lays its icon BESIDE the text in a two-column grid: it fills the extra
   room it takes and stays exactly as tall as its siblings. The gradient is
   fixed, not --accent — a live indicator must read as LIVE in every skin. */
#home-create-live {
  flex: 1.5 1 330px;
  display: grid; grid-template-columns: auto 1fr; align-items: center;
  column-gap: 15px; row-gap: 1px; padding: 18px 20px; border-radius: 16px;
  background: linear-gradient(135deg, #7f1020 0%, #d32036 52%, #f0632b 100%);
  border-color: rgba(255, 255, 255, .22);
  box-shadow: 0 8px 26px rgba(211, 32, 54, .26);
}
#home-create-live:hover { border-color: #fff; transform: translateY(-2px); }
#home-create-live .home-create-icon {
  grid-row: 1 / span 2; width: 66px; height: 66px; border-radius: 17px; font-size: 33px;
  background: rgba(255, 255, 255, .16);
  animation: liveGlow 2.4s ease-in-out infinite;
}
#home-create-live .home-create-title { grid-column: 2; align-self: end; font-size: 30px; line-height: 1.1; color: #fff; letter-spacing: .2px; }
#home-create-live .home-create-sub { grid-column: 2; align-self: start; font-size: 19.5px; line-height: 1.25; color: rgba(255, 255, 255, .88); }
@keyframes liveGlow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .30); }
  50% { box-shadow: 0 0 0 8px rgba(255, 255, 255, 0); }
}
@media (prefers-reduced-motion: reduce) { #home-create-live .home-create-icon { animation: none; } }
/* ✨ AI model promo tiles — fixed decorative gradients on purpose, never
   --accent (users repaint the studio with it; ads are not UI state). White
   ink is picked against each gradient's darkest stop, both themes. */
.home-ai-promos { margin-bottom: 28px; }
/* The section head is a row: copy on the left, the AI Video mark filling the
   space the heading leaves (owner: "put the writing and the logo of AI Video
   on that empty space"). It is a MARK, not a control — all four tiles below
   already open the generator, so a fifth door here would be noise ("it
   doesn't have to be a button, just keep it there"). Logo and word sit tight
   together; the word drops on narrow windows and the logo stays. */
.home-ai-head { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
.home-ai-head-copy { min-width: 0; }
.home-ai-brand {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px;
  color: var(--text); font-size: 17px; font-weight: 800; letter-spacing: .2px;
  user-select: none;
}
.home-ai-brand img { width: 42px; height: 42px; display: block; }
@media (max-width: 700px) { .home-ai-brand span { display: none; } }

.home-ai-promos .home-section-h { margin-bottom: 4px; }
.home-ai-promo-tag { margin: 0 0 12px; font-size: 13px; color: var(--muted); }
/* One row, a slider — settled ("I like this slider that you did very much
   keep that"; wrapping was rejected as worse). The tidy-up he asked for:
   tiles size themselves so exactly three fit and the fourth peeks a
   constant 28px at the row's right edge — the peek is the slider's own
   affordance, deliberate at every window width instead of whatever the old
   fixed 224px happened to leave over. */
.home-ai-promo-row { display: flex; gap: 14px; overflow-x: auto; padding: 2px 0 8px; scrollbar-width: thin; }
.ai-promo-tile { position: relative; flex: 0 0 calc((100% - 70px) / 3); min-width: 180px; min-height: 132px; border-radius: 16px;
  border: 1px solid rgba(255,255,255,.16); padding: 40px 16px 14px; text-align: start; cursor: pointer;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 4px; color: #fff;
  transition: transform .15s ease, box-shadow .15s ease; }
.ai-promo-tile:hover { transform: translateY(-2px); box-shadow: 0 6px 24px rgba(120, 80, 255, .35); }
.ap-g1 { background: linear-gradient(135deg, #1b1464 0%, #3b2fb8 45%, #0e6bd6 100%); }
.ap-g2 { background: linear-gradient(135deg, #4a0f45 0%, #a1125f 55%, #e0552b 100%); }
.ap-g3 { background: linear-gradient(135deg, #052c3f 0%, #0a6e63 60%, #15a06b 100%); }
.ap-g4 { background: linear-gradient(135deg, #151a2e 0%, #2a3350 60%, #41528f 100%); }
.ap-g5 { background: linear-gradient(135deg, #0f766e 0%, #4338ca 55%, #7e22ce 100%); }
/* The four tool doors, each tinted to its own mark so the row reads as a
   family of destinations rather than five variations of the same card. */
.ap-g6 { background: linear-gradient(135deg, #500724 0%, #be185d 55%, #fb7185 100%); }
.ap-g7 { background: linear-gradient(135deg, #2e1065 0%, #7c3aed 55%, #c084fc 100%); }
.ap-g8 { background: linear-gradient(135deg, #1e1b4b 0%, #4f46e5 55%, #818cf8 100%); }
.ap-g9 { background: linear-gradient(135deg, #134e4a 0%, #0d9488 55%, #a3e635 100%); }
.ap-g10 { background-image: linear-gradient(135deg, #4f46e5 0%, #0ea5e9 52%, #22d3ee 100%); }
/* Hailuo 3's red — nobody else's tile wears it. */
.ap-g11 { background-image: linear-gradient(135deg, #f43f5e 0%, #dc2626 55%, #b91c1c 100%); }
/* 🌍 Worlds: dusk sky falling to a lit horizon — the picture the tile shows
   when motion is off, rather than a colour with nothing to do with it. */
.ap-g12 { background-image: linear-gradient(135deg, #0c1e3e 0%, #1d4ed8 45%, #f59e0b 100%); }
.ap-badge { position: absolute; top: 12px; inset-inline-start: 12px; padding: 3px 9px; border-radius: 999px;
  background: linear-gradient(90deg, #ff2f92, #ff6a3d); color: #fff; font-size: 10px; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; }
.ai-promo-tile .ap-title { font-size: 17px; font-weight: 800; line-height: 1.15; }
.ai-promo-tile .ap-sub { font-size: 12px; color: rgba(255, 255, 255, .78); }
.ap-from { font-weight: 800; color: #fff; }
/* Living cards — the reference decks animate theirs. Until real Seedance
   output can loop inside the tiles (blocked on the provider key), the
   gradients themselves drift and a light sheen sweeps through, phase-offset
   per tile so the row never moves in lockstep. Decoration rides ::before/
   ::after, never the i18n spans. Stilled under prefers-reduced-motion. */
.ai-promo-tile { background-size: 220% 220%; animation: apDrift 11s ease-in-out infinite; overflow: hidden; }

/* ── Tiles that SHOW the thing instead of describing it ──────────────────
   A clip he made, and five characters he built. The media sits behind the
   words at low opacity with a dark wash over it, so the title stays readable
   on every frame — a tile whose text becomes unreadable on a bright frame is
   worse than a tile with no picture. */
/* His own frames are BRIGHT — a floodlit pool, a sunset ocean — and the first
   wash that looked fine over my dark test frame left his title unreadable.
   Sized against the worst stop, not the average one. */
.ai-promo-tile.ap-media { padding-top: 46px; }
.ai-promo-tile.ap-media::after { content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(6,8,20,.62) 0%, rgba(6,8,20,.34) 26%,
    rgba(6,8,20,.60) 52%, rgba(6,8,20,.88) 78%, rgba(6,8,20,.97) 100%); }
/* ...and a second, tighter shade directly under the words, so the sub-line
   survives a white wave or a stadium light behind it. */
.ai-promo-tile.ap-media > .ap-title::before { content: ''; position: absolute;
  inset-inline: -16px; top: -10px; bottom: -100px; z-index: -1;
  background: linear-gradient(180deg, transparent, rgba(6,8,20,.72) 30%, rgba(6,8,20,.88)); }
/* Lift the text above the wash WITHOUT touching its position — the badge is
   `position: absolute` by design, and blanket-setting `position: relative` on
   every child dropped it into the flow, straight through the title. */
.ai-promo-tile.ap-media > .ap-title,
.ai-promo-tile.ap-media > .ap-sub { position: relative; z-index: 2;
  text-shadow: 0 1px 3px rgba(0,0,0,.9), 0 2px 14px rgba(0,0,0,.7); }
.ai-promo-tile.ap-media > .ap-sub { color: rgba(255,255,255,.92); }
.ai-promo-tile.ap-media > .ap-badge { z-index: 2; }
.ap-vid, .ap-shots { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; border-radius: inherit; }
.ap-shots { display: block; }
.ap-shots img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center 22%; opacity: 0;
  transition: opacity 900ms ease; border-radius: inherit; }
.ap-shots img.on { opacity: 1; }
/* Motion is decoration here: a looping video behind text is exactly what a
   person who asked for less of it does not want. */
@media (prefers-reduced-motion: reduce) {
  .ap-vid { display: none; }
  .ap-shots img { transition: none; }
}

.home-ai-promo-row .ai-promo-tile:nth-child(2) { animation-delay: -1.2s; }
.home-ai-promo-row .ai-promo-tile:nth-child(3) { animation-delay: -2.4s; }
.home-ai-promo-row .ai-promo-tile:nth-child(4) { animation-delay: -3.6s; }
.home-ai-promo-row .ai-promo-tile:nth-child(5) { animation-delay: -4.8s; }
.home-ai-promo-row .ai-promo-tile:nth-child(6) { animation-delay: -6s; }
.home-ai-promo-row .ai-promo-tile:nth-child(7) { animation-delay: -7.2s; }
.home-ai-promo-row .ai-promo-tile:nth-child(8) { animation-delay: -8.4s; }
.home-ai-promo-row .ai-promo-tile:nth-child(9) { animation-delay: -9.6s; }
@keyframes apDrift { 0%, 100% { background-position: 0% 0%; } 50% { background-position: 100% 100%; } }
.ai-promo-tile::after { content: ''; position: absolute; top: -60%; bottom: -60%; left: -45%; width: 45%;
  background: linear-gradient(105deg, transparent, rgba(255, 255, 255, .17), transparent);
  transform: skewX(-18deg) translateX(-160%); animation: apSheen 7s ease-in-out infinite; pointer-events: none; }
.home-ai-promo-row .ai-promo-tile:nth-child(2)::after { animation-delay: 1.75s; }
.home-ai-promo-row .ai-promo-tile:nth-child(3)::after { animation-delay: 3.5s; }
.home-ai-promo-row .ai-promo-tile:nth-child(4)::after { animation-delay: 5.25s; }
@keyframes apSheen { 0%, 55% { transform: skewX(-18deg) translateX(-160%); } 80%, 100% { transform: skewX(-18deg) translateX(560%); } }
/* Footage-backed tiles: the loop sits under a scrim so the type stays
   legible over any frame. Explicit z-order because the video is an
   absolutely positioned CHILD — without it, static spans paint underneath.
   Video 0 < scrim 1 < text 2 < sheen 3. The gradient remains the poster
   and the reduced-motion state. */
.ai-promo-tile .ap-video { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; pointer-events: none; }
.ai-promo-tile.ap-has-video::before { content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(10, 10, 22, .12) 0%, rgba(8, 8, 18, .68) 82%); pointer-events: none; }
.ai-promo-tile .ap-badge, .ai-promo-tile .ap-title, .ai-promo-tile .ap-sub { z-index: 2; }
.ai-promo-tile .ap-title, .ai-promo-tile .ap-sub { position: relative; }
.ai-promo-tile::after { z-index: 3; }
@media (prefers-reduced-motion: reduce) {
  .ai-promo-tile, .ai-promo-tile::after { animation: none; }
  .ai-promo-tile .ap-video { display: none; }
}
.home-create-icon {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 10px; font-size: 19px;
  background: rgba(255,255,255,.08);
}
.home-create-title { font-size: 15px; font-weight: 700; }
.home-create-sub { font-size: 12px; color: var(--muted); }

/* "Your streams" header row: title + the Upcoming/Past .seg tabs */
.home-streams-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.home-streams-head .home-section-h { margin: 0; }
.home-tabs { width: auto; min-width: 200px; }

/* Row actions on a .stream-card: only "Enter studio" (.stream-enter) should
   grow wide — Edit/Delete stay their natural content width. .ctrl.small
   alone sets flex:1 (two classes = higher specificity than a lone
   .stream-edit/.stream-del), so a same-specificity, later-declared rule is
   needed here rather than relying on .stream-del's older lone declaration —
   that's what let the 🗑 button stretch to match "Enter studio" and squeeze
   .stream-sub's date/dot separators onto a wrapped line. */
.ctrl.stream-edit, .ctrl.stream-del { flex: 0 0 auto; white-space: nowrap; }

/* Empty states (no upcoming / nothing past yet) */
.home-empty { padding: 34px 16px; text-align: center; border: 1px dashed var(--border); border-radius: 14px; }
.home-empty-title { font-size: 15px; font-weight: 700; margin: 0 0 4px; }
.home-empty .hint { max-width: 380px; margin: 0 auto; }
.home-empty .home-empty-btn { margin-top: 14px; }

/* Library/Destinations views reuse .lib-list/.lib-item and .yt-destinations
   as-is (same classes as the studio side panel — no new rules needed).
   Members/Account reuse #team-card + the backup block's own styling,
   moved wholesale from #setup. */

@media (max-width: 860px) {
  .home-layout { flex-direction: column; }
  .home-rail { flex: none; width: 100%; position: static; top: auto; }
}

/* ─── Touch screens (iPad / iPhone) ──────────────────────────────────
   Every control in the studio is a real <button>, so one rule covers the
   lot. The problem on iPad was not that touch was unwired — the app is
   already all Pointer Events — it was Safari's own gestures stealing the
   input:

   • touch-action: manipulation turns OFF double-tap ZOOM. Hitting a cue
     pad twice quickly read as a double-tap, so Safari zoomed the page
     instead of firing the second hit — the decks felt dead to touch.
   • -webkit-touch-callout stops a long press (SCATTER holds, hot-cue
     holds) raising the iOS copy / look-up sheet in the middle of a gesture.
   • the user-select pair stops a drag turning into a text selection.
   • tap-highlight removes the grey flash iOS paints over every button.

   The drag controls (jog wheels, faders, knobs, XY pads) already declare
   touch-action: none and must keep it — they have to swallow scrolling as
   well as zoom. Those are class selectors, so they outrank this element
   selector on specificity and win no matter where this block sits. */
button, .rail-btn, .seg-btn, [role="button"] {
  touch-action: manipulation;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* The drag controls are divs/canvases rather than buttons, so the rule above
   does not reach them — and they are the ones you actually drag. Without
   this, a scratch on the jog wheel or a pull on a fader can start a TEXT
   SELECTION under the finger on iOS, which hijacks the gesture halfway
   through. They keep their own touch-action: none (declared with each
   control); this only adds the selection/callout guards. */
.cdj-jog, .dj-jog, .cdj-tfader, .dj-tf-track, .dj-bfxs-pad, .dj-xpad,
.dj-trim, .dj-eqk, .dj-cfxk, .dj-colk,
.gb-dial, .gb-perfpad, .gb-xy, .sy-key, .sy-ribbon {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* The floating DJ booth monitor's drag bar and resize corners. Their JS is
   already correct — pointer events with setPointerCapture and preventDefault
   — but on iOS preventDefault() on pointerdown does NOT stop the page
   claiming the gesture as a scroll. Only touch-action does. Without it the
   monitor simply would not move on iPad: the drag scrolled the page instead.
   Declared here (outside the monitor-mode block) so it applies wherever those
   handles exist. */
.djmon-drag, .djmon-rs {
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* ─── Store colour theming (Seller Dashboard / Platform Control Panel) ──
   The picker itself. Everything it changes is a CSS custom property on
   <html>, so the whole page re-tints live — see public/store-theme.js. */
.th-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0; }
.th-lbl { font-size: 13px; color: var(--muted); min-width: 86px; }
.th-swatches { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.th-sw { width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; }
.th-sw.on { border-color: var(--text); box-shadow: 0 0 0 2px var(--bg) inset; }
.th-tone { padding: 6px 12px; border-radius: 999px; border: 1px solid var(--border); font: inherit; font-size: 12px;
  cursor: pointer; background: var(--panel-2); color: var(--text); }
.th-range { flex: 1 1 160px; max-width: 240px; accent-color: var(--accent); }
/* Same rule as .studio-swatch.active — these pills sit under the very picker
   that sets the accent, so the selected outline may not be painted in it. */
.th-tone.on { outline: 2px solid #ff4d6d; outline-offset: 1px; font-weight: 700; }
.th-input { width: 42px; height: 30px; padding: 0; border: 1px solid var(--border); border-radius: 8px; background: transparent; cursor: pointer; }
.th-hex { width: 90px; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--tile); color: var(--text); font: inherit; font-size: 13px; }
.th-reset { width: auto !important; }

/* Store pages hardcoded white-alpha hairlines, which vanish on the Light tone.
   --hair is set by applyTheme() per tone; the fallback keeps the old look if
   the theme script has not run. */
.sd-card, .pc-card, .sg-card { border-color: var(--hair, rgba(255,255,255,.08)) !important; }
.pc-table th, .pc-table td, .sg-step { border-bottom-color: var(--hair, rgba(255,255,255,.07)) !important; }

/* Accent buttons get a visible edge only when the theme says the accent is
   too close to its own background (white-on-white, black-on-black). The
   fallback is transparent, so pages without store-theme.js are unaffected,
   and box-shadow is used rather than border to avoid any layout shift. */
.primary, .cartbtn, .add {
  outline: 1px solid var(--accent-edge, transparent);
  outline-offset: -1px;
}

/* 🎧 Music Studio add-on strip — under the plan cards in the pricing modal.
   Shown to everyone (the price is never a surprise); the button changes with
   where you stand: buy it, keep it, or drop it. */
.addon-strip { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 18px 0 4px; padding: 14px 16px; border-radius: 14px;
  border: 1px solid var(--accent2, #ff5aa0); background: rgba(139,90,255,.10); }
.addon-info { flex: 1 1 260px; min-width: 0; }
.addon-name { font-weight: 700; font-size: 15px; }
.addon-desc { font-size: 12.5px; opacity: .78; margin-top: 2px; }
.addon-btn { flex: 0 0 auto; min-height: 38px; padding: 0 18px; border-radius: 10px; cursor: pointer;
  font: inherit; font-weight: 700; color: #fff; border: 0;
  background: linear-gradient(135deg, var(--accent, #8b5aff), var(--accent2, #ff5aa0)); }
.addon-btn.on { background: none; border: 1px solid var(--line, rgba(255,255,255,.18)); color: inherit; }
.addon-btn:disabled { opacity: .6; cursor: default; }

/* ⏺ Studio record button. It used to be grey until you pressed it, which made
   it read as "some other control" in a row of round grey buttons — a record
   button is red on every camera ever made, and that is the whole reason you can
   find it without looking. Red at rest, brighter and pulsing while recording. */
.controls #studio-rec-btn.round {
  background: rgba(255,45,80,.18);
  border-color: rgba(255,45,80,.55);
  color: #ff3b60;
  /* A quarter larger than its 38px row-mates (Marcelo, 2026-08-13): the one
     button that starts a take earns the extra presence, same reasoning as
     its resting red. Height handling lives with each layout below — out of
     flow beside the 🎧 on a wide window, overhanging the row on a narrow one
     — because the two cases need opposite things and one shared margin gave
     the wide case a tag sitting 5px above the headphones. */
  width: 48px; height: 48px; font-size: 20px;
}
.controls #studio-rec-btn.round:hover:not(:disabled) {
  background: rgba(255,45,80,.3); border-color: rgba(255,45,80,.85);
}
#studio-rec-btn.recording { background: rgba(255,45,80,.35); border-color: rgba(255,45,80,.7);
  color: #fff; animation: libRecPulse 1.2s ease-in-out infinite; }
/* ⏺ parks beside the 🎧 (Marcelo, 2026-08-13: "move the recording button next
   to the DJ mixer button — there is a space there in the left"), and the two
   circles share ONE centre line ("could you align them"): both are absolute
   with top:50% + translateY(-50%), so they centre on the bar and therefore on
   each other, whatever either diameter becomes. Start offset clears the 🎧
   (14 + 49) and its two swatches (16 + gaps) → 96px. Something lives on the
   start side now, so that lane gets the same reserve the golive box earned on
   the end side: 96 + 48 + 8. These rules sit AFTER the base block on purpose —
   same specificity, so file order is what decides. */
@media (min-width: 1101px) {
  .controls #studio-rec-btn.round {
    position: absolute; inset-inline-start: 96px; top: 50%;
    transform: translateY(-50%); margin: 0;
  }
  .controls:has(#studio-rec-btn) { padding-inline-start: 152px; }
}
/* Narrow: back in the flex flow, where the extra 10px of diameter would fatten
   the strip — hand it back so the box stays as slim as its 38px buttons set it
   ("keep the box narrow as it is", same day). */
@media (max-width: 1100px) {
  .controls #studio-rec-btn.round { margin: -5px 0; }
}

/* ── 🅣 Text on stage ─────────────────────────────────────────────────────
   The layer is a plain sibling of the other stage overlays; only the text
   controller ever writes into it, so the compositor's periodic innerHTML
   re-render of the logo/overlay layers can never wipe live text. */
/* 🅣 Text on stage rides ABOVE the overlay graphics (z 12) and the logos —
   the words are the message, the graphics are the frame around it. This
   used to be z 6, which put text UNDER every overlay in the studio while
   the compositor and guests already drew it on top: the host was the only
   person seeing the wrong order. Still below the effects canvas (18), so
   confetti falls in front of everything, as it does today. */
/* 🅣 stage text draws ABOVE banners (Marcelo: "I want text on stage to be above
   it, not behind"), and the same way on all three surfaces. */
.text-layer { position: absolute; inset: 0; z-index: 15; pointer-events: none; overflow: hidden; }
/* Keyed on .text-ov, the element the RENDERER creates, not on .text-layer, the
   element the STAGE happens to wrap it in. tx-engine.js builds the same
   .text-ov > .tx-block anywhere it is asked to — including the designer's
   preview pane, which has no .text-layer ancestor. Scoped to the layer, the
   preview's block got no `position: absolute` and no centring translate, so it
   fell to the top-left corner and sat there clipped however big the pane was
   (Marcelo: "it is too much in the corner, it needed to be in the middle").
   One renderer, one set of rules, wherever it draws. */
.text-ov { position: absolute; inset: 0; overflow: hidden; display: none; }
.text-ov .tx-block { position: absolute; transform: translate(-50%, -50%); text-align: center;
  font-weight: 700; line-height: 1.2; white-space: pre-wrap; }
.text-ov .tx-inner { display: inline-block; }
.text-ov .tx-line { white-space: nowrap; unicode-bidi: plaintext; }
.text-ov .tx-line.units { position: relative; height: 1.2em; margin: 0 auto; }
.text-ov .tx-u { position: absolute; top: 0; display: inline-block; white-space: pre; }

/* Media-tab cards: one tap on air, one tap off. */
.stx-cards { display: flex; flex-direction: column; gap: 6px; margin: 6px 0; }
/* Shown only once two or more texts are up, when clearing the stage one card at
   a time becomes a hunt through a scrolling list. */
.stx-alloff { align-self: flex-start; background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.16); color: inherit; border-radius: 8px;
  padding: 4px 12px; font-size: 11.5px; cursor: pointer; }
.stx-alloff:hover { border-color: #ff5aa0; color: #ff5aa0; }
.stx-card { display: flex; align-items: center; gap: 6px; background: var(--panel2, #1e1430);
  border: 1px solid var(--line, rgba(255,255,255,.12)); border-radius: 10px; padding: 6px 8px; }
.stx-card.live { border-color: #ff4d6d; box-shadow: 0 0 0 2px rgba(255,77,109,.28); }
.stx-go { flex: 1; min-width: 0; text-align: left; background: none; border: 0; color: inherit;
  font: inherit; font-weight: 700; font-size: 13px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stx-live { font-size: 9px; font-weight: 800; letter-spacing: .08em; color: #ff4d6d; }
.stx-edit, .stx-del, .stx-loop { background: none; border: 0; color: inherit; opacity: .7; cursor: pointer; font-size: 13px; padding: 2px 4px; }
/* 🔁 Loop state. Hard-coded pink like .stx-tile.on rather than --accent:
   the accent is the host's to repaint, and a repaint must never make an
   ON state invisible. Dimmed when off so the row does not read as busy. */
.stx-loop { opacity: .35; }
.stx-loop.on { opacity: 1; color: #ff5aa0; text-shadow: 0 0 8px rgba(255,90,160,.55); }
.stx-loopbtn { flex: 1; text-align: left; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14);
  border-radius: 8px; color: inherit; cursor: pointer; font-size: 11px; padding: 5px 9px; }
.stx-loopbtn.on { border-color: #ff5aa0; color: #ff5aa0; background: rgba(255,90,160,.12); }
.stx-hint { font-size: 11px; opacity: .65; padding: 2px 2px 6px; }
.stx-edit:hover, .stx-del:hover { opacity: 1; }

/* The designer. Its own preview is the reason auditions never hit the stream. */
.stx-back { position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 9999;
  display: grid; place-items: center; padding: 16px; }
.stx-modal { width: min(430px, 96vw); max-height: 92vh; overflow-y: auto; background: var(--panel, #171022);
  border: 1px solid var(--line, rgba(255,255,255,.14)); border-radius: 14px; padding: 12px; }
/* A true mini-stage, not a letterbox strip. The renderer sizes the text as a
   percentage of its HOST's height and breaks its lines against a 16:9 frame
   (tx-engine.js layout()), so a 132px-tall pane that was ~6:1 wide made those
   two disagree: the text overflowed and sat clipped in a corner instead of
   where it will actually appear (Marcelo: "it needed to be in the middle to
   see that perfectly"). Matching the stage's shape makes the preview honest —
   what you see here is where it lands on air. Capped by height so the modal
   stays usable on a short window. */
.stx-preview { position: relative; width: 100%; aspect-ratio: 16 / 9; height: auto;
  max-height: 42vh; max-width: calc(42vh * 16 / 9);
  border-radius: 10px; background: #0a0a12;
  overflow: hidden; margin: 0 auto 8px; }
/* Portrait studios get a portrait audition. :has() rather than a sibling
   selector — the modal is appended to <body>, not next to #stage. */
body:has(#stage.vertical) .stx-preview { aspect-ratio: 9 / 16; max-width: calc(42vh * 9 / 16); }
.stx-ta { width: 100%; font: inherit; font-size: 13px; background: var(--panel2, #1e1430); color: inherit;
  border: 1px solid var(--line, rgba(255,255,255,.14)); border-radius: 8px; padding: 6px 8px; resize: vertical; }
.stx-sec { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; opacity: .65;
  margin: 10px 2px 6px; padding-top: 8px; border-top: 1px solid var(--line, rgba(255,255,255,.12)); }
.stx-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }
.stx-tile { position: relative; background: #000; border: 2px solid var(--line, rgba(255,255,255,.14));
  border-radius: 8px; padding: 6px 2px 3px; color: inherit; cursor: pointer; font: inherit; }
.stx-tile.on { border-color: #ff5aa0; box-shadow: 0 0 0 2px rgba(255,90,160,.3); }
.stx-abc { display: block; font-weight: 700; font-size: 14px; }
.stx-nm { display: block; font-size: 8px; letter-spacing: .03em; opacity: .8; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stx-gl { position: absolute; right: 3px; bottom: 3px; font-style: normal; font-size: 9px; opacity: .85; }
.stx-seg { display: flex; gap: 4px; margin-bottom: 6px; }
.stx-ph { position: relative; flex: 1; min-width: 0; height: 28px; font: inherit; font-size: 11px; font-weight: 700;
  cursor: pointer; color: inherit; opacity: .7; background: var(--panel2, #1e1430);
  border: 1px solid var(--line, rgba(255,255,255,.14)); border-radius: 8px; }
.stx-ph.on { opacity: 1; border-color: #ff5aa0; background: rgba(255,90,160,.18); }
.stx-ph.filled::after { content: ""; position: absolute; top: 4px; right: 5px; width: 5px; height: 5px;
  border-radius: 50%; background: #ff5aa0; }
.stx-row { display: flex; align-items: center; gap: 8px; padding: 5px 2px; }
/* The stage-text pad — same cluster as the overlay drawer and the editor,
   wearing this sheet's chrome. A wrapping grid so seven buttons never become
   one tall column in a narrow panel. */
.stx-pad-row { display: block; }
.stx-pad-row .stx-val { display: block; text-align: right; margin-bottom: 5px; white-space: nowrap; }
.stx-pad { display: grid; gap: 4px; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); }
.stx-pad-b { min-width: 40px; height: 30px; padding: 0 6px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--panel2); color: var(--text);
  font-size: 13px; line-height: 1; cursor: pointer; }
.stx-pad-b:hover { border-color: var(--accent); }
.stx-pad-b[data-pad="reset"] { grid-column: 1 / -1; }
/* − value ＋ in the name-tag panel. The range stays in the DOM — every
   listener, every sync and the value itself still live on it — it just stops
   being the thing you touch. */
.nt-step-row { display: flex; align-items: center; gap: 6px; }
.nt-stepped { display: none; }
.nt-step { min-width: 44px; height: 30px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--panel2); color: var(--text); font-size: 14px; line-height: 1; cursor: pointer; }
.nt-step:hover { border-color: var(--accent); }
.nt-step-row .bn-val { flex: 1; text-align: center; }
/* The share sheet's name-tag block. Sized as CONTROLS, not as list rows:
   .tile-menu-item's padding is for a column of tap targets, and nine settings
   wearing it made a sheet taller than the stage it sits on. */
.tmi-tag { display: flex; align-items: center; gap: 5px; padding: 2px 10px !important;
  min-height: 0 !important; cursor: default; flex-wrap: nowrap; line-height: 1.1; }
/* nowrap is the whole fix: every one of these rows was breaking onto a second
   line, which is where 357px of sheet came from. The label gives up width
   instead (minmax-style basis + min-width:0), and only the theme row — four
   chips — is allowed to wrap. */
.tmi-tag-l { flex: 0 1 66px; min-width: 0; font-size: 10px; opacity: .65; line-height: 1.1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tmi-tag-wrap { flex-wrap: wrap; }
.tmi-chip { padding: 2px 5px !important; font-size: 10px !important; min-height: 0 !important;
  line-height: 1.2 !important; white-space: nowrap; }
.tmi-step { min-width: 24px !important; height: 20px !important; font-size: 11px !important;
  padding: 0 !important; }
/* The trio stays TOGETHER. flex:1 on the reading pushed − and ＋ to opposite
   ends of a 900px sheet, so a step meant crossing the whole panel with the
   mouse — and it was what made the sheet look mostly empty. */
.tmi-val { flex: 0 0 46px; text-align: center; font-size: 10px; }
/* And the sheet itself stops being as wide as its longest sentence. The audio
   hint was setting the width for every row above it. */
.tile-menu { max-width: 300px; }
.tile-menu .tile-menu-item { white-space: normal; }
.tmi-in { flex: 1 1 auto; min-width: 0; padding: 2px 6px !important; font-size: 11px; height: 22px; }
.tmi-col { width: 30px; height: 20px; padding: 0; border: 1px solid var(--border); border-radius: 5px; background: none; }
.stx-lbl { flex: 0 0 96px; font-size: 12px; opacity: .8; }
.stx-row input[type=range] { flex: 1; min-width: 0; accent-color: #ff5aa0; }
.stx-val { flex: 0 0 52px; text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; color: #ff5aa0; }
.stx-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }

/* ⏺ Record, when it cannot record yet (no object storage configured). Dimmed
   with a question mark rather than dead: a control that does nothing and says
   nothing is the worst answer a button can give. The explanation is on hover
   and on click — but the state is visible without either. */
.controls #studio-rec-btn.round.unavailable {
  background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.14);
  color: rgba(255,255,255,.35);
  position: relative;
}
.controls #studio-rec-btn.round.unavailable::after {
  content: "?"; position: absolute; right: 2px; bottom: 0;
  font-size: 13px; font-weight: 800; color: var(--warm, #ffb347);
}

/* 🔤 The host's chosen stage font reaches every piece of on-stage text through
   one variable. Unset = the studio's own UI font, exactly as before. */
#stage .label,
#stage .banner-layer,
#stage .comment-lower .cl-name, #stage .comment-lower .cl-text,
#stage .chat-overlay { font-family: var(--stage-font, inherit); }

/* ── CHOOSE FONT ────────────────────────────────────────────────────────────
   Shared by the editor and the studio designer. Selected/starred states are
   hard-coded pink, never var(--accent): hosts repaint the studio with the
   accent and an accent-coloured "selected" would vanish on them. */
.fp-back { position: fixed; inset: 0; z-index: 10000; background: rgba(4,6,12,.62);
  display: flex; align-items: center; justify-content: center; backdrop-filter: blur(3px); }
.fp-box { width: min(430px, 96vw); max-height: min(680px, 88vh); display: flex; flex-direction: column;
  background: var(--panel, #171226); color: var(--text, #e9ecf3);
  border: 1px solid rgba(255,255,255,.14); border-radius: 16px; overflow: hidden;
  box-shadow: 0 24px 70px rgba(0,0,0,.6); }
.fp-head { padding: 12px 12px 8px; border-bottom: 1px solid rgba(255,255,255,.09); }
.fp-titlerow { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.fp-title { font-size: 15px; font-weight: 700; }
.fp-done { background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.16); color: inherit;
  border-radius: 8px; padding: 5px 14px; font-size: 12px; font-weight: 600; cursor: pointer; }
.fp-done:hover { background: rgba(255,255,255,.14); }
.fp-search { width: 100%; box-sizing: border-box; padding: 9px 12px; font-size: 14px;
  background: rgba(0,0,0,.28); border: 1px solid rgba(255,255,255,.14); border-radius: 10px;
  color: inherit; outline: none; }
.fp-search:focus { border-color: #ff5aa0; }
.fp-chips { display: flex; gap: 6px; overflow-x: auto; padding: 9px 0 2px; scrollbar-width: none; }
.fp-chips::-webkit-scrollbar { display: none; }
.fp-chip { flex: none; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14);
  color: inherit; border-radius: 999px; padding: 5px 12px; font-size: 11.5px; cursor: pointer; white-space: nowrap; }
.fp-chip.on { background: rgba(255,90,160,.16); border-color: #ff5aa0; color: #ff5aa0; }
.fp-body { display: flex; min-height: 0; flex: 1; }
.fp-scroll { flex: 1; overflow-y: auto; position: relative; -webkit-overflow-scrolling: touch; }
.fp-spacer { width: 1px; }
.fp-win { position: absolute; left: 0; right: 0; top: 0; will-change: transform; }
/* Fixed row height is what lets a specimen land mid-scroll without shifting
   anything, and what makes the virtual window's arithmetic exact. */
.fp-row { height: 52px; box-sizing: border-box; display: flex; align-items: center; gap: 8px;
  padding: 0 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,.05); }
.fp-row:hover { background: rgba(255,255,255,.05); }
.fp-row.on { background: rgba(255,90,160,.12); box-shadow: inset 3px 0 0 #ff5aa0; }
.fp-row.fp-header { cursor: default; background: rgba(255,255,255,.04); height: 52px;
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase; opacity: .72; border-bottom: 0; }
.fp-row.fp-header:hover { background: rgba(255,255,255,.04); }
.fp-main { flex: 1; min-width: 0; }
.fp-name { font-size: 20px; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fp-header .fp-name { font-size: 11px; font-weight: 700; }
.fp-sub { font-size: 10.5px; opacity: .55; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fp-tick { color: #ff5aa0; font-weight: 700; flex: none; }
.fp-star { flex: none; background: none; border: 0; color: inherit; opacity: .28; cursor: pointer;
  font-size: 15px; padding: 6px; line-height: 1; }
.fp-star:hover { opacity: .6; }
.fp-star.on { opacity: 1; color: #ff5aa0; }
.fp-rail { flex: none; width: 22px; display: flex; flex-direction: column; justify-content: center;
  align-items: center; gap: 0; padding: 4px 0; border-left: 1px solid rgba(255,255,255,.06); }
.fp-railL { background: none; border: 0; color: inherit; opacity: .5; cursor: pointer;
  font-size: 9px; line-height: 1.12; padding: 0; width: 100%; }
.fp-railL:hover { opacity: 1; color: #ff5aa0; }
/* The button that replaces the old <select>: shows the current font IN itself. */
.fp-open { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
  background: rgba(0,0,0,.24); border: 1px solid rgba(255,255,255,.16); border-radius: 8px;
  color: inherit; padding: 7px 10px; font-size: 15px; cursor: pointer; text-align: left; }
.fp-open:hover { border-color: #ff5aa0; }
.fp-open .fp-chev { opacity: .5; font-size: 11px; flex: none; }
.fp-open .fp-cur { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 620px) {
  /* Phone: a bottom sheet with thumb-sized rows, the way a native picker behaves. */
  .fp-back { align-items: flex-end; }
  .fp-box { width: 100%; max-width: none; max-height: 88vh; border-radius: 16px 16px 0 0; }
  .fp-row { height: 52px; padding: 0 14px; }
  .fp-rail { width: 26px; }
  .fp-railL { font-size: 10px; padding: 1px 0; }
}

/* ── ⋮ Per-admin permissions menu (Members panel) ─────────────────────────
   The tile-menu idiom reused on team rows: the button is always visible
   (rows have no hover-reveal), the popover floats from <body>. Chips use
   var(--success), NEVER var(--accent) — accent is user-repaintable. */
.team-trow .tile-menu-wrap { position: static; }
.team-trow .tile-menu-btn { opacity: 1; width: 30px; height: 30px; font-size: 17px; }
.team-perm-menu { min-width: 260px; z-index: 1300; }
.team-perm-item { display: flex; align-items: center; gap: 10px; }
.pm-chip {
  font-size: 10px; font-weight: 800; letter-spacing: .4px;
  padding: 2px 9px; border-radius: 999px;
  background: rgba(128,128,140,.22); color: var(--muted, #99a);
}
.pm-chip.on { background: rgba(61,220,151,.16); color: var(--success, #3ddc97); }
.team-perm-note { padding: 6px 10px 4px; max-width: 240px; white-space: normal; }
.team-perm-remove { border-top: 1px solid var(--border); border-radius: 0 0 8px 8px; margin-top: 4px; }

/* A capability the owner switched OFF for this admin. Its own class so the
   schedule/live state machines toggling .hidden can never un-hide it. */
.perm-hidden { display: none !important; }

:root { --ov-heart: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'><path d='M50 88 C22 64 4 46 4 28 C4 12 16 4 29 4 C38 4 46 9 50 17 C54 9 62 4 71 4 C84 4 96 12 96 28 C96 46 78 64 50 88 Z'/></svg>"); }
/* 🖼 OVERLAY SHAPES — one definition, three surfaces (studio · compositor ·
   guests), so a circle is the SAME circle everywhere. The cut lands on the
   <img>, never on the box: the box carries the resize handles, and clipping it
   would slice them off the corners (the stage tiles learned this the hard way).
   Spec: docs/specs/overlay-shapes.md */
.ov-crop { position: absolute; inset: 0; overflow: hidden; }
.ov-circle > .ov-crop { border-radius: 50%; }
.ov-tri > .ov-crop {
  -webkit-clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%);
          clip-path: polygon(var(--tri-apex, 50%) 0%, 100% 100%, 0% 100%); }
.ov-heart > .ov-crop {
  -webkit-mask-image: var(--ov-heart); mask-image: var(--ov-heart);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
/* A shaped overlay fills its box. A transparent PNG wider than its box would
   otherwise float inside a letterboxed rectangle with the cut around it —
   which reads as a bug. Plain rectangles keep whatever fit they had. */
.ov-circle > .ov-crop > img, .ov-tri > .ov-crop > img, .ov-heart > .ov-crop > img { object-fit: cover; }
/* 🔻 A flipped triangle hangs from the top edge — the same cut the stage
   tiles use, with the sliding point still doing the sliding. */
.ov-tri.ov-flip > .ov-crop {
  -webkit-clip-path: polygon(var(--tri-apex, 50%) 100%, 100% 0%, 0% 0%);
          clip-path: polygon(var(--tri-apex, 50%) 100%, 100% 0%, 0% 0%); }


/* ↔ The triangle's point slides, exactly like the stage triangles. */
/* ⇅ Flip the triangle, right on the overlay — the stage tiles put theirs in
   the same place with the same glyph, so the hand already knows where it is. */
.overlay-box .ov-flip-badge { display: none; }
.overlay-box.ov-tri .ov-flip-badge {
  display: flex; position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12%; width: 26px; height: 26px; align-items: center; justify-content: center;
  border-radius: 50%; border: 0; background: rgba(0,0,0,.55); color: #fff; font-size: 14px;
  cursor: pointer; opacity: 0; z-index: 7; padding: 0; transition: opacity .15s; touch-action: none; }
.overlay-box.ov-tri:hover .ov-flip-badge { opacity: .9; }
.overlay-box.ov-tri.ov-flip .ov-flip-badge { bottom: auto; top: 12%; }

/* The point-slider belongs to triangles alone — on a circle it would be a
   handle that does nothing, which is worse than no handle. */
.overlay-box .ov-apex { display: none; }
.overlay-box.ov-tri .ov-apex { display: block; }
.overlay-box.ov-tri.ov-flip .ov-apex { top: auto; bottom: 0; }
.overlay-box .ov-apex {
  touch-action: none;                 /* a finger must drag it, not scroll the page */
  position: absolute; top: 0;         /* fully inside the box — it was half-clipped */ left: var(--tri-apex, 50%); transform: translateX(-50%);
  width: 22px; height: 14px; cursor: ew-resize; z-index: 6; opacity: 0; transition: opacity .15s;
  background: rgba(10,14,30,.8); border: 1px solid var(--border); border-radius: 6px;
  color: #fff; font-size: 9px; line-height: 12px; text-align: center; }
.overlay-box:hover .ov-apex { opacity: 1; }

/* The per-overlay shape pickers in the OVERLAY panel — one row per graphic
   that is actually on stage, because several can be up at once with
   different shapes. */
/* 🔲 The per-overlay shape settings get their OWN scroll window, the same
   call the background Sets got (Marcelo: "add here on the overlay also a
   slider where we do the settings as well"). One row per overlay ON STAGE,
   and several can be up at once — five of them pushed "+ Add your own
   overlay" and everything below it off the panel.

   Sized at 1.5x a three-row window on Marcelo's second look ("maybe I make a
   mistake on the size, just make that slider 50% bigger") — about four and a
   quarter rows, so the fourth is half-shown and says "there is more here"
   without a hunt. Written as the multiplier rather than a pixel count so the
   row basis below still drives it.

   --ov-shape-row-h is re-measured from a real painted row by
   renderOverlayShapes(); the 26px fallback is that measurement taken on
   2026-08-14, so the window is right before any script runs. */
.ov-shapes {
  display: flex; flex-direction: column; gap: 6px; margin: 8px 0 2px;
  max-height: calc(1.5 * (3 * var(--ov-shape-row-h, 26px) + 2 * 6px));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;          /* keeps the bar off the buttons */
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.3) transparent;
}
/* Editing one picture: the drawer's zoom controls are taller than the window,
   so the cap lifts until it closes (app-brand.js toggles .ov-editing). */
.ov-shapes.ov-editing { max-height: none; }
.ov-shapes::-webkit-scrollbar { width: 8px; }
.ov-shapes::-webkit-scrollbar-thumb { background: rgba(255,255,255,.28); border-radius: 4px; }
.ov-shapes::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.45); }
.ov-shape-row { display: flex; align-items: center; gap: 6px; }
.ov-shape-th { width: 34px; height: 26px; border-radius: 6px; background: #0b0f1e center/cover no-repeat;
  border: 1px solid var(--border); flex: 0 0 auto; }
.ov-shape-b { flex: 1; min-width: 0; padding: 5px 0; font-size: 14px; line-height: 1;
  background: var(--panel-2, #16213a); border: 1px solid var(--border); border-radius: 7px;
  color: #fff; cursor: pointer; }
.ov-shape-b:hover { border-color: var(--success); }
.ov-shape-b.on { border-color: var(--success); color: var(--success); box-shadow: 0 0 0 1px var(--success) inset; }

/* ── 🎹 Groovebox faders ───────────────────────────────────────────────────
   Real faders, not rotary knobs: a recessed slot with a bright groove and a
   chunky coloured cap — the SAME language as the DJ mixer's channel faders,
   because it is the same hand doing the same thing one panel away. Half again
   as tall as the first pass (Marcelo), and each one carries its own colour
   through --fader-cap, set from the swatch under it. */
.gb-fader-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; user-select: none; }
.gb-kfader {
  -webkit-appearance: none; appearance: none;
  writing-mode: vertical-lr; direction: rtl;
  width: 12px !important; height: 72px !important;   /* ~50% up from 10 × 46, in the synth's proportions */
  margin: 0; border-radius: 8px; cursor: pointer; touch-action: none;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.22) 45%, rgba(255,255,255,.22) 55%, transparent 60%),
    linear-gradient(90deg, #04030a, #1b1220 44%, #1b1220 56%, #04030a);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.75);
}
.gb-knob-big .gb-kfader { width: 15px !important; height: 96px !important; }
/* The cap: bright indicator line in a groove, lit from above, its own colour. */
.gb-kfader::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: 32px; height: 16px; border-radius: 5px;
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.4) 43%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 57%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #7cc2ff) 45%, #fff), var(--fader-cap, #7cc2ff) 54%, color-mix(in srgb, var(--fader-cap, #7cc2ff) 55%, #000));
  border: 1px solid rgba(0,0,0,.55);
  box-shadow: 0 3px 7px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.75), 0 0 10px color-mix(in srgb, var(--fader-cap, #7cc2ff) 60%, transparent);
  cursor: grab; }
.gb-kfader::-moz-range-thumb { width: 32px; height: 16px; border-radius: 5px; border: 1px solid rgba(0,0,0,.55);
  background:
    linear-gradient(180deg, transparent 40%, rgba(0,0,0,.4) 43%, #fff 47%, #fff 53%, rgba(0,0,0,.4) 57%, transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #7cc2ff) 45%, #fff), var(--fader-cap, #7cc2ff) 54%, color-mix(in srgb, var(--fader-cap, #7cc2ff) 55%, #000));
  box-shadow: 0 3px 7px rgba(0,0,0,.6), 0 0 10px color-mix(in srgb, var(--fader-cap, #7cc2ff) 60%, transparent); }
.gb-knob-big .gb-kfader::-webkit-slider-thumb { width: 40px; height: 19px; }
.gb-knob-big .gb-kfader::-moz-range-thumb { width: 40px; height: 19px; }
.gb-kfader:active::-webkit-slider-thumb { box-shadow: 0 2px 6px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.85), 0 0 16px color-mix(in srgb, var(--fader-cap, #7cc2ff) 85%, transparent); }
.gb-kfader:focus-visible { outline: 2px solid var(--gb-warm, #ffb347); outline-offset: 3px; }
/* 🎨 Each fader's colour chip — the SS-1's swatch, same control and the same
   double-click-to-reset, so the two panels are learned once. */
.gb-capcol { width: 18px; height: 10px; padding: 0; border: 1px solid rgba(0,0,0,.55);
  border-radius: 3px; background: transparent; cursor: pointer; }
.gb-capcol::-webkit-color-swatch-wrapper { padding: 0; }
.gb-capcol::-webkit-color-swatch { border: none; border-radius: 2px; }

/* 🔍 The overlay zoom drawer — how far into the picture, and where inside. */
.ov-zoom-row { display: flex; flex-direction: column; gap: 4px; padding: 4px 0 6px 40px; }
.ov-zoom-line { display: flex; align-items: center; gap: 6px; font-size: 10px; color: var(--muted); min-width: 0; box-sizing: border-box; margin-bottom: 6px; }
.ov-zoom-line input[type=range] { flex: 1; accent-color: var(--success); }
.ov-zoom-line b { min-width: 30px; text-align: right; color: #fff; font-size: 10px; }
/* The overlay pad — the editor's own cluster, wearing the studio's chrome.
   Wraps, because this drawer is narrow and seven buttons on one line would
   either overflow or shrink below a fingertip. */
/* Label and readout on one line, the buttons in a grid beneath. As a flex ROW
   the pad was competing with both for width inside a narrow drawer, lost, and
   collapsed into a single vertical column of seven — legible, useless. */
.ov-pad-line { display: grid; grid-template-columns: auto 1fr; align-items: center;
  gap: 6px 8px; font-size: 10px; color: var(--muted); }
.ov-pad { grid-column: 1 / -1; display: grid; gap: 4px;
  grid-template-columns: repeat(auto-fit, minmax(40px, 1fr)); }
/* Reset spans the row: it is the only button carrying a WORD, and that word is
   "Zurücksetzen" in German and "Redefinir" in Portuguese. Given one cell it
   overflowed its own border. */
.ov-pad-b[data-pad="reset"] { grid-column: 1 / -1; }
/* The two turn rows: label · − · reading · ＋ · ⟲, one line each. */
/* Label on its own line, controls beneath — the same shape as the clocks.
   Sharing one line, the label's column was squeezed to a single character
   wide and "Turn picture" came out spelled vertically, one letter per row,
   with a column of dead space beside it. A caption never has to fight the
   controls it names for width. */
.ov-turn-line { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 3px 6px; align-items: center; }
.ov-turn-line > span { grid-column: 1 / -1; min-width: 0; }
.ov-turn-b { min-width: 26px; height: 26px; border-radius: 7px; border: 1px solid var(--border);
  background: var(--panel2); color: var(--text); font-size: 13px; line-height: 1; cursor: pointer; }
.ov-turn-b:hover { border-color: var(--success); }
.ov-turn-line b { text-align: center; color: #fff; font-size: 10px; }
.ov-pad-b { min-width: 40px; height: 30px; padding: 0 6px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--panel2); color: var(--text);
  font-size: 12px; line-height: 1; cursor: pointer; display: grid; place-items: center; }
.ov-pad-b:hover { border-color: var(--success); }
.ov-pad-b:active { transform: translateY(1px); }
.ov-pad-line > b { text-align: right; color: #fff; font-size: 10px; white-space: nowrap; }
/* The three clocks. A number field, not a slider: these are typed values a
   Host knows ("two seconds"), and a slider makes you hunt for a number you
   already have. */
/* Label on its own line, field beneath. "Wait before showing" beside a number
   box in a 230px drawer wrapped to three lines and pushed the "s" off the
   edge; every language has at least one of these phrases that is long. */
.ov-clock-line { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 1px 6px; align-items: center; }
.ov-clock-line span { grid-column: 1 / -1; }
.ov-clock-in { width: 100%; background: var(--panel2); color: var(--text); font: inherit;
  font-size: 11px; border: 1px solid var(--border); border-radius: 7px; padding: 4px 6px; }
.ov-clock-line b { color: var(--muted); font-weight: 400; }
.ov-zoom-b.on { border-color: var(--success); color: var(--success); }
/* The overlay whose drawer is open — so it is obvious which picture the
   sliders below belong to. */
.ov-zoom-b.open { background: var(--success); color: #06210f; border-color: var(--success); }
/* ⟲ back to square-on, on the rotation line itself. */
.ov-rot-zero { flex: none; width: 22px; padding: 2px 0; font-size: 11px; line-height: 1; border-radius: 6px; }
/* Which gesture the box is in. Wraps, because at 375px it is two lines. */
.ov-mode-hint { font-size: 10px; line-height: 1.45; margin-top: 2px; }
/* 🖐 The drawer is open, so a drag inside the box reframes the picture rather
   than moving the box — the cursor says which before the hand commits. */
.overlay-box.ov-panning { cursor: move; }
/* While its Edit drawer is open, an overlay is in a DIFFERENT mode: dragging
   moves the picture inside the frame, not the frame. Nobody guesses that, and
   the box looks identical either way — so it says so, on itself, for as long as
   it is true. Fixed amber, never var(--accent): the Host repaints with that. */
.overlay-box.ov-panning::after {
  content: attr(data-mode);
  position: absolute; left: 50%; top: 6px; transform: translateX(-50%);
  background: rgba(0,0,0,.72); color: #ffb020; border: 1px solid #ffb020;
  border-radius: 999px; padding: 2px 10px; font-size: 11px; font-weight: 700;
  white-space: nowrap; pointer-events: none; z-index: 8;
}
.overlay-box.ov-panning { outline: 1px dashed rgba(255,176,32,.7); outline-offset: -1px; }

/* ── 📜 Teleprompter (docs/specs/teleprompter.md) ─────────────────────────
   The Host's own glass. It is a SIBLING of #stage, never a child: a
   non-descendant is what Element Capture excludes from a local recording, and
   it is what can be moved clear of the stage rectangle on browsers that only
   have the older rectangular crop. position:fixed in viewport pixels so no
   ancestor's overflow can clip a pane that has dodged outside the stage.
   z 45 sits above everything drawn inside the stage and below every modal. */
#prompter-layer { position: fixed; z-index: 45; pointer-events: none; }
/* With the DJ mixer open the stage becomes a small booth monitor at z-index
   500. The prompter has to outrank it — reading while you DJ is exactly when
   you need it, and being painted over reads as the feature being broken. */
body.dj-monitor #prompter-layer { z-index: 501; }
#prompter-layer[hidden] { display: none; }

.tp-pane {
  position: absolute; overflow: hidden; pointer-events: auto;
  border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,.5);
  /* A hairline so an almost-transparent prompter is still findable, and so the
     Host can see what they are about to drag. */
  outline: 1px solid rgba(255,255,255,.14); outline-offset: -1px;
  will-change: transform;
}
.tp-pane[hidden] { display: none; }
/* Sized to the canonical column and centred, so the scale term below is a pure
   fit factor and Mirror is exactly its negative-X twin. */
.tp-scaler { position: absolute; top: 0; left: 50%; width: 720px; margin-left: -360px;
  transform-origin: center top; }
.tp-column { will-change: transform; }
.tp-text { padding: 10px 22px; white-space: pre-wrap; overflow-wrap: break-word;
  /* The Host's words, laid out physically — an RTL studio interface must not
     move where they asked them to sit. */
  unicode-bidi: plaintext; }

/* Grips: invisible until the pointer is over the pane, so the glass reads as
   text and not as a widget while the Host is on air. */
.tp-grip { position: absolute; inset: 0; cursor: move; }
.tp-size { position: absolute; right: 0; bottom: 0; width: 22px; height: 22px;
  cursor: nwse-resize; opacity: 0; transition: opacity .15s;
  background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,.65) 50%); }
.tp-pane:hover .tp-size { opacity: 1; }

/* Panel */
.tp-script { width: 100%; box-sizing: border-box; resize: vertical; min-height: 110px;
  padding: 9px 11px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--input-bg, rgba(255,255,255,.05)); color: inherit;
  font: inherit; line-height: 1.45; }
.tp-count { margin: 4px 0 10px; opacity: .75; }
/* Section headings need air, or "Your script" reads as the last line of the
   paragraph above it rather than the label of the box below. */
.panel-section[data-tab="prompter"] .file-label { display: block; margin-top: 16px; }
.panel-section[data-tab="prompter"] .hint { margin: 6px 0 0; }
.tp-row { display: flex; align-items: stretch; gap: 8px; margin-bottom: 8px; }
.tp-row input[type=range], .tp-row .tp-lab, .tp-row .tp-val, .tp-row .tp-ico { align-self: center; }
.tp-row input[type=range] { flex: 1; min-width: 0; }
.tp-btn { padding: 7px 10px; border-radius: 9px; border: 1px solid var(--line);
  background: rgba(255,255,255,.05); color: inherit; cursor: pointer;
  font-size: 13px; line-height: 1.2; }
.tp-btn:hover { background: rgba(255,255,255,.11); }
.tp-btn[disabled] { opacity: .45; cursor: default; }
.tp-wide { width: 100%; margin-bottom: 8px; text-align: left; }
.tp-grow { flex: 1; min-width: 0; }
.tp-bold { font-weight: 800; } .tp-italic { font-style: italic; }
.tp-lab { min-width: 58px; opacity: .8; font-size: 12px; }
.tp-val { min-width: 52px; text-align: right; opacity: .8; font-size: 12px;
  font-variant-numeric: tabular-nums; }
.tp-ico { opacity: .7; }
.tp-seek { flex: 2; }
/* ON states are a FIXED colour, never var(--accent): hosts repaint the studio
   with the accent and a state marked with it disappears into the paint. */
.tp-btn.on { border-color: #ff4d6d; background: rgba(255,77,109,.22); color: #fff; }

/* ── 🎬 Studio selector (docs/specs/studio-workspaces.md §6) ──────────────
   Sits in the header's right-hand group, where the status already lives —
   schedule, plan, shape, quality. It shows the studio's NAME and never the
   word "Studio", because #trial-badge right beside it renders the PLAN and on
   a Studio-tier account already reads "★ Studio". */
.studio-sel-wrap { position: relative; display: inline-flex; }
.studio-sel { display: inline-flex; align-items: center; gap: 6px; max-width: 190px; }
.studio-sel-icon { opacity: .9; }
#studio-sel-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.studio-sel-caret { opacity: .6; font-size: 11px; }
.studio-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 300;
  min-width: 300px; max-width: 380px;
  padding: 6px; border-radius: 12px; border: 1px solid var(--line);
  background: var(--panel, #131722);
  box-shadow: 0 18px 48px rgba(0,0,0,.55);
}
.studio-menu.hidden { display: none; }
/* The studio slider. A row is 31px, so 170px shows five and half of the
   sixth — the peek is what says "there is more", the same way the home
   rails leave the next card showing. The footer lives outside this box, so
   ＋ New studio is reachable at 6 studios and at 20. */
.st-list { max-height: 170px; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent; }
.st-list::-webkit-scrollbar { width: 8px; }
.st-list::-webkit-scrollbar-track { background: transparent; }
.st-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); border-radius: 99px;
  border: 2px solid transparent; background-clip: content-box; }
.st-list::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); background-clip: content-box; }
.st-row { display: flex; align-items: center; gap: 4px; border-radius: 9px; }
.st-row:hover { background: rgba(255,255,255,.06); }
/* The active row is marked with a FIXED colour, never var(--accent): hosts
   repaint the studio with the accent and the mark would vanish into it. */
.st-row.on { background: rgba(255,77,109,.16); box-shadow: inset 0 0 0 1px rgba(255,77,109,.45); }
.st-open { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px;
  padding: 8px 10px; background: none; border: 0; color: inherit; cursor: pointer; text-align: left; font: inherit; }
.st-name { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-room { flex: 1; min-width: 0; opacity: .5; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-tick { color: #ff4d6d; font-weight: 700; }
.st-acts { display: flex; gap: 2px; padding-right: 6px; opacity: 0; transition: opacity .12s; }
.st-row:hover .st-acts, .st-row.on .st-acts { opacity: 1; }
.st-act { width: 26px; height: 26px; border-radius: 7px; border: 0; background: rgba(255,255,255,.07);
  color: inherit; cursor: pointer; font-size: 12px; line-height: 1; }
.st-act:hover { background: rgba(255,255,255,.18); }
.st-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 6px; padding: 8px 6px 4px; border-top: 1px solid var(--line); }
.st-add, .st-upgrade { padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line);
  background: rgba(255,255,255,.05); color: inherit; cursor: pointer; font-size: 12px; }
.st-add:hover, .st-upgrade:hover { background: rgba(255,255,255,.12); }
.st-upgrade { border-color: #ff4d6d; background: rgba(255,77,109,.2); }
.st-cap { margin-left: auto; opacity: .55; font-size: 11px; font-variant-numeric: tabular-nums; }

/* 🎨 The Host's own effect: the tile shows the picture it rains, because the
   picture IS the effect and a name alone would not say which one. */
.vfx-chip { width: 18px; height: 18px; object-fit: contain; vertical-align: -4px; margin-right: 6px; }

/* The one ⋯ row that takes something away, once it has been armed. */
.ml-pop-item.ml-pop-danger { color: #ff8a8a; }
.ml-restore { margin-top: 8px; }

/* 🔃 The rotate grip — the editor's handle, re-homed onto the stage. A child of
   the frame it turns, so it swings around with it exactly as the editor's does.
   Revealed on hover like the flip badge and the apex; always visible on touch,
   where there is no hover to reveal it with. */
.rot-grip {
  position: absolute; left: 50%; top: -26px; width: 14px; height: 14px;
  margin-left: -7px; border-radius: 50%;
  background: #ffb020; border: 2px solid #1a1a1a; box-sizing: border-box;
  cursor: grab; z-index: 9; opacity: 0; transition: opacity .12s;
  touch-action: none;
}
/* The stem, so the grip reads as attached to the shape rather than floating. */
.rot-grip::before {
  content: ''; position: absolute; left: 50%; top: 100%;
  width: 2px; height: 14px; margin-left: -1px; background: #ffb020;
}
.rot-grip:active { cursor: grabbing; }
.tile:hover > .rot-grip, .overlay-box:hover > .rot-grip, .rot-grip:focus-visible { opacity: 1; }
@media (hover: none) { .rot-grip { opacity: 1; } }

/* While recording, the round ⏺ becomes the clock — so the answer to "how long
   have I been going?" is on the control itself, not in a tooltip. */
.controls #studio-rec-btn.round.counting {
  font-size: 11px; font-weight: 800; font-variant-numeric: tabular-nums;
  letter-spacing: .02em; color: #fff;
  background: var(--danger); border-color: var(--danger);
}

/* On stage, with its own clock counting it back off. A different state from
   ml-pending (which means "not here yet"), so it gets its own colour — a fixed
   pink, never var(--accent): the Host repaints the studio with that. */
.ml-tile.ml-hiding .ml-chip { background: #ff4d6d; color: #fff; }

/* Which stacked background the stage is actually painting. The others stay
   marked active — they are on, just underneath. Fixed colour, never
   var(--accent): the Host repaints the studio with that. */
#bg-row .bg-thumb.bg-top { outline: 2px solid #ffb020; outline-offset: 2px; }

/* ⛔ The background's stage chrome is gone (Marcelo, 2026-08-12): no ✥ grip,
   no corner handles, no dashed hover outline. The backdrop is the whole 16:9
   frame and is chosen, sequenced and faded from the Media panel only — it can
   no longer be nudged out of place by a stray drag on the stage. Belt: any
   stray handle that ever reaches this layer stays invisible and untouchable. */
#bg-layers .bg-grip, #bg-layers .bg-layer .resize-handle { display: none !important; }
#bg-layers, #bg-layers .bg-layer { pointer-events: none !important; }

/* Choose which backgrounds a timing applies to — the real thumbnails, because
   a Host knows his pictures by sight, not by filename. */
.ml-pop .ml-pop-hint { display: block; padding: 6px 9px 2px; font-size: 11px; color: var(--muted); }
.ml-pop .bn-pick {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  padding: 0 9px 6px; max-height: 148px; overflow-y: auto;
}
.ml-pop .bn-pick-th {
  height: 30px; border-radius: 5px; border: 1px solid var(--border);
  background-size: cover; background-position: center; cursor: pointer; padding: 0;
}
.ml-pop .bn-pick-th.on { outline: 2px solid #ffb020; outline-offset: 1px; border-color: #ffb020; }


/* 🎚 Custom adjustments in the ✨ effects modal. Same − ＋ pad recipe as the
   overlay drawer (.ov-pad) and the name-tag panel (.nt-step), in this modal's
   chrome. State is never marked with --accent alone: a Host who repaints the
   studio with their brand colour must still see which row they are on. */
.fxg { margin-top: 14px; border-top: 1px solid rgba(255,255,255,.10); padding-top: 12px; }
.fxg-h { margin: 0 0 4px; font-size: 14px; font-weight: 700; }
.fxg-hint { margin: 0 0 10px; }
.fxg-row { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 8px; margin-bottom: 7px; }
.fxg-l { font-size: 13px; opacity: .85; }
.fxg-val { font-size: 13px; font-variant-numeric: tabular-nums; min-width: 46px; text-align: right; }
.fxg-pad { display: flex; gap: 6px; }
.fxg-b { min-width: 38px; padding: 6px 9px; font-size: 14px; line-height: 1;
  border-radius: 9px; border: 1px solid rgba(255,255,255,.18);
  background: rgba(255,255,255,.06); color: inherit; cursor: pointer; }
.fxg-b:hover { background: rgba(255,255,255,.13); border-color: rgba(255,255,255,.30); }
.fxg-all { width: 100%; margin-top: 8px; }
@media (max-width: 520px) {
  .fxg-row { grid-template-columns: 1fr auto; grid-template-areas: 'l v' 'p p'; }
  .fxg-l { grid-area: l; } .fxg-val { grid-area: v; } .fxg-pad { grid-area: p; }
  .fxg-b { flex: 1; }
}

/* The self-view inside Custom adjustments. 16:9, capped so the dials stay on
   screen with it — the point is seeing the face and the numbers together. */
.fxg-mon-wrap { margin: 0 0 12px; }
.fxg-mon { width: 100%; max-height: 210px; aspect-ratio: 16 / 9; object-fit: cover;
  display: block; border-radius: 10px; background: #000;
  border: 1px solid rgba(255,255,255,.14); }
.fxg-mon-off { margin: 0; }
/* STICKY, so Save is reachable in a short window. Measured at a 649x501
   viewport — his — the modal stands 922px tall and the button landed 381px
   below the fold: present, correct, and impossible to press. The scroll
   container is .modal-backdrop, so sticking to its bottom keeps the pair on
   screen however tall the sheet gets. */
.fxg-bar {
  position: sticky; bottom: 0; z-index: 2;
  margin-top: 10px; padding: 10px 0 2px;
  background: var(--panel);
  box-shadow: 0 -10px 14px -8px var(--panel);
}
/* And give the monitor less of a short window to eat. It is the biggest thing
   in the sheet, and on a laptop it was pushing the dials themselves under. */
.fxg-mon { max-height: min(210px, 30vh); }

/* The prompter's − ＋ pads. Its panel is narrow like the chat column, so the
   controls are sized to match rather than to the wide Style panel. */
.tp-row .size-zoom.tp-pad { flex: 1; min-width: 0; gap: 5px; padding: 3px; border-radius: 8px; }
.tp-row .size-zoom.tp-pad .zoom-btn { width: 30px; height: 22px; font-size: 14px; border-radius: 5px; }
.tp-row .size-zoom.tp-pad .tp-val { flex: 1 1 auto; text-align: center; }

/* The share sheet's one slider — rotation. Sized to the sheet's narrow rows,
   with the readout beside it like the − ＋ rows have. */
.tmi-tag .tmi-range { flex: 1 1 auto; min-width: 0; margin: 0; }
.tmi-tag .tmi-range + .tmi-val { flex: 0 0 auto; min-width: 38px; text-align: right; }

/* 🖥 "Play on stage" in a Library row. State is marked by a ring and the glyph
   swapping to ⏹, never by --accent alone — a Host who repaints the studio with
   their brand colour must still see which video is up. */
.lib-actions .lib-stage.on { outline: 2px solid rgba(47, 208, 110, .95); outline-offset: -2px; }
.lib-actions .lib-stage:disabled { opacity: .45; cursor: not-allowed; }

/* The further background sets — one button per run of 24, wrapping. Sits under
   "Play all backgrounds", which IS the first set. */
.bg-set-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.bg-set-row .bg-set-btn { flex: 1 1 auto; min-width: 78px; font-variant-numeric: tabular-nums; }

/* 🔀 A Media tile being dragged. Marked by lift and a ring, never by
   --accent alone — the Host repaints the studio with that colour. */
.ml-lift, .bg-thumb.ml-lift {
  transform: scale(1.06);
  box-shadow: 0 12px 26px rgba(0,0,0,.55);
  outline: 2px solid rgba(255,255,255,.85);
  outline-offset: -2px;
  opacity: .92;
  cursor: grabbing;
  z-index: 5;
}
#bg-row .bg-thumb, #overlay-row .bg-thumb, #logo-row .bg-thumb { touch-action: pan-y; }

/* ⏯ ⏮ ⏭ — transport for the recording currently on stage. Present only while
   it is up there, so no control is ever offered that would do nothing. */
.lib-actions .lib-tr { min-width: 30px; }
.lib-actions .lib-tr:hover { background: rgba(255,255,255,.14); }

/* The Library row carries SEVEN buttons once a video is on stage — play, stop,
   ⏮ ⏸ ⏭, rename, download, delete — and at 28px they ran off the panel's right
   edge with the last one cut in half. 30% smaller, and the row is allowed to
   wrap rather than overflow, so nothing is ever unreachable however many
   controls a row grows. */
.lib-actions { flex-wrap: wrap; justify-content: flex-end; gap: 3px; row-gap: 3px; min-width: 0; }
.lib-actions button { width: 20px; height: 20px; border-radius: 5px; font-size: 10px; padding: 0; line-height: 1; }
.lib-actions .lib-tr { min-width: 20px; }

/* 🙂 Guest avatar picker — the same square tile the studio's Edit avatar uses,
   so the two surfaces read as one product. */
.gav-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.gav-tile {
  width: 74px; height: 74px; border-radius: 14px; cursor: pointer;
  background: var(--panel-2) center/cover no-repeat;
  border: 2px solid var(--border); color: var(--text); font-size: 26px;
}
.gav-tile:hover { border-color: var(--accent); }

/* ── Media rows scroll instead of growing forever ────────────────────────
   Marcelo, pointing at the background-music list: "we can scroll until we find
   the music that we want — it saves a lot of space. Could you do this around
   all the Media section?" He is right, and 114 logos made it unarguable: a row
   that grows without limit pushes everything below it off the panel, so finding
   the Backgrounds heading meant scrolling past a hundred tiles.

   Same contract as .media-list (the music list he likes): a fixed window with
   its own scrollbar. Taller than that one because these hold picture tiles
   rather than one-line rows — ~3 rows of thumbnails, enough to browse without
   the row becoming the page.

   `overscroll-behavior: contain` stops a flick inside the row from carrying on
   into the side panel once it hits the end, which is the thing that makes
   nested scrollers feel broken. Drag-to-reorder still works: orderEnable's
   autoScroll already pans a scrolling container when a tile is dragged to its
   edge. */
#bg-row, #overlay-row, #logo-row, #la-mask-row,
#clip-grid, #loop-grid, #sfx-grid, #live-avatar-row, #vfx-grid,
#gla-grid, #ga-anon-avatar,
/* #lib-list left this group for one day (2026-08-12) and came straight back:
   Marcelo wants the COMPACT window ("I don't want to scroll the whole
   library"), just with the last video reachable. The window stays; what
   changed is that the panel's wheel CHAINS INTO it at the bottom (app.js
   libListChain) — one continuous scroll reaches the last card — plus the
   visible thumb and real bottom padding so the final card never sits cut at
   the edge. */
#stx-cards, #banner-list, #lib-list {
  max-height: 300px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;   /* keeps the scrollbar off the tiles */
  padding-bottom: 10px; /* the last card never kisses the window's edge */
  /* The window's scrollbar must be SEEN to be used: macOS hides overlay
     scrollbars until the exact element is scrolled, so the panel's own bar
     hit its end while the list quietly kept a video below the fold —
     Marcelo: "I need to have access to the last video." Always-visible thin
     bar = the affordance that there is more. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.3) transparent;
}
#stx-cards::-webkit-scrollbar, #banner-list::-webkit-scrollbar, #lib-list::-webkit-scrollbar { width: 8px; }
#stx-cards::-webkit-scrollbar-thumb, #banner-list::-webkit-scrollbar-thumb, #lib-list::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.28); border-radius: 4px; }
#stx-cards::-webkit-scrollbar-thumb:hover, #banner-list::-webkit-scrollbar-thumb:hover, #lib-list::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,.45); }
/* "Make the video go a bit more up": at the window's own scroll end the last
   card clears the edge with room to spare — buttons always fully shown. */
#lib-list { padding-bottom: 18px; }

/* The Backgrounds controls, now a sibling of the scrolling tiles rather than
   part of them — so Play all / the Sets / Repeat stay put while the pictures
   scroll behind. .bg-row gave them their wrap and gap; keep it here. */
#bg-controls { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
/* ▶ The Set buttons get their OWN scroll window, two rows tall (Marcelo:
   "create one more slider for this… you have these two to show"). At 139
   backgrounds there are six of them — three stacked rows that pushed Repeat
   the sequence and Clear all / Play all down the panel, and every future
   volume adds another. Two rows is the window; the rest scrolls.

   Only the Sets are inside it. `.bg-clear-timing`, `.bg-playall` and
   `.bg-loop` are SIBLINGS in #bg-controls, not children of this row, so they
   stay put exactly as they are today — which is what he asked for.

   The height is two buttons plus the one gap between them. `--bg-set-btn-h`
   is re-measured from a real painted button by bgSetsPaint(); the 30.5px
   fallback is that measurement taken on 2026-08-14, so the window is right
   even before any script runs and self-corrects if the button ever changes. */
#bg-controls .bg-set-row {
  display: flex; flex-wrap: wrap; gap: 8px; width: 100%;
  max-height: calc(2 * var(--bg-set-btn-h, 30.5px) + 8px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;          /* keeps the bar off the buttons */
  /* Always-visible thin bar, the same call the Library list records: macOS
     hides overlay scrollbars until you scroll the exact element, which turns
     "there are more Sets below" into a secret. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.3) transparent;
}
#bg-controls .bg-set-row::-webkit-scrollbar { width: 8px; }
#bg-controls .bg-set-row::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.28); border-radius: 4px; }
#bg-controls .bg-set-row::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,.45); }

/* The two CARD lists run shorter than the picture grids. A text card is a tall
   row, so 300px showed five and a half of them — Marcelo: "you can make it a
   bit shorter, if I see 4 is enough". The tile grids keep their 300px: he
   looked at those and said not to touch them. */
#stx-cards, #banner-list { max-height: 220px; }

/* 🎭 The guest's Live avatar character list. Two dozen characters on a phone is
   a page and a half of scrolling before you reach "My room / Scene background"
   and the monitor below it — Marcelo, on an iPhone: "you also have the option of
   the Live avatar, could we add the scrollbar there as well". Shorter than the
   studio's rows because a phone screen is short: about five rows of characters,
   leaving the background choice and the live monitor visible without scrolling.
   #gla-grid holds only the characters — the seg buttons and the monitor are
   siblings, so nothing useful is trapped inside the window. */
#gla-grid { max-height: 46vh; }

/* 🔠 Banner size, 30% smaller (Marcelo). Scoped to THIS control by id: the same
   .size-zoom shape is used by Text size, Comment size and Chat size, and he
   pointed at the banner one. 42x32 → 29x22, the glyph 20px → 14px, the readout
   14px → 10px — all three scale together or the buttons stop matching the
   number between them. */
/* #text-size is the NAME TAG size control — the id is historical, the label
   above it reads "🏷️ Name tag size". Same 30% cut as the banner's, at
   Marcelo's ask; Comment size and Chat size keep the original scale. */
#banner-size, #text-size { gap: 6px; padding: 3px; }
#banner-size .zoom-btn, #text-size .zoom-btn { width: 29px; height: 22px; font-size: 14px; border-radius: 5px; }
#banner-size .zoom-val, #text-size .zoom-val { font-size: 10px; }

/* ── Background music: ONE deck for everything that drives playback ────────
   Loop and Shuffle sat above the playlist and the transport below it, so the
   same job read as two unrelated groups (Marcelo). They are one block now,
   directly under the list. Stop joins the transport row because stopping IS a
   transport step; it takes the wide slot so its label always fits, and the
   three icons split what's left. Both rows wrap rather than overflow, which is
   what keeps this honest at the 📐 panel slider's narrow end. */
.music-deck { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.music-transport, .music-modes { display: flex; gap: 6px; flex-wrap: wrap; }
.music-transport .ctrl.small { flex: 1 1 34px; min-width: 0; font-size: 15px; }
.music-transport #music-stop { flex: 1.7 1 62px; font-size: 13px; }
.music-modes .ctrl.small { flex: 1 1 92px; min-width: 0; }

/* ── The volume slider, wearing the DJ crossfader ──────────────────────────
   Same idiom as #dj-xfade: a grooved dark track with a bright centre line, a
   chunky cap with a white stripe down it, and a ruler underneath. The cap and
   its glow take --music-fader from the swatch beside the track, so the Host
   picks the colour; everything else is fixed so it still reads as hardware. */
/* Wrapping is not cosmetic here. --st-panel-w counts the 76px rail, so the 📐
   slider's low end leaves a section barely 36px wide; a swatch holding its 24px
   on the same line squeezed the track itself down to 2px — a volume control you
   cannot see, let alone grab. Wrapped, the swatch drops below and the track
   keeps the whole width, degrading the way the deck above it already does. */
.music-fader { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.music-fader #music-vol {
  -webkit-appearance: none; appearance: none; flex: 1 1 96px; min-width: 0; height: 14px; border-radius: 8px;
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10); box-shadow: inset 0 2px 7px rgba(0,0,0,.75); }
.music-fader #music-vol::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 22px; height: 34px; border-radius: 6px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 48%, rgba(255,255,255,.9) 52%, transparent 60%),
    linear-gradient(180deg, var(--music-fader, #3b82f6), color-mix(in srgb, var(--music-fader, #3b82f6) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 16px color-mix(in srgb, var(--music-fader, #3b82f6) 80%, transparent),
              0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45); cursor: grab; }
.music-fader #music-vol::-moz-range-thumb {
  width: 22px; height: 34px; border-radius: 6px; border: 1px solid rgba(0,0,0,.6);
  background: linear-gradient(180deg, var(--music-fader, #3b82f6), color-mix(in srgb, var(--music-fader, #3b82f6) 42%, black));
  box-shadow: 0 0 16px color-mix(in srgb, var(--music-fader, #3b82f6) 80%, transparent); }
/* the ruler — stops short of the cap's travel ends so it reads as a scale */
.music-fader::after {
  content: ''; position: absolute; inset-inline: 12px 40px; top: calc(100% + 1px); height: 8px; pointer-events: none;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.24) 0 1px, transparent 1px 14px); }
.music-fader .fader-tint {
  flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border-radius: 6px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.18); background: none; }
.music-fader .fader-tint::-webkit-color-swatch-wrapper { padding: 2px; }
.music-fader .fader-tint::-webkit-color-swatch { border: none; border-radius: 4px; }
.music-fader-label {
  margin-top: 11px; font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  text-align: center; color: var(--music-fader, #3b82f6); opacity: .75; }

/* ── 🔴 RECORDING: the Host's own controls leave the picture ────────────────
   Marcelo, with screenshots of "⬇ Remove from panel" and the ▢△◯❤ shape
   pickers sitting in a finished recording: "This is for the host only to
   control on the studio. It doesn't appear in YouTube and also it doesn't
   supposed to appear inside the recording as well … the idea is very similar
   to the Prompter."

   Same guarantee as the prompter, necessarily a different mechanism, and the
   difference is the whole reason this block exists. The prompter is a
   deliberate NON-DESCENDANT of #stage (app-prompter.js:145), so Element
   Capture — which records the target's SUBTREE — excludes it by construction
   and it only has to dodge under Region Capture. Every selector below is a
   DESCENDANT of #stage. Element Capture therefore buys nothing here, and
   unlike PrompterUI.recordingMode and stageSizeBarRecMode this hides under
   BOTH capture modes. Guests and YouTube were never affected: the guest page
   and the compositor render their own DOM and have no equivalent nodes.

   `display: none`, not `opacity: 0` — these are ALREADY opacity:0 until hover,
   and an invisible control that still takes the click is worse than a missing
   one. `!important` because `.tile.shape-tri .tri-apex { display: flex }` and
   `.stage[data-layout="tri"] .tile .tri-flip` out-specify a two-class prefix.
   Every selector is scoped to #stage on purpose: buildTileMenu also mounts on
   backstage cards and in the team table, and blinding those would take away
   the very controls renderStage() hands the Host while recording. */
body.rec-hide-chrome #stage .tile-stage-btn,
body.rec-hide-chrome #stage .tile-menu-wrap,
body.rec-hide-chrome #stage .resize-handle,
body.rec-hide-chrome #stage .tile-shapes,
body.rec-hide-chrome #stage .tri-flip,
body.rec-hide-chrome #stage .tri-apex,
body.rec-hide-chrome #stage .rot-grip,
body.rec-hide-chrome #stage .bg-grip,
body.rec-hide-chrome #stage .ov-apex,
body.rec-hide-chrome #stage .ov-flip-badge,
body.rec-hide-chrome #stage .seqb-h,
body.rec-hide-chrome #stage .sg-drag,
body.rec-hide-chrome #stage .sg-h,
body.rec-hide-chrome #stage #djmon-crop,
body.rec-hide-chrome #stage .djmon-drag,
body.rec-hide-chrome #stage .djmon-rs { display: none !important; }
/* Non-descendants of #stage hide only under REGION capture (the crop records a
   rectangle of pixels). Element Capture records the subtree — the phone
   monitor floats outside it and is excluded from the file by construction, so
   the host keeps it (spec: recording-ghost-controls.md §7). */
body.rec-region #both-monitor { display: none !important; }
/* No node to hide — these are painted by the stage itself, and the first two
   are not hover-gated at all, so they were in EVERY frame of a booth-mode
   recording rather than only the hovered ones. */
body.rec-hide-chrome.dj-monitor #stage::after,
body.rec-hide-chrome #stage .overlay-box.ov-panning::after { content: none !important; }
body.rec-hide-chrome #stage .overlay-box.ov-panning,
body.rec-hide-chrome #stage .tile .label:hover,
body.rec-hide-chrome #stage .stage-eq-box:hover,
body.rec-hide-chrome #stage #bg-layers .bg-layer:hover,
body.rec-hide-chrome #stage .tile.drag-over { outline: none !important; }
body.rec-hide-chrome #stage .sg-tile:hover { box-shadow: none !important; }
body.rec-hide-chrome #stage .tile.dragging { opacity: 1 !important; }

/* 📺 Stage + 🌈 🎨 + 🐢 SPEED 🐇 on their OWN full-width line, under the mixer
   dock instead of crowded in beside the master VOL fader (Marcelo: "you have a
   big space to put it there, put it there"). .gb-knobs already wraps, so a
   100% flex basis is the entire move — nothing is repositioned by hand and the
   row reflows with the rest when the groovebox is zoom-fitted. */
#gb-knobs .sg-gb-row { flex: 0 0 100%; display: flex; align-items: center; gap: 8px; margin-top: 4px; }
/* and the master fader sits a little further right, off the knob bank */
#gb-knobs .gb-mvol { margin-left: 14px; }

/* ── Docked mixer: TWO rows of 12, and 20% bigger with it ──────────────────
   This reverses the "spread it up, make it one line" above, deliberately and
   at Marcelo's request: 24 slots across one line made every fader thin, and
   the panel had vertical room going spare. Twelve per row buys each channel
   twice the width, and the fader grows 88px → 106px (+20%) into the space the
   second row was already occupying.

   A 12-column grid rather than flex-wrap, because wrapping cannot promise
   twelve — the divider between the fixed tracks and the custom SFX channels
   counts as an item and would push the twelfth onto the next line. The grid
   gives it a zero-width column of its own instead. */
.gb-mixer.gb-mixer-dock {
  display: grid !important;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: auto;
  justify-items: center; align-items: end;
  row-gap: 10px; column-gap: 6px;
  padding: 8px 10px; min-height: 0;
}
.gb-mixer.gb-mixer-dock .gb-mix-fader { height: 106px; }
.gb-mixer.gb-mixer-dock .gb-mix-lab { font-size: 22px; }
/* the divider stops being a column of its own — it would cost a channel slot */
.gb-mixer.gb-mixer-dock .gb-mix-divider { display: none; }

/* The tools row (step strip · ECHO/FILTER pad · S1-S4 · stage light) was 547px
   inside a 1024px row — 477px of dead space to its right, which is the gap
   Marcelo kept pointing at. It takes the whole row now and spreads across it,
   and the two elastic members (the step strip and the XY pad) take the slack
   so the space becomes control rather than padding. Measured, not guessed. */
#groovebox .gb-tools { flex: 1 1 100%; width: 100%; justify-content: space-between; gap: 10px; }
#groovebox .gb-tools > .gb-steps, #groovebox .gb-tools > .gb-lcd { flex: 1 1 auto; min-width: 0; }

/* The void on the right of the Groovebox. The rows themselves were already
   1024px — it was their CONTENTS that stopped half way and left a tall empty
   rectangle down the right side (Marcelo, three screenshots of it). Spreading
   each row across its full width is what fills it; nothing is resized here, so
   the frozen shape of the panel is untouched. */
#groovebox .gb-knobs { justify-content: space-between; }
#groovebox .gb-performrow, #groovebox .gb-playrow { display: flex; align-items: center; justify-content: space-between; width: 100%; gap: 10px; }
#groovebox .sg-gb-row { justify-content: space-between; }

/* Mixer channels: shoulder to shoulder, and 20% bigger with the room that buys.
   Each channel was 14px of control marooned in a 132px column — a 71px gap to
   its neighbour (Marcelo: "make them closer to each other… by doing so we can
   actually increase the size 20% more"). The columns now hug the channel and
   the grid centres as one block, so the pitch is the 16px gap and nothing else.

   The sizes need `!important` to land: the base rule at .gb-mix-fader carries
   `width: 12px !important; height: 78px !important`, which is exactly why an
   earlier attempt without it changed nothing at all. 12→14px and 78→94px is
   +20% on both axes, and the cap follows at 30×15 → 36×18. */
.gb-mixer.gb-mixer-dock {
  grid-template-columns: repeat(12, max-content);
  justify-content: center; column-gap: 16px;
}
.gb-mixer.gb-mixer-dock .gb-mix-fader { width: 14px !important; height: 94px !important; }
.gb-mixer.gb-mixer-dock .gb-mix-fader::-webkit-slider-thumb { width: 36px; height: 18px; }
.gb-mixer.gb-mixer-dock .gb-mix-fader::-moz-range-thumb { width: 36px; height: 18px; }

/* +50% again. With the channels packed the block sat in the middle of a wide
   empty dock (Marcelo: "we have so much space… we can increase the size of it
   in 50%"). Everything scales together off the values above: fader 14→21px
   wide and 94→141px tall, cap 36x18→54x27, label 22→33px, and the gap opens
   16→22px so bigger caps do not touch. Still !important, for the same reason
   as above — the base rule carries it. */
.gb-mixer.gb-mixer-dock { column-gap: 22px; row-gap: 14px; }
.gb-mixer.gb-mixer-dock .gb-mix-fader { width: 21px !important; height: 141px !important; }
.gb-mixer.gb-mixer-dock .gb-mix-fader::-webkit-slider-thumb { width: 54px; height: 27px; }
.gb-mixer.gb-mixer-dock .gb-mix-fader::-moz-range-thumb { width: 54px; height: 27px; }
.gb-mixer.gb-mixer-dock .gb-mix-lab { font-size: 33px; }

/* Gap +20% (Marcelo): 22 -> 26px between channels, now that the caps are big
   enough to want a little more air between them. */
.gb-mixer.gb-mixer-dock { column-gap: 26px; }

/* +10% again between the faders (Marcelo): 26 -> 29px. */
.gb-mixer.gb-mixer-dock { column-gap: 29px; }

/* A gap you can actually SEE. The panel renders at ~61% zoom, so the earlier
   +10% (26->29px) landed as 2px on screen and Marcelo rightly said he saw no
   change. 29 -> 48px is ~+65%, which is ~29px on screen instead of 18px. When
   a step has to survive a 0.61 scale, single-digit CSS deltas are noise. */
.gb-mixer.gb-mixer-dock { column-gap: 48px; }

/* Nudge the whole channel block to the right (Marcelo). The grid still centres
   its 12 columns; widening the start padding narrows the box they centre in,
   which slides the block right by half the padding — ~100px, i.e. ~62px on
   screen after the 0.61 zoom. Padding rather than a transform so it stays in
   flow and cannot silently overhang the dock. */
.gb-mixer.gb-mixer-dock { padding-inline-start: 622px; }

/* ── The 13 knob sliders as 6 over 7, tight enough to sit beside the mixer ──
   A grid, not the flex line-break trick: `margin-inline-end: 100%` on the 6th
   made that item too wide for its own line, so it wrapped alone and the bank
   came out 5 / 1 / 7. Seven tracks with the first knob spanning two gives
   exactly 6 on the top line (2+5 tracks) and 7 underneath — by construction,
   not by hoping the wrap lands right.

   The two full-width members of this row keep their own lines explicitly:
   the docked mixer (which relied on flex: 1 1 100%, meaningless in grid) and
   the 📺 Stage cluster. Gap comes down 12 -> 6px so the two short lines are
   narrow enough to leave the channels their room. */
#gb-knobs {
  display: grid;
  grid-template-columns: repeat(7, max-content);
  justify-content: start; align-items: start;
  column-gap: 6px; row-gap: 8px;
}
#gb-knobs > .gb-knob:first-child { grid-column: span 2; }
#gb-knobs > .sg-gb-row,
#gb-knobs > .gb-mixer { grid-column: 1 / -1; }

/* Narrow the knob columns so the 6/7 bank becomes a compact block instead of
   stretching the full row — 7 fixed 56px tracks plus 6px gaps is ~428px, which
   fits the space left of the mixer channels. The labels are what were making
   the columns wide ("Osc Mod", "Amp Env", "Filt Env"), so they wrap and drop a
   point rather than dictating the width. */
#gb-knobs { grid-template-columns: repeat(7, 56px); }
#gb-knobs > .gb-knob { min-width: 0; }
#gb-knobs > .gb-knob label { font-size: 9px; line-height: 1.05; text-align: center; white-space: normal; }
/* …and it has to out-specify the earlier fill-the-width rule: that one is
   `#groovebox .gb-knobs` (id + class) and beats a bare `#gb-knobs`, so the
   56px tracks were being spread across the whole row by space-between and the
   bank still measured the full 1024px. */
#groovebox #gb-knobs { justify-content: start; }
/* An 8th, elastic track. The knob columns are fixed at 56px so the bank stays
   a compact 262px block — but the docked mixer and the Stage cluster span
   1 / -1, so without something elastic the whole row (and therefore they) would
   shrink to the bank's width. The spare column takes the slack instead. */
#groovebox #gb-knobs { grid-template-columns: repeat(7, 56px) 1fr; }
/* …and pin the 7th knob to column 1 so it STARTS the second line. With an
   elastic 8th track present the knobs would otherwise flow into it and the
   split came out 7 / 6. Knobs 1-6 fill tracks 1-7 (the first spans two), the
   7th is forced onto the next row, and 7-13 make the second line of seven. */
#groovebox #gb-knobs > .gb-knob:nth-child(7) { grid-column: 1; }

/* ── ONE box around the knob bank AND the channels ─────────────────────────
   The frame used to belong to the docked mixer alone, which is why the bank
   sat outside it looking like a separate thing. Moving the border, fill and
   inner shadow up to the row means the compact 6/7 bank and the 24 channels
   are enclosed together — which is what Marcelo cleared the space on the left
   for. The mixer keeps its layout and loses only its chrome. */
#groovebox #gb-knobs {
  background: linear-gradient(180deg, #0c0c14, #050507);
  border: 1px solid rgba(255,255,255,.20); border-radius: 12px;
  box-shadow: inset 0 0 20px rgba(0,0,0,.55), 0 1px 0 rgba(255,255,255,.05);
  padding: 10px 12px;
}
#groovebox .gb-mixer.gb-mixer-dock {
  background: none; border-color: transparent; box-shadow: none;
}
/* Side by side, not stacked. Spanning 1 / -1 put the dock on its own grid row
   BELOW the bank — enclosed in the same box, but not beside it, which is what
   was actually asked for. The dock takes the spare 8th track instead and rides
   the bank's two rows, so the compact 6/7 block sits to its left. Its old
   left-padding nudge goes with it: the grid column is what positions it now. */
#groovebox .gb-mixer.gb-mixer-dock {
  grid-column: 8 / -1; grid-row: 1 / span 2;
  /* channels sit further right inside their track (Marcelo, +10%) */
  padding-inline-start: 205px;
}
/* REGRESSION FIX. .gb-jewels — the ECHO/FILTER pad, S1-S4 and the stage light
   — is a child of this row too, and turning the row into a 56px-track grid
   crushed it into one track: the pad went to 28px wide and Marcelo lost the
   control he uses for the echo. It gets a full row of its own at its natural
   width. Anything else that lands in this row later needs the same, which is
   the cost of gridding a container other code appends to. */
#groovebox #gb-knobs > .gb-jewels {
  grid-column: 1 / -1; justify-self: start; width: auto; min-width: 0;
}

/* Top bar +30% (Marcelo). Scoped to the transport/pattern controls by name —
   NOT to .gb-head itself, because #gb-knobs and .gb-tools are its children too
   and would come along. zoom rather than per-property sizing: these are a dozen
   little components with their own paddings, radii and icon sizes, and scaling
   them as drawn keeps their proportions instead of my re-deriving each one. */
#groovebox .gb-head > .gb-logo,
#groovebox .gb-head > .gb-play,
#groovebox .gb-head > .sy-rec,
#groovebox .gb-head > .sy-kbd,
#groovebox .gb-head > .sy-oct,
#groovebox .gb-head > .gb-banks-wrap,
#groovebox .gb-head > .gb-pages,
#groovebox .gb-head > .gb-sync { zoom: 1.56; }
/* The perform row +30% too (Marcelo): the LCD, REC/MOTION, the C1-C4 faders,
   the FX buttons, SCATTER and the note pads. Same reasoning as the top bar —
   zoom scales a dozen small components as drawn rather than me re-deriving
   every padding and radius. It is its own row, so nothing else is affected. */
/* The full +30%, paid for by letting the row wrap. At 1.12 it fitted one
   line; 1.3 needs 158px more than the row has, so it takes two lines
   instead of clipping the LCD and the note pads off the right edge. */
#groovebox .gb-performrow { zoom: 1.56; flex-wrap: wrap; row-gap: 10px; }

/* Knob bank +20% (Marcelo). The tracks widen with the knobs — zoom alone would
   have overflowed the fixed 56px columns — and the labels come up with them. */
#groovebox #gb-knobs { grid-template-columns: repeat(7, 78px) 1fr; }
#groovebox #gb-knobs > .gb-knob { zoom: 1.46; }
#groovebox #gb-knobs > .gb-knob label { font-size: 9px; }

/* The ECHO/FILTER pad +50% — 150x84 -> 225x126. There is empty space either
   side of it in that row, so it grows into that rather than pushing anything
   (Marcelo). The dot and the labels scale with it so the pad still reads. */
#groovebox .gb-xy { width: 180px; height: 101px; }
#groovebox .gb-xy-dot { width: 17px; height: 17px; }
#groovebox .gb-xy i { font-size: 10px; }

/* The beat clock moves ABOVE the ECHO pad instead of sitting beside it, which
   frees that whole width for the pad to grow into (Marcelo's plan, and the
   reason the space was there). Pad another +50%: 225x126 -> 337x189. */
#groovebox #gb-knobs > .gb-jewels { flex-wrap: wrap; align-items: flex-start; }
#groovebox .gb-jewels > .gb-clock { flex: 0 0 100%; margin-bottom: 8px; }
#groovebox .gb-xy { width: 324px; height: 181px; }
#groovebox .gb-xy-dot { width: 35px; height: 35px; }
#groovebox .gb-xy i { font-size: 21px; }
/* The bigger bank left one element a few px past the edge — the row's own side
   padding is what it needed back. */
#groovebox #gb-knobs { padding-left: 6px; padding-right: 6px; max-width: 100%; }
#groovebox .sg-gb-row { flex-wrap: wrap; }

/* Close the gap between the knob bank and the channels by moving ONLY the bank
   right — the channels stay exactly where they are (Marcelo: "it was perfect
   before… put only the one in the first picture more to the right"). The row's
   left padding moves the bank; the dock's own start padding comes down by the
   same amount so the channels do not budge. Net zero width, so nothing clips. */
#groovebox #gb-knobs { padding-left: 82px; }
#groovebox .gb-mixer.gb-mixer-dock { padding-inline-start: 42px; }

/* Master VOL up to the box's top-left corner, and 30% bigger (Marcelo). Taken
   out of the grid flow rather than given a cell: the knob bank's placement is
   already pinned (first knob spans two tracks, seventh starts row two) and
   inserting a column would renumber all of that. The row's left padding — put
   there when the bank was nudged right — is exactly the space it sits in. */
#groovebox #gb-knobs { position: relative; }
#groovebox #gb-knobs > .gb-mvol {
  position: absolute; left: 18px; top: 4px; zoom: 1.3; margin: 0;
}
/* Tempo stops spanning two tracks. That span was how the top line got 6 knobs,
   but pinning the 7th to column 1 already does that — and the span left Tempo's
   slider centred in a double-width cell, which is the whole reason the gap to
   Swing looked bigger than every other gap (Marcelo, three times). Tracks come
   down a notch with it so all six sit closer. */
#groovebox #gb-knobs > .gb-knob:first-child { grid-column: auto; }
#groovebox #gb-knobs { grid-template-columns: repeat(7, 70px) 1fr; }
/* Knob bank +10% (Marcelo). Tracks grow with the knobs so nothing collides. */
#groovebox #gb-knobs > .gb-knob { zoom: 1.61; }
#groovebox #gb-knobs { grid-template-columns: repeat(7, 77px) 1fr; }
/* VOL out of the top-left corner and into the grid, at the end of the first
   line (Marcelo). The top line carries 6 knobs across 7 tracks — the 7th has
   been empty since the 6/7 split — so this is a real cell rather than another
   absolute nudge, and it moves with the bank from now on. */
#groovebox #gb-knobs > .gb-mvol {
  position: static; grid-column: 7; grid-row: 1;
  justify-self: center; align-self: start; left: auto; top: auto;
}

/* The 16 performance pads over TWO lines and 40% bigger (Marcelo: "this look
   very thin and very small"). Eight per line as a grid, so the split is exact
   rather than left to where the wrap happens to fall, and each pad gets real
   height instead of being a sliver. */
#groovebox .gb-notepads {
  display: grid; grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 6px; flex: 1 1 100%;
}
#groovebox .gb-notepad { min-height: 50px; font-size: 19px; padding: 10px 4px; }
/* VOL nudged right within its cell and 10% bigger (Marcelo). justify-self end
   plus a small offset moves it without leaving the grid cell it now lives in. */
#groovebox #gb-knobs > .gb-mvol { justify-self: end; margin-right: -14px; zoom: 1.57; }
/* 🎨 The master VOL fader takes its colour from the Host's swatch. */
#groovebox .gb-mvol .gb-mvol-slider { accent-color: var(--gb-vol-tint, #4f8cff); }
#groovebox .gb-mvol .gb-mvol-val { color: var(--gb-vol-tint, #ffb347); }
#groovebox .gb-mvol .gb-mvol-tint {
  width: 18px; height: 18px; padding: 0; margin-top: 4px; border-radius: 5px;
  border: 1px solid rgba(255,255,255,.18); background: none; cursor: pointer;
}
#groovebox .gb-mvol .gb-mvol-tint::-webkit-color-swatch-wrapper { padding: 2px; }
#groovebox .gb-mvol .gb-mvol-tint::-webkit-color-swatch { border: none; border-radius: 3px; }

/* BASS / LEAD / NOTE / CHORD onto their own line ABOVE the pads (Marcelo).
   `flex: 0 0 100%` alone was not enough — it gave them a full line but they
   landed BELOW the pads, because .gb-notepads also claims a full line and wins
   the wrap. `order: -1` puts them first in the row's flow, which is what
   actually lifts them into the empty band over the pad grid. */
/* The chips live in .gb-playrow, NOT .gb-performrow — two earlier attempts
   targeted the wrong parent and computed order stayed 0, which is why nothing
   moved. They are already first in DOM order, so a full-width basis plus a
   wrapping row is all it takes to lift them above the pads. */
#groovebox .gb-playrow { flex-wrap: wrap; row-gap: 10px; }
#groovebox .gb-playrow > .gb-padchips {
  flex: 0 0 100%; display: flex; gap: 8px; align-items: center;
}
/* BASS / LEAD / NOTE / CHORD +20% (Marcelo). */
#groovebox .gb-playrow > .gb-padchips { zoom: 1.2; }
/* REC / MOTION / ✕ +20% on top of their row's scale (Marcelo). */
#groovebox .gb-motioncluster { zoom: 1.2; }
/* SCATTER cluster +20% (Marcelo). */
#groovebox .gb-scatcluster { zoom: 1.2; }
/* 🎨 The Groovebox's INNER surfaces — the black panels the controls sit on —
   take the Host's colour. NOT the outer chassis: that already has its own BG
   control in the tools row, and tinting it there was the wrong target
   (Marcelo: "I don't want to change the color of the outside"). */
/* REMOVED: these were painted from --gb-bg-tint, a variable set by a swatch I
   added and then deleted as a duplicate — leaving these surfaces pinned to the
   fallback and the panel's own BG control with nothing to paint. The Groovebox
   already had .gb-swatch-bg for this; its original styling is left to work. */
#groovebox .gb-bg-tint {
  width: 22px; height: 22px; padding: 0; border-radius: 6px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.20); background: none; flex: 0 0 auto;
}
#groovebox .gb-bg-tint::-webkit-color-swatch-wrapper { padding: 2px; }
#groovebox .gb-bg-tint::-webkit-color-swatch { border: none; border-radius: 4px; }

/* Tools row +20% and pulled together (Marcelo). It was set to space-between
   back when the job was filling the width; now the controls are bigger it wants
   the opposite — start-packed with a real gap instead of stretched apart. */
#groovebox .gb-tools { zoom: 1.2; justify-content: flex-start; gap: 18px; }
/* FX cluster (REVERB/DELAY/PHASE + FX PRM/FX DEPTH) +10% and nudged right. */
#groovebox .gb-fxcluster { zoom: 1.1; margin-left: 46px; }
/* the relocated tools row takes a full line of the perform area */
#groovebox .gb-performrow > .gb-tools { flex: 0 0 100%; }
/* The step grid +30% (Marcelo) — the pads are what you actually play, so they
   get the room. It is its own row, so the extra size costs height, not width. */
#groovebox .gb-grid { zoom: 1.3; }
/* 📺 Stage + 🌈 🎨 SPEED — half the gap, 20% bigger (Marcelo). The pair is
   space-between across the full row, so capping the row at half width halves
   the distance between them exactly, rather than guessing a pixel gap. */
#groovebox #gb-knobs > .sg-gb-row { max-width: 34%; zoom: 1.73; }
/* The beat clock strip at half width (Marcelo) — 16 LEDs and a readout never
   needed the whole row; it took it because it was given a full-width line when
   it moved above the ECHO pad. */
#groovebox .gb-jewels > .gb-clock { flex: 0 0 50%; max-width: 50%; }
/* the scene buttons sit inside the clock strip, at its right end */
#groovebox .gb-clock { display: flex; align-items: center; gap: 8px; }
#groovebox .gb-jewels > .gb-scenes {
  flex: 0 0 100%; display: flex; gap: 6px; align-items: center; zoom: 1.44;
  margin-bottom: 6px;
}

/* S1-S4 sit directly on top of the beat-clock strip, left-aligned with it.
   The earlier 120px push and right padding were applied one at a time and
   together left them floating in open space, detached from the strip they
   belong to — both removed. */
#groovebox .gb-jewels > .gb-scenes { justify-content: flex-start; margin: 0 0 6px; }
/* Close the dead band under the jewels row. Measured at 91px between
   .gb-jewels and .gb-grid — the panel's one remaining large empty stretch. */
#groovebox #gb-knobs { padding-bottom: 2px; }
#groovebox .gb-grid { margin-top: 0; }
#groovebox .gb-viz-wrap.gb-viz-below { margin: 4px 0; }
/* The spectrum visualiser is the black band. It is a control, not empty space,
   but it is 78px of dark canvas whenever nothing is playing — so it comes down
   to a slim meter and the band goes with it. */
/* Back to a real height. Squeezing this to 26px was chasing what looked like a
   dead black band, but it is the spectrum visualiser — it only reads as empty
   because it draws dark when nothing is playing. Flattened, it just became a
   black bar above the grid (Marcelo: "fix the equalizer, you squeeze it"). */
#groovebox .gb-viz-wrap.gb-viz-below { height: 78px !important; min-height: 78px; margin: 6px 0; }
#groovebox .gb-viz-wrap.gb-viz-below .gb-viz { height: 78px !important; width: 100%; }

/* the pad grows into the 198px of dead space to its right; ratio held */

/* 🎨 the pad's own colour swatch, in its top-right corner like the DJ mixer's
   BEAT FX pad. pointer-events kept to itself so picking a colour never reads as
   a drag on the pad underneath. */
#groovebox .gb-xy { position: relative; }
#groovebox .gb-xy-tint {
  position: absolute; top: 6px; right: 6px; z-index: 3;
  width: 20px; height: 20px; padding: 0; border-radius: 5px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.25); background: none;
}
#groovebox .gb-xy-tint::-webkit-color-swatch-wrapper { padding: 2px; }
#groovebox .gb-xy-tint::-webkit-color-swatch { border: none; border-radius: 3px; }
/* The pad's dot rests in the MIDDLE, like the DJ mixer's BEAT FX puck, and
   only leaves it while you are actually dragging. The pad already springs its
   values back on release, so this makes the dot agree with that — it does NOT
   change Filter or Echo, only where the puck sits at rest. */
#groovebox .gb-xy:not(.xy-live) .gb-xy-dot { left: 50% !important; top: 50% !important; }
/* The old outside jewel picker is redundant — the swatch inside the pad now
   colours the clock, the dot and the scenes, which was that control's whole
   job. Hidden rather than deleted: it is built in groovebox.js and other code
   may still read it. */
#groovebox .gb-jewels > .gb-jewel-pick { display: none; }
/* The pad's FILL follows the picked colour too. It was a fixed dark panel, so
   choosing white only lightened the grid lines and the dot and the pad stayed
   black (Marcelo). Mixed rather than set flat, so the grid lines and labels
   keep their contrast at every hue — pick white and the pad reads white. */
/* The fill IS the picked colour — no mixing. A 55% mix turned white into grey,
   which is the very complaint this was meant to fix. The grid lines and the two
   labels are drawn from the same colour but pushed to the opposite end, so they
   stay readable whether the pad is white or near-black. */
/* background-COLOR, not the shorthand: `background: … !important` also resets
   background-image, which is what draws the pad's grid — so the grid lines
   vanished and the pad came out flat (Marcelo, comparing it to BEAT FX). */
#groovebox .gb-xy { background-color: var(--gb-xy-fill, var(--gb-jewel, #b06bff)) !important; }
#groovebox .gb-xy i,
#groovebox .gb-xy { --gb-ink: color-mix(in srgb, var(--gb-jewel, #b06bff) 25%, #000); }
#groovebox .gb-xy i { color: var(--gb-ink) !important; }
/* The background picker was a 23px unlabelled-looking square at the end of six
   theme circles — too small and too easy to mistake for a seventh theme
   (Marcelo removed it once thinking it was a duplicate). Bigger, spaced off
   the theme row, and its BG label made legible so it reads as its own control. */
#groovebox .gb-themes .gb-swatch-bg {
  width: 34px !important; height: 34px !important; border-radius: 8px;
  border: 2px solid rgba(255,255,255,.45); margin-left: 2px;
}
#groovebox .gb-themes .gb-bg-lbl {
  font-size: 12px; font-weight: 800; letter-spacing: .06em; opacity: 1;
  margin-left: 14px; color: #cdd4e4;
}
/* the colour group sits on its own line at the top, clear of the transport */
#groovebox .gb-head > .gb-themes {
  flex: 0 0 100%; display: flex; align-items: center; gap: 8px; margin-top: 4px;
}
/* the colour row leads the panel, full width, above the transport */
#groovebox .gb-head > .gb-themes { order: -1; margin: 0 0 6px; }

/* REVERTED: pointing the interior at --gb-bg turned the whole panel mauve
   because that variable also carries the theme tint. Marcelo: "what is this,
   go back to what it was". The interior keeps its own dark surface. */


/* PANEL swatch — the inner dark surface. Separate from BG, which keeps the
   outer chassis. Falls back to the shipped dark, so untouched installs and a
   double-click reset both look exactly as before. */
#groovebox #gb-knobs,
#groovebox .gb-grid,
#groovebox .gb-viz-wrap,
#groovebox .gb-clock { background: var(--gb-inner, #0b0b12); }
#groovebox .gb-themes .gb-swatch-inner {
  width: 34px !important; height: 34px !important; border-radius: 8px;
  border: 2px solid rgba(255,255,255,.45);
}
#groovebox .gb-themes .gb-in-lbl { margin-left: 12px; }

/* ── ECHO pad: BEAT FX's own styling, copied rather than approximated ──────
   Three hand-written attempts did not match, so this lifts the real values from
   .dj-bfxs-pad and friends (style.css ~3109) and applies them to .gb-xy. Same
   34% accent-over-dark fill, same 25% 25% grid, same label sizes, same puck.
   NOTE the fill is a MIX, as BEAT FX's is — so picking white gives BEAT FX's
   pale tint, not pure white. That is what "exactly like the second picture"
   means, and it is the opposite of the earlier "white must be white" ask. */
#groovebox .gb-xy {
  background-color: color-mix(in srgb, var(--gb-xy-fill, var(--gb-jewel, #b06bff)) 34%, #0a0e16) !important;
  background-image:
    linear-gradient(rgba(255,255,255,.14) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.14) 1px, transparent 1px) !important;
  background-size: 25% 25% !important;
  border: 1px solid color-mix(in srgb, var(--gb-xy-fill, var(--gb-jewel, #b06bff)) 45%, rgba(255,255,255,.12));
  overflow: hidden;
}
#groovebox .gb-xy i { position: absolute; font-style: normal; pointer-events: none;
  text-shadow: 0 1px 2px rgba(0,0,0,.6); }
#groovebox .gb-xy .gb-xy-title { top: 6px; left: 8px; font-size: 10px; font-weight: 800;
  letter-spacing: .1em; color: #fff !important; text-shadow: 0 1px 3px rgba(0,0,0,.7); }
#groovebox .gb-xy .gb-xy-e { font-size: 8px; font-weight: 800; letter-spacing: .08em;
  color: rgba(255,255,255,.75) !important; }
#groovebox .gb-xy .gb-xy-w  { left: 6px;  top: 50%; transform: translateY(-50%); }
#groovebox .gb-xy .gb-xy-ee { right: 6px; top: 50%; transform: translateY(-50%); }
#groovebox .gb-xy .gb-xy-n  { top: 22px;   left: 50%; transform: translateX(-50%); }
#groovebox .gb-xy .gb-xy-s  { bottom: 6px; left: 50%; transform: translateX(-50%); }
#groovebox .gb-xy .gb-xy-off { left: 50%; top: 50%; transform: translate(-50%, 14px);
  font-size: 7px; font-weight: 800; letter-spacing: .14em;
  color: rgba(255,255,255,.4) !important; }
#groovebox .gb-xy .gb-xy-dot {
  width: 17px !important; height: 17px !important; border-radius: 50%;
  background: radial-gradient(circle at 50% 38%, #ffffff, var(--gb-xy-fill, var(--gb-jewel, #b06bff))) !important;
  box-shadow: 0 0 16px 5px color-mix(in srgb, var(--gb-xy-fill, var(--gb-jewel, #b06bff)) 85%, transparent) !important;
}
#groovebox .gb-xy .gb-xy-tint { top: 5px; right: 6px; width: 24px; height: 17px;
  border: 1px solid rgba(255,255,255,.4); border-radius: 4px; z-index: 4; }
/* Stage cluster and scenes sit in the header run, beside the transport */
#groovebox .gb-head > .sg-gb-row { flex: 0 0 auto; max-width: none; margin: 0 0 0 14px; }
#groovebox .gb-head > .gb-scenes { flex: 0 0 auto; margin: 0 0 0 14px; }

/* REMOVED: this flat-white puck and dark-label patch was overriding BEAT FX's
   own radial puck and white labels, which is why the pad still did not match. */
/* Align the channel faders. The dock aligned BOTTOMS, so the six labelled
   channels — which carry an emoji and a colour cap the empty ones lack — sat
   with their faders starting lower than the rest and the row read ragged
   (Marcelo: "could you align them"). Tops line up now, and every column is the
   same height so both rows agree. */
.gb-mixer.gb-mixer-dock { align-items: stretch !important; }
.gb-mixer.gb-mixer-dock .gb-mix-col {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
}
.gb-mixer.gb-mixer-dock .gb-mix-fader { margin-top: 0; }
/* Stage cluster and S1-S4 +20% in their header run (Marcelo). */
#groovebox .gb-head > .sg-gb-row { zoom: 1.2; }
#groovebox .gb-head > .gb-scenes { zoom: 1.2; }
/* the ECHO pad in the perform area keeps its own block rather than stretching */
#groovebox .gb-performrow > .gb-xy { flex: 0 0 auto; }
/* Centre the channel block. It was nudged right in stages with a start padding
   back when the bank sat to its left; now that the bank has its own lane the
   padding just pushed it off-centre (Marcelo: "align them, put it more in the
   center"). Padding cleared, grid centred in the dock. */
.gb-mixer.gb-mixer-dock {
  padding-inline-start: 10px !important;
  justify-content: center !important;
}
/* …and sit them a little lower in the dock, so the two rows are not crowded
   against the knob bank's top edge (Marcelo: "move them down a bit"). */
.gb-mixer.gb-mixer-dock { padding-top: 26px !important; }
/* The C1-C4 / VALUE cluster moves left, off the right edge of the perform row
   (Marcelo). Negative end margin rather than a transform, so it stays in flow
   and the row's wrapping still accounts for it. */
#groovebox .gb-performrow > .gb-ccluster { margin-inline-end: 26%; }
/* REC / MOTION / ✕ onto their own line ABOVE the LCD (Marcelo). order:-1 puts
   them first in the perform row's flow and a full-width basis keeps the LCD
   from riding up beside them. */
#groovebox .gb-performrow > .gb-motioncluster {
  order: -1; flex: 0 0 100%; display: flex; align-items: center; gap: 8px; margin-bottom: -8px;
}
/* The FX cluster (REVERB/DELAY/PHASE + FX PRM + FX DEPTH) moves to the right
   corner, beside the VALUE fader, instead of sitting out on the left (Marcelo).
   margin-left:auto pushes it to the end of the row; the C-cluster gives back
   the end margin that was holding that corner open. */
/* Not flush to the right edge: that left a wide gap between VALUE and REVERB.
   A fixed left margin instead, so the FX cluster sits closer to the C-cluster
   while still keeping the corner (Marcelo). */
#groovebox .gb-performrow > .gb-fxcluster { margin-left: 90px !important; margin-right: 0 !important; }
#groovebox .gb-performrow > .gb-ccluster { margin-inline-end: 12px; }
/* The LCD sits right under REC/MOTION. It was 44px lower because it centres
   itself against the taller C-cluster beside it, not because of any gap I could
   shrink — which is why trimming margins kept doing nothing (Marcelo, twice).
   align-self pulls it to the top of the row instead. */
#groovebox .gb-performrow > .gb-screenwrap { align-self: flex-start; margin-top: 0; }
/* SCATTER moves left in its row (Marcelo). */
#groovebox .gb-performrow > .gb-scatcluster { margin-right: auto; margin-left: 0; }
/* SCATTER moves up under the LCD, closing the tall run below it (Marcelo). */
#groovebox .gb-performrow > .gb-scatcluster { align-self: flex-start; margin-top: -78px; }
/* VALUE's colour was arriving muddy (Marcelo: "doesn't change very nicely").
   Cause: .gb-vunbound dimmed the WHOLE cell to 45% until a knob bound it, so
   the cap — and the swatch you set it from — were painted through that veil.
   Dim the label and readout only; the cap keeps the colour you picked. */
#groovebox .gb-vunbound { opacity: 1; }
#groovebox .gb-vunbound > label, #groovebox .gb-vunbound > .gb-kval { opacity: .45; }
/* FX cluster in another 20% toward VALUE (Marcelo). */
#groovebox .gb-performrow > .gb-fxcluster { margin-left: 26px !important; }
/* Stage cluster +10%, S1-S4 +10% with their gap closed 10% (Marcelo). */
#groovebox .gb-head > .sg-gb-row { zoom: 1.32; }
#groovebox .gb-head > .gb-scenes { zoom: 1.32; gap: 4.5px; }
/* Align the perform row's fader cells on one line (Marcelo: "could you align
   them?"). Three things pulled them apart: the cells are three different
   heights (C1-C4 69px, VALUE 92px because it is `big`, FX 76px), the label
   boxes differ (emoji labels make 15px, plain text 11px), and the FX cluster
   renders at zoom:1.1 so its whole stack is 10% taller. Result: labels,
   readouts and colour swatches each landed on three separate lines.
   Fix: hang every cell from the BOTTOM and pin the label/readout/swatch stack
   to one height, dividing by each cluster's zoom so it measures the same on
   screen either side of the row. */
#groovebox .gb-performrow > .gb-ccluster,
#groovebox .gb-performrow > .gb-fxcluster { align-self: flex-end; align-items: flex-end; }
#groovebox .gb-performrow .gb-knob { --gb-cellz: 1; justify-content: flex-end; gap: calc(4px / var(--gb-cellz)); }
#groovebox .gb-performrow > .gb-fxcluster .gb-knob { --gb-cellz: 1.1; }
#groovebox .gb-performrow .gb-knob > label {
  flex: 0 0 auto; height: calc(15px / var(--gb-cellz)); line-height: calc(15px / var(--gb-cellz)); }
#groovebox .gb-performrow .gb-knob > .gb-kval {
  flex: 0 0 auto; height: calc(11px / var(--gb-cellz)); line-height: calc(11px / var(--gb-cellz)); }
#groovebox .gb-performrow .gb-knob > .gb-capcol {
  flex: 0 0 auto; height: calc(10px / var(--gb-cellz)); }
/* Last 2px: the FX labels land a touch high once the stack below them is pinned
   — subpixel rounding through zoom:1.1 inside the panel's zoom:0.61. Nudged in
   paint only, so the readout and swatch rows keep their exact alignment. */
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { position: relative; top: 1.8px; }
/* The whole Groovebox header strip +20% (Marcelo). Same selector list as the
   1.56 block above, plus the two clusters that carry their own 1.32, so the
   transport, the pattern banks, Stage and S1-S4 all grow together and the row
   keeps reading as one strip. .gb-themes and the knob bank are left alone —
   they are not part of this strip. */
#groovebox .gb-head > .gb-logo,
#groovebox .gb-head > .gb-play,
#groovebox .gb-head > .sy-rec,
#groovebox .gb-head > .sy-kbd,
#groovebox .gb-head > .sy-oct,
#groovebox .gb-head > .gb-banks-wrap,
#groovebox .gb-head > .gb-pages,
#groovebox .gb-head > .gb-sync { zoom: 1.872; }
#groovebox .gb-head > .sg-gb-row { zoom: 1.584; }
#groovebox .gb-head > .gb-scenes { zoom: 1.584; gap: 4.5px; }
/* Put the Stage cluster back in the 🗑 corner (Marcelo). The header +20% pushed
   row 1 past the panel width, so the cluster wrapped onto a line of its own.
   Closing the header gap buys it back — at +20% the row's buttons total 758px
   against 1024px available, so ~5px gaps is what it takes to seat Stage's 215px
   beside the bin. ("If you need the space, just close the gap.") */
#groovebox .gb-head { gap: 5px; }
#groovebox .gb-head > .sg-gb-row { margin-left: 3px; }
/* Fader row up 10% toward the pad grid: 61px -> 55px (Marcelo). */
#groovebox .gb-performrow { margin-top: 2px; }
/* OCT lands between VALUE and the FX cluster. It rides in the C-cluster now, so
   the cluster grew by its width — the FX cluster's own left margin comes off to
   pay for it, keeping FX where Marcelo put it and the row inside the panel. */
#groovebox .gb-ccluster > .gb-oct-inline { margin-left: 2px; }
#groovebox .gb-performrow > .gb-fxcluster { margin-left: 0 !important; }
/* REC alone +10% — LAYER shares .sy-rec, so this is keyed to the button's id. */
#groovebox .gb-head > #gb-rec2 { zoom: 2.059; }
/* The label→readout gap in the perform row is 19px, not the cell's 4px: a
   site-wide `label { margin-bottom: 16px }` leaks in. Closed 10% (Marcelo),
   scoped here rather than touching the global rule. Divided by the cluster's
   zoom so both sides of the row close by the same amount on screen. */
#groovebox .gb-performrow .gb-knob > label { margin-bottom: calc(14px / var(--gb-cellz)); }
/* 🎚 OCT joins the fader line properly (Marcelo: "and then we can align them").
   Its markup runs value → track → label, the opposite of the fader cells around
   it, so reordering puts its track in the fader band, its label on the label
   line and its readout on the number line. The padding stands in for the colour
   swatch the other cells carry below their readout. */
#groovebox .gb-ccluster > .gb-oct-inline { justify-content: flex-end; gap: 4px; padding-bottom: 14px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { order: 1; height: 72px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: 72px !important; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-lbl {
  order: 2; height: 15px; line-height: 15px; margin-bottom: 14px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-val { order: 3; height: 11px; line-height: 11px; }
/* The FX labels carry a 1.8px paint nudge (they align that way), which also
   eats 1.8px out of the gap below them — so the same margin closed that half of
   the row further than the C-cluster's. Given back here so both halves show the
   same label→readout gap. */
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { margin-bottom: calc(15.8px / var(--gb-cellz)); }
/* OCT: 20% taller and a nudge right (Marcelo). It grows upward off the shared
   bottom line, the same way VALUE's `big` fader does, so the label and readout
   lines it was just aligned to stay put. */
#groovebox .gb-ccluster > .gb-oct-inline { margin-left: 6px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { height: 86px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: 86px !important; }
/* …and the readout+swatch pair rises another 10% toward the labels (Marcelo). */
#groovebox .gb-performrow .gb-knob > label { margin-bottom: calc(12.2px / var(--gb-cellz)); }
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { margin-bottom: calc(14px / var(--gb-cellz)); }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-lbl { margin-bottom: 12.2px; }
/* The colour swatch itself moves up under its number. Asked three times; each
   time I closed the label→number gap instead, which moves the number and the
   swatch together and leaves the swatch just as far from its number as before —
   so nothing about the colour changer actually moved. This is the gap directly
   above the swatch: 3.8px -> ~1px, and the label→number gap closes too. */
#groovebox .gb-performrow .gb-knob > .gb-capcol { margin-top: calc(-3px / var(--gb-cellz)); }
#groovebox .gb-performrow .gb-knob > label { margin-bottom: calc(8px / var(--gb-cellz)); }
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { margin-bottom: calc(9.8px / var(--gb-cellz)); }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-lbl { margin-bottom: 8px; }
/* OCT's bottom padding stands in for the swatch the other cells carry. Now the
   swatch is pulled up 3px, that stand-in is 3px too tall — trimmed so OCT stays
   on the shared label and number lines. */
#groovebox .gb-ccluster > .gb-oct-inline { padding-bottom: 11px; }
/* Fourth pass on this: taken all the way rather than another few pixels. The
   swatch sits flush under its number, and the label→number gap goes from the
   19px the global `label` margin gave it down to ~5px. */
#groovebox .gb-performrow .gb-knob > .gb-capcol { margin-top: calc(-4px / var(--gb-cellz)); }
#groovebox .gb-performrow .gb-knob > label { margin-bottom: calc(1.5px / var(--gb-cellz)); }
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { margin-bottom: calc(3.3px / var(--gb-cellz)); }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-lbl { margin-bottom: 1.5px; }
#groovebox .gb-ccluster > .gb-oct-inline { padding-bottom: 10px; }
/* Last of the slack: label→number 5.6px -> 4.8px between the glyphs. The swatch
   itself cannot rise any further — it is already touching the digits above it. */
#groovebox .gb-performrow .gb-knob > label { margin-bottom: calc(0.5px / var(--gb-cellz)); }
#groovebox .gb-performrow > .gb-fxcluster .gb-knob > label { margin-bottom: calc(2.3px / var(--gb-cellz)); }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-lbl { margin-bottom: 0.5px; }
#groovebox .gb-ccluster > .gb-oct-inline { padding-bottom: 9px; }
/* OCT +20% (Marcelo). Zoom on the TRACK only — that scales its cap and tick
   marks with it, and leaves the label and readout on the shared lines, which a
   zoom on the whole cell would have dragged off. It grows upward off the
   bottom edge, so the fader band it shares with VALUE still lines up. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { zoom: 1.2; }
/* The band between the pad grid and the faders closes 15% (52px -> ~44px). The
   margin on the perform row was already at its floor — the space is the
   REC/MOTION line's own height, so it comes off there. Checked: REC/MOTION
   still clears the faders below it. */
#groovebox .gb-performrow > .gb-motioncluster { margin-bottom: -16px; }
/* OCT +20%: 82px -> ~98px. Driven by height, since zoom alone was absorbed by
   the panel's own scale and moved it 2%, not 20%. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { height: 101px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: 101px !important; }
/* REVERT: -16px pulled the REC/MOTION row down onto the fader tops — a real
   overlap, caught before it shipped. -8px is as tight as this gap goes while
   REC/MOTION still clears the faders. */
#groovebox .gb-performrow > .gb-motioncluster { margin-bottom: -8px; }
/* OCT +10% again: ~98px -> ~108px. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { height: 111px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: 111px !important; }
/* OCT's swatch takes the bottom row, so the padding that stood in for a swatch
   it did not have comes off. */
#groovebox .gb-ccluster > .gb-oct-inline { padding-bottom: 0; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-capcol { order: 4; margin-top: calc(-4px / var(--gb-cellz, 1)); }
/* The cap takes that colour — it was a fixed grey, so the new swatch would have
   had nothing to paint. */
#groovebox .gb-ccluster > .gb-oct-inline .gb-mini-slider::-webkit-slider-thumb {
  background: linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #52525c) 45%, #fff),
    var(--fader-cap, #52525c) 55%, color-mix(in srgb, var(--fader-cap, #52525c) 55%, #000)); }
#groovebox .gb-ccluster > .gb-oct-inline .gb-mini-slider::-moz-range-thumb {
  background: linear-gradient(180deg, color-mix(in srgb, var(--fader-cap, #52525c) 45%, #fff),
    var(--fader-cap, #52525c) 55%, color-mix(in srgb, var(--fader-cap, #52525c) 55%, #000)); }
/* The dark line ran ACROSS the cap (Marcelo, third picture). It is the range
   input's own runnable-track, which paints over the thumb in this vertical
   writing mode. Blanked, and the line redrawn on the track element underneath —
   so it now passes behind the cap. */
#groovebox .gb-ccluster > .gb-oct-inline .gb-mini-slider::-webkit-slider-runnable-track {
  background: transparent; box-shadow: none; }
#groovebox .gb-ccluster > .gb-oct-inline .gb-mini-slider::-moz-range-track { background: transparent; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track {
  background-image: linear-gradient(90deg, transparent calc(50% - 2.5px), #050506 calc(50% - 2.5px),
    #26262e 50%, #050506 calc(50% + 2.5px), transparent calc(50% + 2.5px)); }
/* THE GAP, properly this time. Asked for repeatedly and I kept refusing to
   close it because a box-overlap test said REC/MOTION was in the way. It is
   not: that row's buttons end at x=210 and the faders start at x=474 — the
   overlap was with an empty full-width flex box, nothing visible. The fader
   clusters rise into that band instead. 19px -> ~5px above the pad grid;
   past ~-20px they actually reach the grid, so this stops short of that. */
#groovebox .gb-performrow > .gb-ccluster,
#groovebox .gb-performrow > .gb-fxcluster { margin-top: -28px; }
/* THE GAP — the one actually being pointed at. Closing the row's top margin only
   moved the TALLEST fader up; every cell hangs from a shared bottom line, so the
   shorter faders left the space above them untouched: Kick-Hat 54px, FX 47px,
   VALUE 35px, while OCT sat at 5px. That is the band in the picture. The faders
   themselves grow to fill it, bottoms unchanged — so labels, readouts and
   swatches all stay exactly where they are. Divided by the cluster zoom so both
   halves of the row reach the same line. */
#groovebox .gb-performrow .gb-knob > .gb-kfader { height: calc(133px / var(--gb-cellz)) !important; }
/* Stage back in the 🗑 corner, and this time with room to stay there. It kept
   flip-flopping because it only just fitted: making REC 10% bigger was enough to
   push it off again, and at a narrower window it never fitted at all. Space is
   taken where Marcelo said to take it — the gaps, and the 🎹 GROOVEBOX wordmark,
   which is the one decorative thing in a row of controls. Verified in the
   corner at both 993px and 1280px wide, with slack to spare. */
#groovebox .gb-head { gap: 3px; }
#groovebox .gb-head > .gb-logo { zoom: 1.15; }
/* The VOL cap takes its swatch colour. When VOL was made tintable the variable
   was wired to accent-color and the readout only — and accent-color paints
   nothing on a slider with appearance:none, so the cap kept the hard-coded grey
   it has had since the fader was built. That is why only the number changed.
   Same treatment as every other cap in the panel. */
#groovebox .gb-mvol .gb-mvol-slider::-webkit-slider-thumb {
  background: linear-gradient(180deg, color-mix(in srgb, var(--gb-vol-tint, #52525c) 45%, #fff),
    var(--gb-vol-tint, #52525c) 55%, color-mix(in srgb, var(--gb-vol-tint, #52525c) 55%, #000));
  box-shadow: 0 2px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.4),
    0 0 9px color-mix(in srgb, var(--gb-vol-tint, transparent) 55%, transparent); }
#groovebox .gb-mvol .gb-mvol-slider::-moz-range-thumb {
  background: linear-gradient(180deg, color-mix(in srgb, var(--gb-vol-tint, #52525c) 45%, #fff),
    var(--gb-vol-tint, #52525c) 55%, color-mix(in srgb, var(--gb-vol-tint, #52525c) 55%, #000)); }
/* The line still crossed the cap. Blanking the input's own track was not enough:
   the tick marks are absolutely-positioned pseudo-elements of the track, and
   positioned boxes paint above a static child — so they and the track's line sat
   over the thumb. The slider is given its own stacking order so the cap is
   unambiguously on top. Same for the master VOL fader, which had it too. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { position: relative; z-index: 0; }
#groovebox .gb-ccluster > .gb-oct-inline .gb-mini-slider { position: relative; z-index: 2; }
#groovebox .gb-mvol .gb-mvol-track { position: relative; z-index: 0; }
#groovebox .gb-mvol .gb-mvol-slider { position: relative; z-index: 2; }
/* Align the knob bank, VOL and the mixer on one baseline (Marcelo). The three
   sat on three different lines — bank faders ended at 785, mixer at 812, VOL at
   820. The faders grow to the lowest of them, so nothing moves down. */
#groovebox #gb-knobs > .gb-knob > .gb-kfader { height: 114px !important; }
#groovebox .gb-mixer-dock .gb-mix-col > .gb-mix-fader { height: 155px !important; }
/* …and the perform-row faders come back 15% (Marcelo): 133px -> 113px. */
#groovebox .gb-performrow .gb-knob > .gb-kfader { height: calc(113px / var(--gb-cellz)) !important; }
/* OCT shorter 10% (Marcelo): 111px -> 100px. It carries its own height rather
   than the .gb-kfader one, so the row's -15% left it standing taller. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { height: 100px; }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: 100px !important; }
/* OCT level with its neighbours (Marcelo: "align them"). It was still 97 against
   their 91 because its track carries a zoom:1.2 of its own — so its height is
   expressed as the row's own 113px divided by that zoom, and the two now render
   identically instead of by a hand-picked number. */
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track { height: calc(113px / 1.2); }
#groovebox .gb-ccluster > .gb-oct-inline > .gb-mini-track .gb-mini-slider { height: calc(113px / 1.2) !important; }
/* The tools row: gaps closed and the items put on one line (Marcelo). They ran
   24-28px tall and were centre-aligned, so tops sat 7px apart and bottoms 10px.
   One height for all of them lines up both edges. */
#groovebox .gb-performrow > .gb-tools { gap: 2px; align-items: center; }
#groovebox .gb-performrow > .gb-tools > * { height: 28px; box-sizing: border-box; }
#groovebox .gb-performrow > .gb-tools > .gb-scale-wrap,
#groovebox .gb-performrow > .gb-tools > .gb-proj-nav { display: inline-flex; align-items: center; }
/* …and the 🎼 scale control sat 8px high because it carries a 16px bottom margin
   — the same site-wide `label { margin-bottom }` leak that was padding out the
   fader cells. Its box was 43px against the buttons' 27, so centring lifted it. */
#groovebox .gb-performrow > .gb-tools > .gb-scale-wrap { margin-bottom: 0; }
/* SCATTER was running 16px INTO the LCD. Its -78px lift was measured when the
   LCD sat lower; everything since moved under it and nothing re-checked. Backed
   off to sit just below the screen with a 2px gap, which is where it was meant
   to be. */
#groovebox .gb-performrow > .gb-scatcluster { margin-top: -60px; }
/* The pad block moves up into the empty band beside the ECHO pad (Marcelo,
   repeatedly). The ECHO pad occupies the left ~290px of that band and the
   remaining ~600px was doing nothing. The block is indented past the pad and
   its width reduced to match, so the pads narrow instead of running off the
   panel — which is what broke the first attempt at this.
   Values are in the panel's own CSS pixels: it renders at ~0.52 zoom, so the
   measured 274px clearance is 528px here, and the 151px lift is 291px. */
#groovebox .gb-performrow > .gb-playrow {
  flex: 1 1 0; min-width: 0; margin: 0; align-self: flex-start; }
/* …and the pads stack ONE ABOVE THE OTHER: 4 across, so the second group of
   1-2-3-4 sits under the first instead of beside it. */
#groovebox .gb-performrow > .gb-playrow > .gb-notepads {
  grid-template-columns: repeat(4, 1fr) !important; }
/* The BASS/LEAD/NOTE/CHORD chips right-align with the pad grid under them
   (Marcelo: the reason for moving them right was to align them). Done as an
   alignment, not a nudge, so their right edge tracks the pads at any width
   instead of drifting off the next time something resizes. */
#groovebox .gb-tools > .gb-padchips {
  margin-left: auto; padding-left: 0; justify-content: flex-end; height: auto; }
/* The 🎹 GROOVEBOX wordmark is removed (Marcelo). It was the one decorative item
   in a row of controls, and shrinking it was only ever a way to buy space for
   the Stage cluster — dropping it gives that space back properly. */
#groovebox .gb-head > .gb-logo { display: none; }
/* Pads 10% smaller (Marcelo). Applied as a scale on the block: the pad height
   drops 40px -> 36px. Their width holds at 146px because the grid's 1fr columns
   re-fill the row whatever the scale — narrowing that would mean shrinking the
   block itself and leaving a gap on its left. */
#groovebox .gb-performrow > .gb-playrow > .gb-notepads { zoom: 0.81; margin-left: auto; }
/* Align the ECHO pad with the pad grid beside it (Marcelo). The grid sat 18px
   lower because the BASS/LEAD/NOTE/CHORD chips take a row above it. The ECHO pad
   comes down to meet it rather than the block going up — lifting the block put
   the chips into the tools row. Their heights already match within 3px, so the
   tops meeting brings the bottoms with them. */
#groovebox .gb-performrow > .gb-xy { margin-top: 0; }
/* The knob bank gets the same treatment as the perform row (Marcelo: "like we
   did earlier"). Same cause too — the site-wide `label { margin-bottom: 16px }`
   was holding the readout 16.7px under the label, and the swatch sat 3.3px under
   the readout. Label→readout ~4px, swatch flush under its number. */
#groovebox #gb-knobs > .gb-knob > label { margin-bottom: 0.8px; }
#groovebox #gb-knobs > .gb-knob > .gb-capcol { margin-top: -4px; }
/* VOL joins the bank's lines too. Its markup runs value → track → label → swatch,
   so its readout sat at the very top instead of on the number row. Reordered to
   match the knob cells beside it, with the same tightened gaps divided by its
   own zoom:1.57. */
#groovebox #gb-knobs > .gb-mvol > .gb-mvol-track { order: 1; }
#groovebox #gb-knobs > .gb-mvol > .gb-mvol-lbl { order: 2; margin-bottom: calc(0.8px / 1.57); }
#groovebox #gb-knobs > .gb-mvol > .gb-mvol-val { order: 3; }
#groovebox #gb-knobs > .gb-mvol > .gb-mvol-tint { order: 4; margin-top: calc(-4px / 1.57); }
/* …and VOL drops onto the bank's lines. Its label box is taller than the knob
   cells' (its own zoom:1.57 on "🔊 VOL"), so the label takes a paint-only nudge
   to reach the line without dragging the readout and swatch off theirs. */
#groovebox #gb-knobs > .gb-mvol { margin-top: 13px; }
#groovebox #gb-knobs > .gb-mvol > .gb-mvol-lbl { position: relative; top: 2.6px; }
/* Last edge: the ECHO pad was 4px taller than the pad grid, so its bottom sat
   proud of the last pad row. Height trimmed so top and bottom both meet. */
#groovebox .gb-performrow > .gb-xy { height: 178px; }
/* With the chips on the tools row, the pad block no longer carries a row above
   it — so it rises to meet the ECHO pad and the empty band under the tools row
   closes. */
#groovebox .gb-performrow > .gb-playrow { margin-top: 0; }
/* The theme swatches (6 presets + custom + BG + PANEL) move to the top-right
   corner (Marcelo). The row already spans the panel with 669px of it empty —
   the swatches just sat at the left end of it. */
#groovebox .gb-head > .gb-themes { justify-content: flex-end; }
/* The swatch row no longer costs a line (Marcelo: "reduce the line, not increase
   a line"). It is lifted out of the flow into the header's top-right corner, in
   the 135px that sits empty to the right of the Stage cluster. That space is
   what sets the size: the row is 197px at full scale, so it is scaled to fit
   rather than pushing anything else around — every other arrangement I measured
   either moved Stage out of the 🗑 corner or added a line back. */
#groovebox .gb-head { position: relative; }
#groovebox .gb-head > .gb-themes {
  position: absolute; top: 0; right: 0; width: 122px; flex-wrap: wrap;
  justify-content: flex-end; align-content: flex-start; margin: 0; }
/* ── SS-1 SYNTH: sized for fingers, not mice (Marcelo) ──────────────────────
   The whole pass has one measurable goal: the faders were 13px wide on the
   single strip, against a 44pt minimum touch target, so the panel could not be
   played on a phone or an iPad. */
/* The keyboard +15% — 96px -> 110px of key height. */
.streamsynth .sy-kb { height: 110px; }
/* The lifted SEQ transport sits at the end of the TWIN line. SPEED is capped so
   the group clears the 324px that line has spare — it is 182px on its own and
   the whole group needs 378px. */
.streamsynth .sy-prog > .sy-seq-lifted { align-self: center; }
/* SPEED +30% (96px -> 125px) and shifted 38px left (Marcelo). Its label was
   being clipped to "SP..." at 96px. The line was full, so the room comes from
   that row's own gap, 8px -> 3px — 'if you don't have space, close the gap'.
   Past ~50px of shift the group wraps to a second line again. */
/* SPEED +30% again (125px -> 162px) and further left (713 -> 654). The line was
   full, so the room comes from that row's gap, 3px -> 2px, and from the word
   "SEQ" beside the transport buttons — 44px of label whose ▶ ⏺ − ✕ carry their
   own titles. Without both, the group drops to a second line. */
.streamsynth .sy-prog { gap: 2px; }
.streamsynth .sy-prog > .sy-lbl.sy-seq-lifted { display: none; }
/* SPEED +30% again, 162px -> 211px, and nothing had to be given up for it.
   Raising the cap alone just dropped the group to a second line; letting it
   GROW into the row's leftover width instead reaches the full length on one
   line, with the 👯 TWIN label kept. */
.streamsynth .sy-prog > .sy-bendwrap.sy-seq-lifted {
  flex: 1 1 auto; max-width: 211px; margin-right: 0; }
/* …and the 16 step cells take the width the transport gave up — a much bigger
   target per step, which is the point of this pass. */
.streamsynth .sy-seq > .sy-cells { flex: 1 1 auto; width: auto; }
/* …and the slider INSIDE that wrapper has to come with it — capping the wrapper
   alone left the input at its own 129px, hanging 15px past the panel edge. */
.streamsynth .sy-prog > .sy-bendwrap.sy-seq-lifted .sy-bender { width: 100%; max-width: 100%; min-width: 0; }
/* Both fader lines run edge to edge (Marcelo: "align them"). One shared zoom is
   set by the WIDER line, so the shorter one stopped short and the two right
   edges disagreed. Letting the sections grow takes up that slack — and the
   space lands inside the sections, where the faders are, rather than as a gap
   at the end of the line. */
.streamsynth .sy-row > .sy-sect { flex: 1 1 auto; }
.streamsynth .sy-row > .sy-sect .sy-s { flex: 1 1 auto; }
/* …and the word SPEED stops being clipped to "SP…": the label was allowed to
   shrink (flex 0 1 44px) while the slider held its width. Label keeps its
   natural size, slider takes whatever is left. */
.streamsynth .sy-prog > .sy-bendwrap.sy-seq-lifted > .sy-lbl {
  flex: 0 0 auto; min-width: max-content; overflow: visible; }
.streamsynth .sy-prog > .sy-bendwrap.sy-seq-lifted > input[type=range] { flex: 1 1 auto; min-width: 0; }
/* ── The synth header in TWO lines, not three (Marcelo) ─────────────────────
   Arithmetic first: 22 items totalling 2255px plus 210px of gaps = 2465px,
   against 1704px for two lines at this width. 761px had to go, so every wide
   item gives a little and the decorative wordmark goes entirely — the same
   drill as the Groovebox's. Header 136px -> 91px. */
.streamsynth .sy-head { gap: 4px; }
.streamsynth .sy-head > .sy-logo { display: none; }
/* The spectrum window moves to the END of its header row and grows into the
   slack there rather than sitting at 120px on the left with the space unused
   (Marcelo). Basis stays small so it never forces a third line: it takes
   leftover width when there is some and shrinks back when there is not. */
.streamsynth .sy-head > .sy-minispec {
  order: 99; flex: 1 1 120px; width: auto; max-width: none; min-width: 0; }
.streamsynth .sy-head > .sy-midi {
  max-width: 70px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.streamsynth .sy-head > .sy-pre { max-width: 170px; }
/* The skin ovals keep their full width — squeezing them to 100px squashed them
   out of shape, and row 2 had 98px of slack sitting unused (Marcelo: "you
   didn't have to squeeze them, you have a lot of space here"). Restoring the
   70px pushes the MIDI status down to that row, which still leaves two lines. */
.streamsynth .sy-head > .sy-bendwrap { max-width: 110px; }
/* The voice-activity strip is removed (Marcelo). It was 8 lights, one per
   playing voice; my 50px cap had squashed them to 3px marks, which is what he
   was looking at. Frees 50px on that header row. */
.streamsynth .sy-head > .sy-leds { display: none; }
/* The switch shelf tightened: gap 6px -> 3px and the Song Builder status text
   capped so it stops eating a whole line's worth of width. Height 124px -> 94px.
   NOT two lines — see the note below; that needs a decision, not a tweak. */
.streamsynth .sy-shelf { gap: 3px; }
.streamsynth .sy-shelf .sy-sb-status {
  max-width: 60px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* TWO lines, as asked three times. The arithmetic left one lever: the eight
   parameter groups alone are 1802px against 1704px for two lines, so they
   exceed it before Song Builder's 244px is counted at all. The per-control
   words go — "Wave", "Octave", "Mode", "Chord", "Target", "Range" (479px) —
   and Song Builder's wordmark and status with them. The pink SECTION labels
   stay, so each dropdown still says which block it belongs to, and every word
   removed survives as the control's tooltip. Shelf 124px -> ~63px. */
.streamsynth .sy-shelf .sy-s > .sy-lbl { display: none; }
.streamsynth .sy-shelf .sy-build { flex: 0 1 auto; }
.streamsynth .sy-shelf .sy-build > .sy-sb-lbl { display: none; }
.streamsynth .sy-shelf .sy-build > .sy-sb-status { display: none; }
/* ── CDJ deck buttons sized for fingers (Marcelo) ───────────────────────────
   Same touch-target reason as the Groovebox and the synth: these are 28px
   minimum against a 44pt guideline. +20% across the deck's control rows, and
   the BEAT LOOP row a further 10% on top of that. */
#dj-mixer .cdj-mrow button {
  min-height: 34px; font-size: 11.4px; padding: 5px 7px; }
#dj-mixer .cdj-bl-row button {
  min-height: 41px; font-size: 13.8px; padding: 7px 9px; }   /* +10% again — 41px is one step off the 44pt target */
/* BEAT LOOP numbers + LOOP IN/OUT/RELOOP row — another +15% on top of the
   above, there's spare room under the jog wheel for it (Marcelo). */
#dj-mixer .cdj-bl-row button {
  min-height: 47px; font-size: 15.9px; padding: 8px 10px; }
#dj-mixer .cdj-loopctl button {
  min-height: 39px; font-size: 13.1px; padding: 6px 8px; }
/* The CDJ tempo column +10% — the ±6/±10/±16 range buttons, the readout, the
   pitch fader and RESET, so the whole strip grows together rather than the
   buttons drifting off the fader they belong to (Marcelo). */
#dj-mixer .cdj-tr { min-height: 22px; font-size: 8.8px; padding: 2.2px 4.4px; }
#dj-mixer .cdj-tval { font-size: 13.2px; }
#dj-mixer .cdj-tfader { width: 29px; min-height: 132px; }
#dj-mixer .cdj-tthumb { height: 20px; margin-top: -10px; }
#dj-mixer .cdj-treset { min-height: 24px; font-size: 9.4px; padding: 3.3px 6.6px; }
/* Tempo column +15% again — range buttons, readout, RESET and TEMPO label, per
   Marcelo's picture. Column width and the fader grow with it so the buttons
   stay flush with the fader instead of drifting wider than it. */
#dj-mixer .cdj-tempo { flex-basis: 87px; }
#dj-mixer .cdj-tr { min-height: 25px; font-size: 10.1px; padding: 2.5px 5px; }
#dj-mixer .cdj-tval { font-size: 15.2px; }
#dj-mixer .cdj-tfader { width: 33px; min-height: 152px; }
#dj-mixer .cdj-tthumb { height: 23px; margin-top: -11.5px; }
#dj-mixer .cdj-treset { min-height: 28px; font-size: 10.8px; padding: 3.8px 7.6px; }
#dj-mixer .cdj-tlbl { font-size: 9.2px; }
/* ── Deck VOL restyled as a crossfader (Marcelo) ────────────────────────────
   Same language as #dj-xfade one panel away: a grooved dark track with a bright
   centre line, a chunky glowing cap carrying a white index line, and the tick
   ruler underneath. Scaled to the deck row rather than copied at full size, and
   it takes the deck's own --cdj-accent instead of the mixer's warm colour, so
   deck A and deck B stay told apart. */
#dj-mixer .cdj-vol {
  -webkit-appearance: none; appearance: none;
  height: 24px; border-radius: 13px; accent-color: auto;   /* three +10% asks, sized to actually show at 0.615 */
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 2px 7px rgba(0,0,0,.75); }
#dj-mixer .cdj-vol::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 30px; height: 50px; border-radius: 10px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 46%, rgba(255,255,255,.9) 54%, transparent 60%),
    linear-gradient(180deg, var(--cdj-accent, #2dd4ff), color-mix(in srgb, var(--cdj-accent, #2dd4ff) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 14px color-mix(in srgb, var(--cdj-accent, #2dd4ff) 80%, transparent),
    0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45);
  cursor: grab; }
#dj-mixer .cdj-vol::-moz-range-thumb {
  width: 30px; height: 50px; border-radius: 10px;
  background: linear-gradient(180deg, var(--cdj-accent, #2dd4ff), color-mix(in srgb, var(--cdj-accent, #2dd4ff) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 14px color-mix(in srgb, var(--cdj-accent, #2dd4ff) 80%, transparent); }
#dj-mixer .cdj-vol:active::-webkit-slider-thumb { cursor: grabbing;
  box-shadow: 0 0 22px color-mix(in srgb, var(--cdj-accent, #2dd4ff) 95%, transparent), inset 0 1px 0 rgba(255,255,255,.6); }
/* the ruler, like the one under the crossfader */
#dj-mixer .cdj-volrow { position: relative; align-items: center; }
#dj-mixer .cdj-volrow::after {
  content: ''; position: absolute; left: 34px; right: 34px; top: calc(100% - 2px); height: 7px; pointer-events: none;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.24) 0 1px, transparent 1px 13px); }
/* BOOTH gets the crossfader's build too (Marcelo). It was a 2px hairline with
   the browser's default cap; now the same grooved track, glowing slab cap with
   a white index line, and tick ruler as #dj-xfade — in the master section's warm
   colour, which it already used for its accent. */
#dj-mixer #dj-booth {
  -webkit-appearance: none; appearance: none;
  height: 14px; border-radius: 8px;
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 2px 7px rgba(0,0,0,.75); }
#dj-mixer #dj-booth::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 22px; height: 34px; border-radius: 6px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 46%, rgba(255,255,255,.9) 54%, transparent 60%),
    linear-gradient(180deg, var(--dj-warm, #ff8a3d), color-mix(in srgb, var(--dj-warm, #ff8a3d) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 16px color-mix(in srgb, var(--dj-warm, #ff8a3d) 80%, transparent),
    0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45);
  cursor: grab; }
#dj-mixer #dj-booth::-moz-range-thumb {
  width: 22px; height: 34px; border-radius: 6px;
  background: linear-gradient(180deg, var(--dj-warm, #ff8a3d), color-mix(in srgb, var(--dj-warm, #ff8a3d) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 16px color-mix(in srgb, var(--dj-warm, #ff8a3d) 80%, transparent); }
#dj-mixer #dj-booth:active::-webkit-slider-thumb { cursor: grabbing;
  box-shadow: 0 0 24px color-mix(in srgb, var(--dj-warm, #ff8a3d) 95%, transparent), inset 0 1px 0 rgba(255,255,255,.6); }
/* …and the BEAT FX level slider, the third hairline in the mixer. Same build,
   scaled to its narrower row (171px), in the mixer's accent. */
#dj-mixer #dj-bfx-level {
  -webkit-appearance: none; appearance: none;
  height: 12px; border-radius: 7px;
  background:
    linear-gradient(0deg, transparent 42%, rgba(255,255,255,.14) 47%, rgba(255,255,255,.14) 53%, transparent 58%),
    linear-gradient(180deg, #04030a, #1c1228 45%, #1c1228 55%, #04030a);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 2px 6px rgba(0,0,0,.75); }
#dj-mixer #dj-bfx-level::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 28px; border-radius: 5px;
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 46%, rgba(255,255,255,.9) 54%, transparent 60%),
    linear-gradient(180deg, var(--dj-accent, #ff5aa0), color-mix(in srgb, var(--dj-accent, #ff5aa0) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 14px color-mix(in srgb, var(--dj-accent, #ff5aa0) 80%, transparent),
    0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45);
  cursor: grab; }
#dj-mixer #dj-bfx-level::-moz-range-thumb {
  width: 18px; height: 28px; border-radius: 5px;
  background: linear-gradient(180deg, var(--dj-accent, #ff5aa0), color-mix(in srgb, var(--dj-accent, #ff5aa0) 42%, black));
  border: 1px solid rgba(0,0,0,.6);
  box-shadow: 0 0 14px color-mix(in srgb, var(--dj-accent, #ff5aa0) 80%, transparent); }
/* Channel strip +10% — TRIM, the HI/MID/LOW knobs and the channel faders
   (Marcelo). The needles' transform-origin scales with the knobs: it is a fixed
   pixel distance, so growing the knob alone would swing the pointer about the
   wrong centre and the knob would read the wrong value. */
#dj-mixer .dj-trim { width: 36.3px; height: 36.3px; }
#dj-mixer .dj-trim > .dj-trim-i { height: 13.3px; transform-origin: 50% 15.15px; }   /* knob 36.3 / 2 - needle top 3 */
#dj-mixer .dj-eqk { width: 34.1px; height: 34.1px; }
#dj-mixer .dj-eqk > .dj-trim-i { height: 12.1px; transform-origin: 50% 14.05px; }   /* knob 34.1 / 2 - needle top 3 */
#dj-mixer .cfader { width: 24.7px !important; height: 189px !important; }   /* 5% thinner track */
/* The cap keeps the extra HEIGHT (that is the grab dimension along a vertical
   fader's travel) but gives the width back: at 79px the caps were 49px rendered
   against a 53px channel pitch, so neighbours sat 4px apart and read as one
   another's edge. 70px leaves a real gap between channels. */
/* A bit more gap. 2% of the cap is 1.4px, which at the mixer's 0.615 scale is
   under one screen pixel — invisible. 70px -> 66px is the smallest step that
   actually shows: the gap between neighbouring caps goes 10px -> 13px. */
#dj-mixer .cfader::-webkit-slider-thumb { width: 61px; height: 38.5px; }   /* 3% again — gap 14px -> 15px */
#dj-mixer .cfader::-moz-range-thumb { width: 61px; height: 38.5px; }
/* …and a little more air above the caps, under the LOW knobs. */
#dj-mixer .dj-fadwrap { margin-top: 8px; }
/* COLOR knobs and the channel CUE buttons +10% (Marcelo). The needle's
   transform-origin scales with the knob for the same reason as TRIM/EQ — it is a
   fixed pixel distance, and leaving it behind would rotate the pointer about the
   wrong centre. The six-decks variant has its own smaller needle, so it is
   scaled separately rather than inheriting a value meant for the big knob. */
#dj-mixer .dj-colk { width: 37.4px; height: 37.4px; }
#dj-mixer .dj-colk .dj-trim-i { height: 14.3px; transform-origin: 50% 15.4px; }
#dj-mixer.six-decks .dj-colk .dj-trim-i { height: 11px; transform-origin: 50% 12.1px; }
#dj-mixer .dj-cue-btn { padding: 11px 0; font-size: 1.1em; }
/* The channel LED meter runs down to the fader's bottom again. Lengthening the
   fader earlier in this pass (172px -> 189px) left the meter 32px short of it,
   so the two no longer ended on the same line. 408px -> 460px. */
#dj-mixer .dj-chan-meter { height: 460px; }
/* Loop row (IN/OUT/RELOOP/SLIP/Q/REV) and the pad-mode tabs +10% (Marcelo). */
/* REVERTED: .dj-loop-row is the loop-LENGTH row (◄ / value / ►), not the row
   Marcelo was pointing at. I grew it three times by mistake; it goes back to
   its original padding. */
/* The row actually asked for: IN / OUT / RELOOP / SLIP / Q / ◄◄ REV. Three
   requests of +10% compounded, applied to the right selector this time —
   15px -> 20px tall, 9px -> 12px text. */
/* min-height 20px did nothing: the mixer renders at 0.615, so 20 CSS px is 12 on
   screen — below the button's natural 15. Written in the scale that reaches the
   eye instead: 33 CSS px = 20 rendered. */
#dj-mixer .dj-mloop-row button { padding: 8px 4px; font-size: 13px; min-height: 33px; }
#dj-mixer .dj-pm { min-height: 24.2px; font-size: 9.9px; padding: 3.3px 2.2px; }
/* BEAT FX: the effect buttons, the LOW/MID/HI band picks and the beat nav +10%. */
#dj-mixer .dj-bft { min-height: 22px; font-size: 9.35px; padding: 3.3px 1.1px; }
#dj-mixer .dj-bfreq { min-height: 24.2px; font-size: 10.3px; padding: 3.6px 1.2px; }   /* LOW/MID/HI grow on their own now */
#dj-mixer .dj-bfreq { min-width: 35.2px; }
#dj-mixer .dj-bfx-beat { font-size: 11px; }
#dj-mixer .dj-bfx-beat-dn, #dj-mixer .dj-bfx-beat-up {
  min-width: 28.6px; min-height: 22px; font-size: 11px; }
/* DECK A/C/E selector pills +50% (Marcelo). The label grows with them so the
   row keeps its proportions. */
#dj-mixer .dj-dsel { min-width: 54px; height: 36px; border-radius: 18px; font-size: 18px; }
#dj-mixer .dj-dsel-lbl { font-size: 15px; }
/* BEAT FX nav row: the ◄ ► steppers and LOW/MID/HI 30% wider, and the row's gap
   closed so they sit together (Marcelo). "1 BEAT" between them is flex:1, so the
   wider buttons take their extra width from its slack rather than from the row. */
#dj-mixer .dj-bfx-row { gap: 2px; }
/* explicit width, not min-width: their natural size (44px) already exceeded the
   37.2px floor, so a min-width did nothing at all. */
#dj-mixer .dj-bfx-beat-dn, #dj-mixer .dj-bfx-beat-up { width: 57px; flex: 0 0 auto; }
/* …and "1 BEAT" keeps its own width so the wider steppers cannot clip it. */
#dj-mixer .dj-bfx-beat { min-width: max-content; }
#dj-mixer .dj-bfreq { min-width: 50.4px; }   /* LOW/MID/HI +10% again */
/* The 🎧 headphone-cue pill +20% (Marcelo). Its width comes from the channel
   column, so a min-width is needed as well as the padding — padding alone would
   only have made it taller. */
/* 🎧 pill +30%, written to land at 0.615: it renders ~14x30, so the CSS numbers
   are ~1.6x the size wanted on screen. min-width 30px = ~18 rendered. */
#dj-mixer .dj-hp { padding: 14px 0; min-width: 30px; font-size: 1.55em; }
/* The deck VOL row drops a little, away from the pads above it (Marcelo).
   Written at the deck's scale: 8px of CSS margin is ~5px on screen, which is the
   smallest nudge that shows — 3% of the row would have been well under a pixel. */
#dj-mixer .cdj-volrow { margin-top: 10px; }
/* CROSSFADER / MASTER / BOOTH 10% closer (Marcelo). The 32px between them is a
   16px bottom margin on each master row; 11px takes ~3px off each gap, which is
   the 10% at this scale. */
#dj-mixer .dj-master { margin-bottom: 11px; }
/* REVERTED: a negative margin on the CROSSFADER caption made both gaps BIGGER
   (32->47, 31->43), not smaller — it changed the centre column's height enough
   that the mixer re-fit at a different zoom, and everything grew. Left alone. */
/* The CUE row lifts a little toward the channels above it (Marcelo). Kept small
   on purpose: this column re-fits its zoom when its height changes, and a bigger
   pull made every gap in the master section GROW rather than shrink. */
#dj-mixer .dj-deck-btns { margin-top: -16px; }   /* 2px off the jog wheel — the floor */
/* The whole block under each fader — letter, colour chip, COLOR knob, CUE —
   lifts together (Marcelo). Pulling its FIRST element up carries the rest with
   it, rather than nudging each one and having them drift apart. The LED meter
   sits between them in the DOM but is absolutely positioned, so it is skipped. */
/* A negative margin on the block's first item did almost nothing — the channel
   column has an 8px gap between EVERY child, and freeing space there just got
   redistributed (block moved 1px, the fader moved down 5). Tightening the gap
   itself is what actually lifts the lower half. */
/* REVERTED that too: tightening the column gap moved the block DOWN 3px and the
   knobs down 22px. Every height change in this channel re-fits the whole mixer,
   and the re-fit outweighs the space saved. The CUE row is moved on its own
   margin instead, which is the smallest possible change to the column. */
#dj-mixer .dj-chcue { margin-top: -2px; }
/* The crossfader's cap was pushing up into the CUE row. Its track top sits at
   1410 and the cap is 46px tall centred on it, so the cap's top edge reaches
   ~1400 while the CUE row ends at 1401.5 — the red cap Marcelo photographed
   poking up between the CUE buttons. The channels sat only 7px above the
   crossfader; this gives the cap the room it needs to clear them.
   Adding height (rather than taking it) is also the safe direction here: this
   panel re-fits when a column's height changes, and every attempt to CLOSE space
   in the channel made the gaps grow instead. */
#dj-mixer .dj-xfade-wrap { margin-top: 16px; }
/* The crossfader's track line runs behind its cap, not across it — the same fix
   as the OCT and master VOL faders earlier: give the input its own stacking
   order so the thumb is unambiguously the topmost thing in the wrap, and make
   the cap's colour layer fully opaque so nothing shows through it. */
#dj-mixer .dj-xfade-wrap { position: relative; z-index: 0; }
#dj-mixer #dj-xfade { position: relative; z-index: 2; }
#dj-mixer #dj-xfade::-webkit-slider-runnable-track { background: transparent; box-shadow: none; }
#dj-mixer #dj-xfade::-moz-range-track { background: transparent; }
/* The crossfader cap takes its own colour variable, falling back to the mixer's
   warm so nothing changes until a colour is picked. */
#dj-mixer #dj-xfade::-webkit-slider-thumb {
  background:
    linear-gradient(90deg, transparent 40%, rgba(255,255,255,.9) 48%, rgba(255,255,255,.9) 52%, transparent 60%),
    linear-gradient(180deg, var(--xf-cap, var(--dj-warm, #ff8a3d)),
      color-mix(in srgb, var(--xf-cap, var(--dj-warm, #ff8a3d)) 42%, black));
  box-shadow: 0 0 18px color-mix(in srgb, var(--xf-cap, var(--dj-warm, #ff8a3d)) 80%, transparent),
    0 3px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.45); }
#dj-mixer #dj-xfade::-moz-range-thumb {
  background: linear-gradient(180deg, var(--xf-cap, var(--dj-warm, #ff8a3d)),
    color-mix(in srgb, var(--xf-cap, var(--dj-warm, #ff8a3d)) 42%, black));
  box-shadow: 0 0 18px color-mix(in srgb, var(--xf-cap, var(--dj-warm, #ff8a3d)) 80%, transparent); }
/* Moved out of the crossfader row (Marcelo) — it used to sit inline next to the
   B label, competing with #dj-xfade for flex width. Now it's tucked below the
   track instead, so the slider itself can grow to the same length as MASTER/BOOTH. */
#dj-mixer .dj-xf-color { position: absolute; top: 100%; right: 0; margin-top: 6px; width: 22px; height: 16px; padding: 0;
  border: 1px solid rgba(255,255,255,.35); border-radius: 4px; background: transparent; cursor: pointer; z-index: 1; }
#dj-mixer .dj-xf-color::-webkit-color-swatch-wrapper { padding: 1px; }
#dj-mixer .dj-xf-color::-webkit-color-swatch { border: none; border-radius: 3px; }
/* The deck PLAY buttons become rounded rectangles instead of circles (Marcelo).
   Wider than tall, like the reference — which also gives the thumb a bigger
   target than a 38px circle did, the same reason behind the rest of this pass.
   Colour, gradient and glow are untouched; only the shape changes. */
/* The deck PLAY buttons become rounded rectangles rather than circles. Sized so
   they are bigger than the 38px circle they replace, not smaller — a wider
   target, which is the point of this pass. NB this deck is NOT inside the
   channel strip's 0.615 zoom, so these numbers land 1:1 on screen. */
#dj-mixer .dj-play { width: 91px; height: 58px; border-radius: 12px; }   /* ~56x36 on screen */
/* CUE loses its circle too, to match. It is 54px round in the deck row; this is
   the same rectangle as PLAY so the pair reads as one control group. */
#dj-mixer .dj-deck-btns .dj-cue-btn { width: 91px; height: 58px; border-radius: 12px; }
/* The last round PLAY button: .dj-mini-play, the purple circle in the browse /
   library rows. Same rounded rectangle as the deck's PLAY and CUE, so every play
   control in the mixer reads the same. Its purple gradient and the pink
   "playing" glow are untouched — only the shape and the width change. */
#dj-mixer .dj-mini-play { width: 58px; height: 38px; border-radius: 11px; }
/* The CDJ deck's PLAY gets a wider share of the transport row (Marcelo). It sat
   at flex:1 alongside CUE, 🎧 and 📁, so all four were equal; PLAY is the one
   reached for most, and it now takes roughly twice a neighbour's width without
   the row growing or the others being squeezed out of usefulness. */
#dj-mixer .cdj-play { flex: 2.2 1 0; }

/* ── 🎥👻 Recording ghost controls (spec: recording-ghost-controls.md) ──────
   Fixed overlay OUTSIDE #stage: Element Capture excludes it from the file by
   construction, so the host keeps every control while recording. The layer
   never eats pointer events — the stage below must keep receiving tile-body
   drags — only the relocated chrome itself is interactive. z 140 keeps it
   above the stage and below the portaled ⋮ sheet (150) and every modal. */
#rec-ghost-layer { position: fixed; inset: 0; z-index: 140; pointer-events: none; }
#rec-ghost-layer.hidden { display: none; }
/* The proxy box is a pure coordinate frame: it must never eat a pointer event
   (the stage below keeps receiving tile-body drags — 'I cannot move it' was a
   box shielding the tile), never paint an owner class's visuals, and never
   grow the pseudo-content some owners carry. Only the relocated chrome inside
   it is real. */
#rec-ghost-layer .rg-box { position: fixed !important; pointer-events: none !important;
  margin: 0 !important; background: none !important; border: none !important;
  box-shadow: none !important; outline: none !important; clip-path: none !important;
  -webkit-clip-path: none !important; overflow: visible !important; filter: none !important;
  backdrop-filter: none !important; }
#rec-ghost-layer .rg-box::before, #rec-ghost-layer .rg-box::after { content: none !important; }
#rec-ghost-layer .rg-box > * { pointer-events: auto; }
/* 🖱 The ghost pointer (rec-ghost.js tickCursor): the host's cursor while the
   real one is hidden over the stage so the capture cannot draw it.
   EVERYWHERE over the stage area, no exceptions: Chrome stamps the system
   cursor into the captured frames wherever it is visible over the stage —
   including over the ghost layer's own chrome, which is how Marcelo's file
   got the drag-hand right where he was pulling a grip. The relocated chrome,
   the portaled ⋮ sheet and the phone monitor all go cursorless too; the
   ghost arrow is the only pointer, and it can never be recorded. */
/* A 1×1 TRANSPARENT cursor image, not `none`. Verified 2026-08-12: Chrome does
   not support the getDisplayMedia `cursor` constraint at all
   (getSupportedConstraints().cursor === false), so `cursor: 'never'` was
   ignored on every recording ever made here — CSS is the only lever. And a
   capturer that composites the cursor asks for the cursor's IMAGE: `none` can
   leave it drawing the default arrow, while a transparent image gives it
   exactly one thing to draw — nothing. `none` stays as the fallback for any
   engine that refuses the URL. */
body.rec-nocursor #stage, body.rec-nocursor #stage *,
body.rec-nocursor #rec-ghost-layer, body.rec-nocursor #rec-ghost-layer *,
body.rec-nocursor > .tile-menu, body.rec-nocursor > .tile-menu *,
body.rec-nocursor #both-monitor, body.rec-nocursor #both-monitor * {
  cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==") 0 0, none !important;
}
#rg-cursor { position: fixed; z-index: 149; pointer-events: none; margin: -1px 0 0 -1px; }

/* 📅 The scheduled-streams list on Home gets the same window as the panel's
   card lists (Marcelo: "it will be a lot of people that will schedule
   many streams… add a scroller there as well"). Same three-part recipe the
   recordings list needed before it behaved: a window, an always-visible
   thumb, and real space under the last card — with app.js chaining the
   page's own wheel into the box so nobody has to find a second scrollbar. */
#home-stream-list {
  max-height: min(37.2vh, 400px);   /* trimmed to taste: +10%, −15%, −15%, −10% (Marcelo, 2026-08-12) */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 6px;
  padding-bottom: 14px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.3) transparent;
}
#home-stream-list::-webkit-scrollbar { width: 10px; }
#home-stream-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,.28); border-radius: 5px; }
#home-stream-list::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.45); }

/* ─── ⛔ Trial-expiry lock (trial-expiry-lockout.md decision 8) ─────────────
   The pricing modal as the ONLY surface: opaque backdrop (nothing usable may
   show through — the studio was never built behind it), no close affordance,
   and the lock head above the plans. Fixed colours, never var(--accent). */
body.trial-locked #pricing-modal { background: #0b0b11; }
body.trial-locked #pricing-close { display: none; }
.trial-lock-head {
  text-align: center;
  border: 1px solid rgba(255, 176, 32, .45);
  background: rgba(255, 176, 32, .08);
  border-radius: 12px;
  padding: 14px 16px 12px;
  margin-bottom: 14px;
}
.trial-lock-head h2 { margin: 0 0 6px; }
.trial-lock-head p { margin: 0 0 10px; opacity: .85; }
