/**
 * Operator UI.
 *
 * Deliberately flat: no shadows on moving parts, no blur, no WebGL, no
 * animation that runs when nothing changed. On the festival machine this may
 * share a GPU with TouchDesigner rendering a building, and the operator screen
 * must never be the thing that costs a frame.
 *
 * Colour is used as information, not decoration - a lane's state must be
 * readable from arm's length on a dark stage.
 */

:root {
    --bg: #0d0f12;
    --bg-panel: #15181d;
    --bg-raised: #1c2027;
    --bg-hover: #242932;
    --line: #2b313b;
    --line-bright: #3a424f;

    --text: #e6e9ef;
    --text-dim: #8b94a3;
    --text-faint: #5b6472;

    --active: #35d07f;
    --active-dim: #1d6b45;
    /* Fader fill. Brighter than --active-dim so gain reads at arm's length on
       a dark stage, but cooler and flatter than the meter's --active so the
       two green bars beside each other stay tellable apart. */
    --fader-fill: #2f8f5e;
    --queued: #f0b429;
    --exiting: #e2564a;
    --accent: #5b9dff;

    --lane-gap: 6px;
    --radius: 5px;

    --strip-height: 190px;
    --topbar-height: 62px;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    background: var(--bg);
    color: var(--text);
    font: 13px/1.4 "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
    user-select: none;
}

button, select, input { font: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; }
[hidden] { display: none !important; }

/* --- boot ---------------------------------------------------------------- */

.boot {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--bg);
    z-index: 50;
}

.boot-panel {
    width: 340px;
    padding: 28px;
    background: var(--bg-panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    text-align: center;
}

.boot-panel h1 { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.boot-sub { margin: 4px 0 24px; color: var(--text-faint); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; }

.picker-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 6px;
    font-size: 11px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.picker-count { font-variant-numeric: tabular-nums; }
.picker-count.full { color: var(--active); }

/* Scrolls rather than growing: the list is however many packs are staged, and
   the panel must stay on screen on a tablet. */
.pack-list {
    max-height: 300px;
    overflow-y: auto;
    text-align: left;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg-raised);
    padding: 4px;
    scrollbar-width: thin;
    scrollbar-color: var(--line-bright) transparent;
}

.pack-row {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 8px 9px;
    margin-bottom: 2px;
    border: 1px solid transparent;
    border-left: 3px solid var(--line-bright);
    border-radius: 3px;
    background: transparent;
    text-align: left;
    color: var(--text-dim);
}
.pack-row:hover { background: var(--bg-hover); color: var(--text); }

/* The order badge is the point of the design: it shows WHICH pack anchors the
   session tempo and key, not merely that two are selected. */
.pack-order {
    flex: none;
    width: 18px; height: 18px;
    display: grid; place-items: center;
    border: 1px solid var(--line-bright);
    border-radius: 3px;
    font-size: 10px; font-weight: 700;
    color: transparent;
}
.pack-row.on { border-left-color: var(--active); color: var(--text); background: rgba(53, 208, 127, 0.08); }
.pack-row.on .pack-order { background: var(--active); border-color: var(--active); color: #06121f; }
.pack-row.anchor .pack-order { background: var(--accent); border-color: var(--accent); }

.pack-meta { flex: 1; min-width: 0; }
.pack-name { display: block; font-weight: 600; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pack-sub { display: block; font-size: 10px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.pack-row.disabled { opacity: 0.4; cursor: not-allowed; }
.pack-row.disabled:hover { background: transparent; color: var(--text-dim); }

.picker-hint { margin: 10px 0 14px; font-size: 11px; color: var(--text-faint); text-align: left; }

.boot-actions { display: flex; gap: 8px; }
.boot-button {
    flex: 1;
    padding: 11px;
    background: var(--accent);
    color: #06121f;
    font-weight: 600;
    border-radius: var(--radius);
}
.boot-button:disabled { background: var(--bg-raised); color: var(--text-faint); cursor: not-allowed; }

/* A lane's clips are grouped by pack when more than one is loaded, so a
   merged column reads as labelled blocks rather than an interleaved jumble. */
.clip-pack-head {
    padding: 6px 8px 3px;
    font-size: 9px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-faint);
    position: sticky;
    top: -3px;
    background: var(--bg-panel);
}

.boot-status { margin-top: 14px; min-height: 32px; font-size: 12px; color: var(--text-dim); }
.boot-status.error { color: var(--exiting); }

/* --- topbar -------------------------------------------------------------- */

.topbar {
    display: flex;
    align-items: center;
    /* 14px, not 22: with six groups the gaps alone were 110px of the bar. */
    gap: 12px;
    height: var(--topbar-height);
    padding: 0 16px;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--line);
}

.topbar-group { display: flex; align-items: center; gap: 10px; }
.topbar-group.right { margin-left: auto; }

.transport-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    /* Fixed: "▶ Play" and "■ Stop" render 2.6px apart, and the button sits at
       the far left of the bar - so every transport start nudged every control
       to its right. */
    width: 88px;
    padding: 8px 0;
    background: var(--bg-raised);
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    font-weight: 600;
}
.transport-button .glyph { font-size: 11px; color: var(--active); }
.transport-button.playing { background: var(--active-dim); border-color: var(--active); }
.transport-button.playing .glyph { color: var(--text); }

.ghost-button {
    padding: 8px 12px;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    color: var(--text-dim);
}
.ghost-button:hover { background: var(--bg-hover); color: var(--text); }

.phrase-group { gap: 12px; }

.phrase-readout {
    font-variant-numeric: tabular-nums;
    font-size: 19px;
    font-weight: 600;
    min-width: 52px;
}
.phrase-sep { color: var(--text-faint); margin: 0 1px; }
.phrase-beat { color: var(--text-dim); }

/* Eight ticks = eight bars. The fill crossing the last tick is the cue that a
   queued clip is about to land. */
.phrase-meter {
    position: relative;
    width: 110px;
    height: 8px;
    background: var(--bg-raised);
    border-radius: 4px;
    overflow: hidden;
}
.phrase-fill { height: 100%; width: 0; background: var(--accent); }
.phrase-ticks { position: absolute; inset: 0; display: flex; }
.phrase-ticks i { flex: 1; border-right: 1px solid var(--bg); }
.phrase-ticks i:last-child { border-right: none; }

.countdown {
    font-variant-numeric: tabular-nums;
    font-size: 12px;
    color: var(--text-dim);
    min-width: 40px;
}
.countdown.imminent { color: var(--queued); font-weight: 600; }

.field { display: flex; align-items: center; gap: 8px; }
.field-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }

.tempo-control { display: flex; align-items: center; gap: 9px; }
.tempo-control input[type="range"] { width: 92px; accent-color: var(--accent); }
.tempo-value { font-variant-numeric: tabular-nums; font-size: 15px; font-weight: 600; min-width: 30px; }

/* The chip always occupies its space and only toggles visibility. Appearing
   and disappearing with display:none shoved every control to its right
   sideways, which is unacceptable on a bar the operator is aiming at. */
.pending-chip {
    width: 44px;
    text-align: center;
    padding: 3px 0;
    background: rgba(240, 180, 41, 0.14);
    border: 1px solid var(--queued);
    border-radius: 3px;
    color: var(--queued);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.pending-chip.invisible { visibility: hidden; }

/* The theme select carries its own pending state - it already shows the
   incoming name, so it only needs to say "not yet". */
.theme-select.pending {
    border-color: var(--queued);
    color: var(--queued);
    animation: pulse 0.9s ease-in-out infinite;
}

.theme-select {
    padding: 6px 8px;
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-weight: 600;
}
.picker-head-theme { margin-top: 14px; }
.boot-theme { width: 100%; padding: 9px 10px; }

.key-select {
    padding: 6px 8px;
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-weight: 600;
}

.toggle { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); }
.toggle input { accent-color: var(--accent); }

/* Key lock as an icon toggle. The words "Key lock" cost ~55px on a bar that
   had none to spare; the padlock plus its lit state carries the same meaning
   and the title attribute carries the rest. */
.lock-toggle { position: relative; display: inline-grid; place-items: center; }
.lock-toggle input {
    position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer;
}
.lock-toggle span {
    display: grid; place-items: center;
    width: 30px; height: 30px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg-raised);
    font-size: 13px;
    color: var(--text-faint);
}
.lock-toggle input:checked + span {
    background: var(--active-dim);
    border-color: var(--active);
    color: #fff;
}
.lock-toggle input:focus-visible + span { outline: 1px solid var(--accent); outline-offset: 2px; }

/* Queued for the next boundary - same amber language as a queued clip. */
.lock-toggle.pending span {
    border-color: var(--queued);
    color: var(--queued);
    animation: pulse 0.9s ease-in-out infinite;
}

/* An inert control should look inert. */
.key-select:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    color: var(--text-faint);
}

/* --- master output ------------------------------------------------------- */

.master-wrap { position: relative; }
/* Keep the label and its value on one line - a cramped top bar was wrapping
   them, which changes the bar's height as well as the button's width. */
#masterButton { white-space: nowrap; }

/* Reserve room for three digits so the button does not resize as the value
   crosses 100 -> 99 -> 9. `ch` is the width of one figure, and tabular-nums
   makes every digit that same width, so 3ch is exactly three digits. */
.master-value {
    display: inline-block;
    /* 3.25ch, not 3ch: a three-digit value renders a fraction wider than three
       `ch` units, which left the button 0.1px wider at 100 than at 99. The
       extra quarter pads every case onto the same box. */
    min-width: 3.25ch;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--text);
    margin-left: 4px;
}
.master-value.reducing { color: var(--queued); }

.master-popup {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    width: 230px;
    padding: 14px;
    background: var(--bg-panel);
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    z-index: 40;
}

.master-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}
.master-label { font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-dim); }
/* Same fixed-width treatment inside the popup. The limiter readout needs more
   room: it swings between "0.0 dB" and "-12.3 dB". */
.master-readout {
    display: inline-block;
    min-width: 3.25ch;
    text-align: right;
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.master-readout.wide { min-width: 8ch; }

#masterFader {
    width: 100%;
    margin: 2px 0 12px;
    accent-color: var(--active);
}

.master-meter {
    flex: 1;
    height: 8px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 4px;
    overflow: hidden;
}
.master-meter i {
    display: block;
    height: 100%;
    width: 0;
    background: linear-gradient(to right, var(--active) 0%, var(--active) 70%, var(--queued) 88%, var(--exiting) 100%);
}

/* Amber whenever the limiter is actually working, so it reads as "engaged",
   not as a fault. */
.master-readout.reducing { color: var(--queued); }

.master-note { margin: 4px 0 10px; font-size: 10px; line-height: 1.5; color: var(--text-faint); }
.master-reset { width: 100%; padding: 6px; font-size: 11px; }

/* Mode is derived, never chosen - so it reads as a status word, not a control.
   Fixed width so the bar does not shift as it changes. */
.mode-label {
    display: inline-block;
    /* Short codes, not full words: reserving 11 characters for "PERFORMANCE"
       cost ~55px on a bar with none to spare. Colour carries most of the
       meaning anyway, and the full word is in the title.
       A FIXED width, not min-width: these are letters, so `ch` (a digit's
       width) does not bound them - "PERF" renders ~3px wider than "IDLE" at
       the same character count, and the bar shifted every time the transport
       started. */
    width: 52px;
    text-align: center;
    padding: 4px 8px;
    font-size: 10px;
    letter-spacing: 0.09em;
    border: 1px solid var(--line);
    border-radius: 3px;
    background: var(--bg-raised);
    color: var(--text-faint);
}
.mode-label.PERFORMANCE { color: var(--active); border-color: var(--active-dim); }
.mode-label.LOADING { color: var(--queued); border-color: var(--queued); }

.status { display: flex; align-items: center; gap: 14px; }
.status-pack { font-size: 12px; color: var(--text-dim); letter-spacing: 0.04em; }

.bridge-wrap { position: relative; }
.bridge-light {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg-raised);
}
.bridge-light:hover { background: var(--bg-hover); }
.bridge-light i { width: 8px; height: 8px; border-radius: 50%; background: var(--exiting); }
.bridge-light.up i { background: var(--active); }

.bridge-popup {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    width: 250px;
    padding: 14px;
    background: var(--bg-panel);
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    z-index: 40;
}
.bridge-state { font-size: 12px; font-weight: 600; color: var(--exiting); }
.bridge-state.up { color: var(--active); }
.bridge-addr { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text); }

/* --- session ------------------------------------------------------------- */

.session {
    display: grid;
    grid-template-rows: 1fr var(--strip-height);
    height: calc(100% - var(--topbar-height));
}

.lanes, .strips {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: var(--lane-gap);
    padding: 0 var(--lane-gap);
}

.lanes { padding-top: var(--lane-gap); min-height: 0; }

.lane { display: flex; flex-direction: column; min-height: 0; min-width: 0; }

.lane-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 8px;
    background: var(--bg-panel);
    border: 1px solid var(--line);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
}
.lane-name { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; }
.lane-count { font-size: 10px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* The clip list is the only scrolling region: lane sizes differ per pack, so
   the column scrolls rather than the page. */
.clips {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    background: var(--bg-panel);
    border: 1px solid var(--line);
    border-top: none;
    padding: 3px;
    scrollbar-width: thin;
    scrollbar-color: var(--line-bright) transparent;
}
.clips::-webkit-scrollbar { width: 8px; }
.clips::-webkit-scrollbar-thumb { background: var(--line-bright); border-radius: 4px; }
.clips::-webkit-scrollbar-track { background: transparent; }

.clip {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 7px 8px;
    margin-bottom: 2px;
    background: var(--bg-raised);
    border: 1px solid transparent;
    border-left: 3px solid var(--line-bright);
    border-radius: 3px;
    text-align: left;
    color: var(--text-dim);
    font-size: 12px;
    overflow: hidden;
}
.clip:hover { background: var(--bg-hover); color: var(--text); }

.clip-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clip-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--line-bright); flex: none; }

.clip.active {
    background: var(--active-dim);
    border-left-color: var(--active);
    color: #fff;
    font-weight: 600;
}
.clip.active .clip-dot { background: var(--active); }

/* Queued has to read instantly from across a stage - the performer may be
   waiting up to 8 bars and needs to know the machine heard them. */
.clip.queued {
    border-left-color: var(--queued);
    color: var(--queued);
    background: rgba(240, 180, 41, 0.10);
}
.clip.queued .clip-dot { background: var(--queued); animation: pulse 0.9s ease-in-out infinite; }

.clip.exiting { border-left-color: var(--exiting); color: var(--exiting); }
.clip.exiting .clip-dot { background: var(--exiting); animation: pulse 0.9s ease-in-out infinite; }

@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* --- channel strips ------------------------------------------------------ */

.strips {
    padding-top: var(--lane-gap);
    padding-bottom: var(--lane-gap);
    /* The row is a fixed height; without this a tall child would spill past
       the bottom of the page instead of being contained. */
    min-height: 0;
    overflow: hidden;
}

.strip {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px;
    background: var(--bg-panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    min-width: 0;
    /* Grid items default to min-height:auto, which refuses to shrink below
       content. The vertical range input reports a large intrinsic height, so
       without these the strip grew past its row and ran off screen. */
    min-height: 0;
    height: 100%;
    overflow: hidden;
}

.strip-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.strip-name { font-size: 11px; font-weight: 700; letter-spacing: 0.09em; }

.strip-state {
    font-size: 9px;
    letter-spacing: 0.08em;
    padding: 2px 5px;
    border-radius: 3px;
    background: var(--bg-raised);
    color: var(--text-faint);
}
.strip-state.ACTIVE { background: var(--active-dim); color: #fff; }
.strip-state.QUEUED { background: rgba(240, 180, 41, 0.18); color: var(--queued); }
.strip-state.EXITING { background: rgba(226, 86, 74, 0.18); color: var(--exiting); }

.strip-playing {
    font-size: 11px;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-height: 15px;
}

/* Fader and meter sit side by side so level and gain read as one control. */
.strip-body { display: flex; gap: 9px; flex: 1 1 0; min-height: 0; align-items: stretch; }

.fader-wrap { flex: 1; display: flex; min-height: 0; justify-content: center; }

/* Vertical range input.
 *
 * `-webkit-appearance: slider-vertical` was REMOVED in Chrome 121 - keeping it
 * alongside writing-mode rendered a bare track with no thumb until a drag
 * forced a repaint. Orientation now comes from writing-mode alone, and the
 * track/thumb are drawn explicitly so the result does not depend on whatever
 * the browser's native vertical slider happens to look like. */
.fader {
    -webkit-appearance: none;
    appearance: none;
    writing-mode: vertical-lr;
    direction: rtl;
    width: 26px;
    height: 100%;
    min-height: 0;
    margin: 0;
    background: transparent;
    cursor: ns-resize;
}
/* The filled portion below the thumb is the current gain. Chrome draws no
 * such fill for a range input, so it is a gradient with a hard stop at
 * --fill, which the view updates whenever the value changes. Gradient
 * keywords are physical, so `to top` fills upward regardless of the
 * vertical writing mode. */
.fader::-webkit-slider-runnable-track {
    width: 6px;
    height: 100%;
    border: 1px solid var(--line);
    border-radius: 3px;
    background: linear-gradient(
        to top,
        var(--fader-fill) 0 var(--fill, 0%),
        var(--bg) var(--fill, 0%) 100%
    );
}
.fader:hover::-webkit-slider-runnable-track {
    background: linear-gradient(
        to top,
        var(--active) 0 var(--fill, 0%),
        var(--bg-raised) var(--fill, 0%) 100%
    );
}
.fader::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 24px;
    height: 11px;
    /* Centre the thumb across the 6px track. */
    margin-left: -10px;
    background: var(--text-dim);
    border: 1px solid var(--line-bright);
    border-radius: 2px;
}
.fader:hover::-webkit-slider-thumb { background: var(--text); }
.fader:active::-webkit-slider-thumb { background: var(--active); border-color: var(--active); }
.fader:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

.meter {
    width: 11px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 2px;
    position: relative;
    overflow: hidden;
    flex: none;
}
.meter-fill {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 0;
    background: linear-gradient(to top, var(--active) 0%, var(--active) 62%, var(--queued) 84%, var(--exiting) 100%);
}

.strip-foot { display: flex; gap: 6px; }
.strip-foot button {
    flex: 1;
    padding: 5px 0;
    font-size: 10px;
    letter-spacing: 0.06em;
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 3px;
    color: var(--text-faint);
}
.strip-foot button:hover { background: var(--bg-hover); color: var(--text); }
.strip-foot .mute.on { background: var(--exiting); border-color: var(--exiting); color: #fff; font-weight: 600; }
.strip-foot .solo.on { background: var(--queued); border-color: var(--queued); color: #1a1204; font-weight: 600; }

/* Silenced only because another lane is soloed - dimmed so it is obvious why
   the lane went quiet without hunting for which button is lit. */
.strip.silenced { opacity: 0.45; }
.strip.silenced .meter-fill { background: var(--line-bright); }

/* --- toast --------------------------------------------------------------- */

.toast {
    position: fixed;
    bottom: 18px;
    left: 50%;
    transform: translateX(-50%);
    padding: 9px 16px;
    background: var(--bg-raised);
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    font-size: 12px;
    z-index: 60;
}
.toast.error { border-color: var(--exiting); color: var(--exiting); }

/* Narrow screens: an iPad in portrait still needs all eight lanes visible, so
   the columns compress rather than wrap. Below tablet width the strips shrink
   first, since clip selection matters more than fader precision. */
@media (max-width: 1100px) {
    :root { --strip-height: 150px; --lane-gap: 4px; }
    .topbar { gap: 14px; }
    .phrase-meter { width: 130px; }
    .tempo-control input[type="range"] { width: 90px; }
}
