/* ==========================================================================
   VISUALIZE — VJ gig player & music visualizer
   Visual skin: Handfish design tokens (theme picked at runtime in settings).
   This file owns only the bespoke grid for the deck/main/library workspace.
   ========================================================================== */

/* Handfish theme CSS lists --hf-font-family for several themes WITHOUT a
   Blank metric-matched fallback (only Cabin / Lora / Atkinson / etc, no
   "X Blank"). Patch each to end in its matching Blank so the browser has
   somewhere to fall back to while the real font downloads. The Blank
   @font-faces are declared inline in index.html, and the active theme's
   Blank is preloaded by the inline boot script. */
[data-theme="dusk"]                { --hf-font-family: 'Cabin', 'Cabin Blank'; }
[data-theme="gothic"]              { --hf-font-family: 'Crimson Pro', 'Crimson Pro Blank'; }
[data-theme="earthy"]              { --hf-font-family: 'Roboto Slab', 'Roboto Slab Blank'; }
[data-theme="kawaii"]              { --hf-font-family: 'Baloo 2', 'Baloo 2 Blank'; }
[data-theme="high-contrast-dark"],
[data-theme="high-contrast-light"] { --hf-font-family: 'Atkinson Hyperlegible', 'Atkinson Hyperlegible Blank'; }
[data-theme="newspaper"]           { --hf-font-family: 'Lora', 'Lora Blank'; }
[data-theme="ocean"]               { --hf-font-family: 'Poppins', 'Poppins Blank'; }
[data-theme="sunset"]              { --hf-font-family: 'Quicksand', 'Quicksand Blank'; }
[data-theme="synthwave"]           { --hf-font-family: 'Tomorrow', 'Tomorrow Blank'; }
[data-theme="rave"]                { --hf-font-family: 'Comfortaa', 'Comfortaa Blank'; }

:root {
    /* Layout */
    --topbar-h: 72px;
    --controls-h: 168px;       /* two rows now */
    --library-w: 190px;        /* single-column card list */

    /* Handfish's default size scale is tuned for dense designer dashboards;
       this app gets read from a typical music-desk distance, not 2ft from
       the screen. Bump everything ~1.2x so labels are legible at a glance. */
    --hf-size-xs:   0.75rem;    /* 12px (handfish: 10px) */
    --hf-size-sm:   0.875rem;   /* 14px (handfish: 12px) */
    --hf-size-base: 1rem;       /* 16px (handfish: 14px) */
    --hf-size-md:   1.125rem;   /* 18px (handfish: 16px) */
    --hf-size-lg:   1.3125rem;  /* 21px (handfish: 18px) */
    --hf-size-xl:   1.5rem;     /* 24px (handfish: 20px) */
    --hf-size-2xl:  1.6875rem;  /* 27px (handfish: 24px) */

    /* Noisedeck visual language: borders off by default; layer
       distinction comes from bg color contrast (--hf-bg-base /
       -surface / -elevated) and 0.5rem-ish gaps. Per-element borders
       reintroduced where they actually carry information (focus
       rings, slider rims, manual-mode hint on density button). */
    --hf-border-width: 0;

    /* Near-invisible accent-tinted hairline for section dividers
       *inside* a panel (e.g. deck head / panel-head bottom rules).
       Panel-to-panel boundaries rely on bg contrast instead. */
    --vz-hairline: 1px solid color-mix(in srgb, var(--hf-text-bright) 8%, transparent);
}

html, body {
    height: 100%;
    overflow: hidden;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    font-family: var(--hf-font-family);
    font-size: var(--hf-size-sm);
    -webkit-font-smoothing: antialiased;
}

#app {
    display: grid;
    grid-template-columns: var(--library-w) 1fr;
    grid-template-rows: var(--topbar-h) 1fr var(--controls-h);
    /* Library extends full-height down the left edge; topbar + stage +
       controls all live in the right column. Right column self-aligns
       so the controls strip sits directly under the decks. */
    grid-template-areas:
        "lib top"
        "lib stage"
        "lib ctl";
    height: 100vh;
    width: 100vw;
    gap: 0;
}

button:focus-visible,
input:focus-visible,
select:focus-visible,
.program-card:focus-visible {
    outline: var(--hf-focus-ring-width) solid var(--hf-focus-ring-color);
    outline-offset: var(--hf-focus-ring-offset);
    border-radius: var(--hf-radius-sm);
}

/* Material Symbols Outlined helper (loaded via --hf-font-family-icon in
   handfish tokens.css). Inherits color from parent so it tints with the
   button text. */
.icon {
    font-family: var(--hf-font-family-icon);
    font-weight: normal;
    font-style: normal;
    font-size: 1em;
    line-height: 1;
    display: inline-block;
    vertical-align: middle;
    text-transform: none;
    letter-spacing: normal;
    word-wrap: normal;
    white-space: nowrap;
    direction: ltr;
    font-variation-settings: 'FILL' 0;
}

/* ─────────────── topbar ─────────────── */
.topbar {
    grid-area: top;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: 0 var(--hf-space-4);
    background: var(--hf-bg-surface);
    border-bottom: var(--hf-border-width) solid var(--hf-border-subtle);
    z-index: 10;
}

.brand {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
}

.brand-logo {
    font-size: var(--hf-size-xl);
    color: var(--hf-accent);
}

.brand-name {
    font-size: var(--hf-size-md);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    background: linear-gradient(90deg, var(--hf-blue), var(--hf-yellow), var(--hf-red));
    background-size: 200% 200%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: gradient-shift 6s ease-in-out infinite;
}

@keyframes gradient-shift {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
}

.brand-tag {
    font-size: var(--hf-size-xs);
    text-transform: uppercase;
    letter-spacing: var(--hf-tracking-wide);
    color: var(--hf-text-dim);
    margin-left: var(--hf-space-1);
}

.topbar-center { display: flex; justify-content: center; }

.bpm-block {
    display: flex;
    align-items: center;
    gap: var(--hf-space-3);
    padding: var(--hf-space-1) var(--hf-space-3);
    background: var(--hf-bg-elevated);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-md);
}

.tap-button {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: var(--hf-space-1) var(--hf-space-3);
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    letter-spacing: var(--hf-tracking-wide);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-bg), var(--hf-transition-border);
}
.tap-button:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }
.tap-button:active, .tap-button.flash {
    background: var(--hf-accent);
    color: var(--hf-bg-base);
    border-color: var(--hf-accent);
}

.bpm-display { display: flex; align-items: baseline; gap: var(--hf-space-1); }

#bpm-input {
    width: 90px;
    background: transparent;
    border: none;
    color: var(--hf-text-bright);
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-lg);
    font-weight: var(--hf-weight-bold);
    text-align: right;
    outline: none;
    appearance: textfield;
    -moz-appearance: textfield;
}
#bpm-input::-webkit-outer-spin-button,
#bpm-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.bpm-label {
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    letter-spacing: var(--hf-tracking-wide);
    white-space: nowrap;
}

.bpm-divider {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-color: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 2px 14px 2px 6px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    line-height: 1.3;
    cursor: pointer;
    outline: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 5' fill='currentColor'%3E%3Cpath d='M0 0l4 5 4-5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 4px center;
    background-size: 6px;
    transition: var(--hf-transition-border);
    margin: 0 var(--hf-space-1);
}
.bpm-divider:hover { border-color: var(--hf-border-hover); }
.bpm-divider:focus { border-color: var(--hf-accent); }
.bpm-divider option { background: var(--hf-bg-elevated); color: var(--hf-text-normal); }

.bpm-block.midi-sync .bpm-label,
.bpm-block.midi-sync #bpm-input { color: var(--hf-accent); }

.beat-indicator { display: flex; gap: var(--hf-space-1); }

.beat-dot {
    width: 12px;
    height: 12px;
    border-radius: var(--hf-radius-full);
    background: var(--hf-bg-muted);
    transition: background 0.04s linear, transform 0.04s linear;
}

.beat-dot.active {
    background: var(--hf-accent);
    transform: scale(1.3);
}

.beat-dot.downbeat.active {
    background: var(--hf-red);
    transform: scale(1.5);
}

.topbar-right {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
    justify-content: flex-end;
}

.status-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: var(--hf-space-1) var(--hf-space-2);
    background: var(--hf-bg-elevated);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    font-family: var(--hf-font-family-mono);
}

.status-pill .status-icon { font-size: var(--hf-size-sm); }
.status-pill[data-state="on"] { color: var(--hf-green); border-color: color-mix(in srgb, var(--hf-green) 50%, transparent); }
.status-pill[data-state="on"] .status-icon { color: var(--hf-green); }
.status-pill[data-state="warn"] { color: var(--hf-red); border-color: color-mix(in srgb, var(--hf-red) 50%, transparent); }

/* Audio/MIDI pills double as click-to-toggle buttons. */
button.status-pill {
    appearance: none;
    cursor: pointer;
    font: inherit;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
}
button.status-pill:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }
button.status-pill:hover .status-icon { color: var(--hf-text-bright); }
button.status-pill[data-state="on"]:hover { border-color: var(--hf-green); color: var(--hf-green); }

.icon-button {
    appearance: none;
    background: var(--hf-bg-elevated);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    width: 44px;
    height: 44px;
    border-radius: var(--hf-radius-sm);
    font-size: var(--hf-size-md);
    cursor: pointer;
    transition: var(--hf-transition-border), var(--hf-transition-color);
}
.icon-button:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }

/* ─────────────── library ─────────────── */
.library {
    grid-area: lib;
    display: flex;
    flex-direction: column;
    background: var(--hf-bg-surface);
    border-right: var(--hf-border-width) solid var(--hf-border-subtle);
    overflow: hidden;
}

.panel-head {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
    padding: var(--hf-space-3);
    border-bottom: var(--hf-border-width) solid var(--hf-border-subtle);
}

.panel-head h3 {
    font-size: var(--hf-size-xs);
    text-transform: uppercase;
    letter-spacing: var(--hf-tracking-wide);
    color: var(--hf-text-dim);
    font-weight: var(--hf-weight-bold);
    flex: 0 0 auto;
}

#library-search {
    flex: 1 1 auto;
    min-width: 0;             /* don't grow past the narrow column */
    width: 100%;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-normal);
    border-radius: var(--hf-radius-sm);
    padding: 5px 10px;
    font-size: var(--hf-size-sm);
    font-family: inherit;
    outline: none;
    transition: var(--hf-transition-border);
}
#library-search:focus { border-color: var(--hf-accent); }

.library-grid {
    flex: 1;
    overflow-y: auto;
    padding: var(--hf-space-2);
    display: grid;
    /* Single-column card list. Each card stays a compact 16:9 tile
       with title + load buttons superimposed on the rendered
       thumbnail; at 190px library width that's ~175px per card →
       ~98px tall. */
    grid-template-columns: 1fr;
    grid-auto-rows: max-content;
    gap: var(--hf-space-2);
    scrollbar-width: thin;
    scrollbar-color: var(--hf-border-subtle) transparent;
}

.library-grid::-webkit-scrollbar { width: 6px; }
.library-grid::-webkit-scrollbar-thumb { background: var(--hf-border-subtle); border-radius: 3px; }

.program-card {
    aspect-ratio: 16 / 9;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    overflow: hidden;
    cursor: grab;
    transition: var(--hf-transition-border);
    position: relative;
}

.program-card:hover { border-color: var(--hf-accent); }
.program-card:active { cursor: grabbing; }

/* Thumbnail layer: fills the entire card. Until the WebP arrives the
   skeleton ::after stripe gives the tile a tint cued by --card-tint so
   it's not just a blank rectangle. */
.program-card .pc-thumb {
    position: absolute;
    inset: 0;
    background: var(--hf-bg-elevated);
}

.program-card .pc-thumb::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--card-tint, var(--hf-accent)) 22%, transparent),
        transparent 65%);
    opacity: 0.7;
    transition: opacity 0.25s ease;
    pointer-events: none;
}
.program-card.pc-thumb-ready .pc-thumb::after { opacity: 0; }

.program-card .pc-thumb-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.25s ease;
}
.program-card.pc-thumb-ready .pc-thumb-img { opacity: 1; }
.program-card.pc-thumb-failed .pc-thumb-img { display: none; }

/* Overlay: title / tagline / load-buttons floated over the thumb with
   a bottom-up dark gradient so labels stay legible regardless of how
   bright the program renders. Pointer events pass through except for
   the buttons (re-enabled below) so dragging the card stays easy. */
.program-card .pc-overlay {
    position: absolute;
    inset: 0;
    padding: 5px 7px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    color: #fff;
    pointer-events: none;
    background: linear-gradient(to top,
        rgba(0, 0, 0, 0.85) 0%,
        rgba(0, 0, 0, 0.55) 40%,
        rgba(0, 0, 0, 0) 75%);
}

.program-card .pc-title {
    font-size: 11px;
    font-weight: var(--hf-weight-semibold);
    line-height: 1.15;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
}

.program-card .pc-tagline {
    margin-top: 1px;
    font-size: 9px;
    line-height: 1.1;
    color: rgba(255, 255, 255, 0.78);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Imported entries get a tiny corner indicator so attribution stays
   visible without taking layout space (full credit on hover-tooltip). */
.program-card[data-imported="1"]::before {
    content: '';
    position: absolute;
    top: 4px;
    right: 4px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--card-tint, var(--hf-accent));
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
    z-index: 2;
    pointer-events: none;
}

.program-card .pc-actions {
    display: flex;
    gap: 3px;
    margin-top: 4px;
    pointer-events: auto;
}

.program-card .pc-load {
    flex: 1;
    appearance: none;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.22);
    color: #fff;
    border-radius: var(--hf-radius-sm);
    padding: 2px 0;
    font-size: 10px;
    font-weight: var(--hf-weight-bold);
    font-family: var(--hf-font-family-mono);
    line-height: 1;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.6);
}
.program-card .pc-load:hover {
    background: rgba(255, 255, 255, 0.28);
    border-color: rgba(255, 255, 255, 0.55);
}

.library-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--hf-space-2) var(--hf-space-3);
    border-top: var(--hf-border-width) solid var(--hf-border-subtle);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    font-family: var(--hf-font-family-mono);
}

.ghost-button {
    appearance: none;
    background: var(--hf-bg-elevated);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 5px 10px;
    font-size: var(--hf-size-xs);
    font-family: var(--hf-font-family-mono);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border);
    white-space: nowrap;
}
.ghost-button:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }

/* ─────────────── stage (decks + main) ─────────────── */
/* Main canvas (mixer output) is twice as wide as either deck preview
   so the user can read the live mixer at full resolution while the
   deck previews stay just-readable thumbnails. */
.stage {
    grid-area: stage;
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    gap: var(--hf-space-3);
    padding: var(--hf-space-3);
    min-height: 0;
}

.deck {
    position: relative;          /* positioning context for canvas-wrap */
    display: grid;
    grid-template-rows: auto 1fr auto;
    background: var(--hf-bg-surface);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-md);
    overflow: hidden;
    min-height: 0;
    transition: var(--hf-transition-border);
}
/* Head / content / meta render *over* the absolutely-centered
   canvas so the bg-elevated head + meta strips paint cleanly at
   the top / bottom of the panel even when the canvas is large. */
.deck-head, .deck-content, .deck-meta { position: relative; z-index: 1; }

/* Deck head: single row, zoom (density) button left, edit + shuffle
   right. The DECK A / program-name / tagline identity block moved
   to .deck-meta at the bottom so the head is just the action rail. */
.deck-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--hf-space-2);
    padding: var(--hf-space-2) var(--hf-space-3);
    font-family: var(--hf-font-family-mono);
}

.deck-head-actions {
    display: flex;
    align-items: center;
    gap: var(--hf-space-1);
    justify-content: flex-end;
}

/* The "DECK A" / "DECK B" badge that lives in the bottom meta strip.
   Mono + uppercase + per-deck color so A reads blue, B reads red. */
.deck-label {
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    text-transform: uppercase;
}
.deck-a .deck-label { color: var(--hf-blue); }
.deck-b .deck-label { color: var(--hf-red); }

.deck-load-random,
.deck-edit-toggle {
    appearance: none;
    background: transparent;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-dim);
    border-radius: var(--hf-radius-sm);
    width: 32px;
    height: 32px;
    font-size: var(--hf-size-base);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border);
}
.deck-load-random:hover,
.deck-edit-toggle:hover { color: var(--hf-accent); border-color: var(--hf-accent); }
.deck-edit-toggle.active {
    color: var(--hf-bg-base);
    background: var(--hf-accent);
    border-color: var(--hf-accent);
}

/* Pixel-density button: cycles AUTO → 100% → 75% → 50% → 25%. Label
   shows "auto 100%" / "auto 50%" / etc when auto-deciding; plain
   "75%" / "25%" / etc when the user has pinned a manual override.
   Border tint shifts amber when auto-stepped-down so the user can see
   at a glance that the deck dropped to a lower buffer. */
.deck-density {
    appearance: none;
    background: transparent;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-dim);
    border-radius: var(--hf-radius-sm);
    height: 32px;
    padding: 0 8px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    letter-spacing: 0.04em;
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border);
}
.deck-density:hover { color: var(--hf-accent); border-color: var(--hf-accent); }
.deck-density[data-mode="manual"] {
    color: var(--hf-text-bright);
    border-color: color-mix(in srgb, var(--hf-accent) 60%, transparent);
}

/* The wrap is a thin frame around the canvas — height is driven by
   the canvas's intrinsic aspect ratio (width:100% + height:auto).
   Vertically centered in the deck *panel* (not the 1fr row of the
   deck's grid) via top:50% + translateY. That guarantees all three
   canvas midpoints (deck A / main / deck B) share a Y coordinate
   regardless of how tall each panel's head + meta strips are. */
.deck-canvas-wrap {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    background: var(--hf-bg-base);
    overflow: hidden;
    z-index: 0;
}

.deck-canvas {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
}

.deck-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* Holds both the canvas wrap (aspect-locked, top-anchored) and the
   DSL editor overlay. The editor takes position:absolute inset:0 of
   this container, which now fills the whole 1fr row of .deck — so
   when the editor opens it expands across the entire deck content
   area, not just the 16:9 canvas patch at the top. */
.deck-content {
    position: relative;
    min-height: 0;
    overflow: hidden;
}

/* DSL editor — per-deck overlay. Fills the .deck-content container,
   which means it stretches across the whole deck panel below the
   head, not just the canvas's 16:9 region. Per-segment darkening
   (injected by js/ui/codeEditor.js) keeps code legible over the
   live canvas content behind it. Font size knocked down a notch
   from the default 0.875rem since the deck columns are narrow. */
.deck-editor {
    position: absolute;
    inset: 0;
    z-index: 10;
    --code-editor-font-size: 0.75rem;
}
.deck-editor[hidden] { display: none; }
.deck-editor code-editor { width: 100%; height: 100%; }

/* DSL compile error — same treatment as polymorphic's #compiler-error:
   pinned to the bottom of the editor pane, red text with a dark
   per-line backdrop so it stays legible over the canvas. */
.deck-editor-error {
    position: absolute;
    left: 0.5em;
    right: 0.5em;
    bottom: 0.5em;
    padding: 0;
    background: transparent;
    color: #ff6b6b;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    line-height: 1.45;
    z-index: 11;
    cursor: pointer;
    user-select: text;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    word-break: break-word;
    max-height: 35%;
    overflow-y: auto;
}
.deck-editor-error[hidden] { display: none; }
.deck-editor-error span {
    display: inline;
    background: rgba(0, 0, 0, 0.85);
    color: #ff6b6b;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    padding: 0 0.15em;
}

/* Bottom-of-panel label block: DECK A / program title / tagline.
   Title + tagline match .program-card's .pc-title / .pc-tagline
   pixel-for-pixel (same font sizes, weights, line-heights,
   text-shadows, line clamps) so the deck panels read as a natural
   extension of the library list above. */
.deck-meta {
    padding: var(--hf-space-2) var(--hf-space-3);
}

.deck-meta .deck-program {
    margin-top: 2px;
    font-size: 11px;
    font-weight: var(--hf-weight-semibold);
    line-height: 1.15;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
}

.deck-meta .deck-tagline {
    margin-top: 1px;
    font-size: 9px;
    line-height: 1.1;
    color: rgba(255, 255, 255, 0.78);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ─────────────── main ─────────────── */
.main {
    position: relative;         /* positioning context for canvas-wrap */
    display: grid;
    /* head (top) | 1fr empty space (canvas-wrap floats here) |
       fx strip | mixer controls (bottom). The canvas is absolute,
       so the 1fr row stays unoccupied — without explicit placement,
       auto-placement would dump main-fx into the 1fr row and
       stretch the strobe/invert/… buttons full-height. */
    grid-template-rows: auto 1fr auto auto;
    background: var(--hf-bg-surface);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-md);
    overflow: hidden;
    min-height: 0;
}
.main-head        { grid-row: 1; }
.main-fx          { grid-row: 3; }
.mixer-controls   { grid-row: 4; }
/* Head / FX strip / mixer-controls paint above the absolute canvas. */
.main-head, .main-fx, .mixer-controls { position: relative; z-index: 1; }

.main-head {
    display: flex;
    align-items: center;
    gap: var(--hf-space-3);
    padding: var(--hf-space-2) var(--hf-space-3);
    border-bottom: var(--hf-border-width) solid var(--hf-border-subtle);
    font-family: var(--hf-font-family-mono);
}

.main-label {
    font-size: var(--hf-size-xs);
    font-weight: var(--hf-weight-bold);
    color: var(--hf-accent);
    letter-spacing: var(--hf-tracking-wide);
}

.main-meter {
    flex: 1;
    display: flex;
    gap: 6px;
    align-items: center;
}

.audio-band {
    flex: 1;
    height: 10px;
    background: var(--hf-bg-base);
    border-radius: 3px;
    overflow: hidden;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
}

.audio-band b {
    display: block;
    height: 100%;
    width: 0%;
    transform-origin: left;
    background: var(--hf-accent);
    transition: width 0.04s linear;
}

.audio-band.low b { background: var(--hf-blue); }
.audio-band.mid b { background: var(--hf-accent); }
.audio-band.high b { background: var(--hf-red); }

.main-res {
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
}

/* Same approach as the deck wraps — vertically centered in the
   .main panel so its midpoint lines up with the deck canvases'
   midpoints. */
.main-canvas-wrap {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    background: var(--hf-bg-base);
    overflow: hidden;
    z-index: 0;
}

.main-canvas {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
    image-rendering: auto;
}

.main-canvas.invert { filter: invert(1) hue-rotate(180deg); }
.main-canvas.bw { filter: grayscale(1) contrast(1.1); }
.main-canvas.invert.bw { filter: invert(1) grayscale(1) contrast(1.1); }

.main-fx-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: white;
    opacity: 0;
    transition: opacity 0.06s ease-out;
}

.main-fx-overlay.flash { opacity: 0.9; transition: opacity 0.3s ease-out; }
.main-fx-overlay.strobe { opacity: 1; transition: none; }

.main-canvas-wrap.zoomed .main-canvas {
    transform: scale(1.15);
    transition: transform 0.08s ease-out;
}

.main-fx {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: var(--hf-space-1);
    padding: var(--hf-space-2);
    border-top: var(--hf-border-width) solid var(--hf-border-subtle);
}

.fx-button {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-dim);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 6px 4px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    letter-spacing: var(--hf-tracking-wide);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border), var(--hf-transition-bg);
}

.fx-button:hover { color: var(--hf-text-bright); border-color: var(--hf-accent); }
.fx-button.active {
    background: var(--hf-accent);
    color: var(--hf-bg-base);
    border-color: var(--hf-accent);
}

/* ─────────────── controls bar ─────────────── */
/* Two-row layout now that the bar is narrower (library took the left
   column). Row 1 = transport (speed A, mixer + crossfader, speed B —
   the things you wiggle live during a set). Row 2 = utility
   (auto-VJ + scenes + record + output — the things you set up once
   and leave alone for the rest of the set). */
.controls {
    grid-area: ctl;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    grid-template-areas:
        "transport"
        "status";
    gap: var(--hf-space-2);
    padding: var(--hf-space-2) var(--hf-space-3);
    background: var(--hf-bg-surface);
    border-top: var(--hf-border-width) solid var(--hf-border-subtle);
    z-index: 5;
}

/* Three-column grid matching the stage's 1fr 2fr 1fr — each child
   centers in its column so speed-A lands under deck-A, the mixer
   centers under the main canvas, and speed-B lands under deck-B
   regardless of how wide the mixer's content actually is. */
.controls-transport {
    grid-area: transport;
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    gap: var(--hf-space-3);
    align-items: center;
    min-width: 0;
}

.controls-transport > .control-group:first-child,
.controls-transport > .control-group:last-child {
    justify-content: center;
}
.controls-transport > .control-group { min-width: 0; }

/* Polymorphic-style status bar — single horizontal strip that unifies
   audio / midi / fps status pills + auto-VJ config + scenes / REC /
   output. Centered. Items separate visually via .status-divider
   spacers between logical groups. Falls back to wrap on narrow
   viewports rather than truncating. */
.status-bar {
    grid-area: status;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--hf-space-2);
    min-width: 0;
}

.status-divider {
    width: 1px;
    height: 20px;
    background: var(--hf-border-subtle);
    margin: 0 var(--hf-space-1);
}

.status-field {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.status-field select {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 3px 18px 3px 7px;
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
    text-transform: none;
    background-image:
        linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right 7px top 50%, right 3px top 50%;
    background-size: 4px 4px, 4px 4px;
    background-repeat: no-repeat;
}
.status-field select:hover  { border-color: var(--hf-accent); }
.status-field select:focus  { outline: none; border-color: var(--hf-accent); }

/* AUTO-VJ active state — bg fills with the app accent so the "on"
   state reads decisively against the muted status row. Default
   (data-state="off") inherits the plain .status-pill look. */
.status-automix[data-state="on"] {
    background: color-mix(in srgb, var(--hf-accent) 88%, var(--hf-bg-base));
    color: var(--hf-bg-base);
}
.status-automix[data-state="on"]:hover {
    background: var(--hf-accent);
}

.control-group { display: flex; align-items: center; gap: var(--hf-space-2); min-width: 0; }

/* Constrain the speed sliders so the bottom toolbar fits a 1280px viewport
   instead of stretching to whatever flex-grow gives them. */
#speed-a, #speed-b { flex: 0 0 100px; min-width: 0; }

.control-label {
    font-size: var(--hf-size-xs);
    letter-spacing: var(--hf-tracking-wide);
    text-transform: uppercase;
    color: var(--hf-text-dim);
    font-family: var(--hf-font-family-mono);
    white-space: nowrap;
    flex: 0 0 auto;
}

.control-value {
    font-size: var(--hf-size-xs);
    color: var(--hf-text-normal);
    font-family: var(--hf-font-family-mono);
    white-space: nowrap;
    flex: 0 0 auto;
    min-width: 40px;
    text-align: right;
}

/* Range-input styling for OUR direct sliders (speed A/B, crossfader,
   audio sensitivity). Scoped with :not(.slider) so the rules don't
   leak into the handfish <slider-value> custom element's internal
   input.slider — that one is owned by the handfish bundle's
   injected CSS and we don't override its track / thumb metrics.
   (Mixing our 22px / -8px-margin thumb with handfish's 14px track
   was why the mixer-controls knobs floated above the slider line.) */
input[type="range"]:not(.slider) {
    appearance: none;
    background: transparent;
    cursor: pointer;
}

input[type="range"]:not(.slider)::-webkit-slider-runnable-track {
    height: 6px;
    background: var(--hf-bg-base);
    border-radius: 3px;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
}
input[type="range"]:not(.slider)::-moz-range-track {
    height: 6px;
    background: var(--hf-bg-base);
    border-radius: 3px;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
}

input[type="range"]:not(.slider)::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 22px;
    height: 22px;
    border-radius: var(--hf-radius-full);
    background: var(--hf-text-bright);
    border: 2px solid var(--hf-bg-base);
    margin-top: -8px;
    cursor: grab;
    transition: background 0.1s ease;
}
input[type="range"]:not(.slider)::-moz-range-thumb {
    width: 22px;
    height: 22px;
    border-radius: var(--hf-radius-full);
    background: var(--hf-text-bright);
    border: 2px solid var(--hf-bg-base);
    cursor: grab;
}

input[type="range"]:not(.slider):hover::-webkit-slider-thumb { background: var(--hf-accent); }
input[type="range"]:not(.slider):active::-webkit-slider-thumb { cursor: grabbing; }

.big-toggle {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 8px 14px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-sm);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border), var(--hf-transition-bg);
    white-space: nowrap;
    flex: 0 0 auto;
}

.big-toggle:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }
.big-toggle[data-state="on"] {
    background: var(--hf-accent);
    color: var(--hf-bg-base);
    border-color: var(--hf-accent);
}

.automix-config {
    display: flex;
    flex-direction: column;
    gap: var(--hf-space-1);
    font-size: var(--hf-size-xs);
    font-family: var(--hf-font-family-mono);
    color: var(--hf-text-dim);
}

.automix-config label {
    display: flex;
    align-items: center;
    gap: var(--hf-space-1);
    white-space: nowrap;
}

.automix-config select {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 2px 6px;
    font-size: var(--hf-size-xs);
    font-family: inherit;
    cursor: pointer;
}

/* Crossfader (centerpiece). Lives in the middle column of the
   transport grid (1fr 2fr 1fr) and is constrained to that column's
   width so the speed sliders to either side stay centered under
   their decks rather than getting pushed to the column edges. */
.controls-center {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--hf-space-2);
    min-width: 0;
    width: 100%;
}

/* Mixer-effect picker: dropdown above the crossfader. Optional
   per-effect "mode" sub-dropdown appears next to it (e.g. blendMode's
   add/multiply/screen/…). Both styled to match the existing ghost
   buttons so the bottom strip feels uniform. */
.mixer-picker {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--hf-space-2);
}

.mixer-label {
    font-size: var(--hf-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--hf-text-dim);
    font-family: var(--hf-font-family-mono);
}

.mixer-select {
    appearance: none;
    background: var(--hf-bg-elevated);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 3px 22px 3px 8px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    letter-spacing: 0.04em;
    cursor: pointer;
    background-image:
        linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right 9px top 50%, right 5px top 50%;
    background-size: 4px 4px, 4px 4px;
    background-repeat: no-repeat;
}
.mixer-select:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }
.mixer-select:focus { outline: none; border-color: var(--hf-accent); }

.xfader-wrap {
    display: grid;
    grid-template-columns: 20px 1fr 20px;
    align-items: center;
    gap: var(--hf-space-3);
    width: 100%;
}

.xfader-label-a {
    color: var(--hf-blue);
    font-family: var(--hf-font-family-mono);
    font-weight: var(--hf-weight-bold);
    font-size: var(--hf-size-base);
    text-align: right;
}

.xfader-label-b {
    color: var(--hf-red);
    font-family: var(--hf-font-family-mono);
    font-weight: var(--hf-weight-bold);
    font-size: var(--hf-size-base);
}

.xfader {
    width: 100%;
    height: 32px;
    margin: 0;
}

.xfader::-webkit-slider-runnable-track {
    height: 24px;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
}
.xfader::-moz-range-track {
    height: 24px;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
}

.xfader::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 36px;
    height: 36px;
    border-radius: var(--hf-radius-sm);
    background: var(--hf-text-bright);
    border: var(--hf-border-width) solid var(--hf-bg-base);
    margin-top: -8px;
    cursor: grab;
}
.xfader::-moz-range-thumb {
    width: 36px;
    height: 36px;
    border-radius: var(--hf-radius-sm);
    background: var(--hf-text-bright);
    border: var(--hf-border-width) solid var(--hf-bg-base);
    cursor: grab;
}

/* Three-column layout so the cut/auto-fade button trio stays
   visually centered regardless of the fade controls flanking
   them. Left + right columns mirror each other (1fr each) and
   are sized to their content's max-width; the auto-width center
   column holds the buttons. */
.xfader-quick {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--hf-space-2);
}

.xfader-cuts {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
}

/* Left + right fade cells. justify-self hugs each cell against
   the center column so the curve dropdown and the duration slider
   sit immediately beside the cut/auto-fade button trio. Inner
   justify-content mirrors that pull (end vs start) so the
   controls — not the labels — are adjacent to the buttons. */
.xfader-fade {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
}
.xfader-fade-left  { justify-content: flex-start; }
.xfader-fade-right { justify-content: flex-end; }

.xfader-fade-label {
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    text-transform: uppercase;
    letter-spacing: var(--hf-tracking-wide);
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Compact fade curve dropdown. Fixed width matches the duration
   slider on the opposite side so the two affordances are exactly
   the same width — the user wants them visually balanced and
   "not wider than necessary". */
.xfader-fade-curve {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 3px 18px 3px 8px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    cursor: pointer;
    height: 26px;
    width: 110px;
    flex: 0 0 110px;
    background-image:
        linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right 7px top 50%, right 3px top 50%;
    background-size: 4px 4px, 4px 4px;
    background-repeat: no-repeat;
    transition: var(--hf-transition-border);
}
.xfader-fade-curve:hover { border-color: var(--hf-accent); }

/* Duration slider sizing. handfish gives <slider-value> a
   `display: contents` host, so width on the host itself is
   ignored — its children flow up into .xfader-fade's flex layout
   and stretch. Wrapping the element in a fixed-width flex box
   gives the children a 110px layout context that matches the
   curve dropdown opposite. */
.xfader-fade-duration-wrap {
    display: flex;
    align-items: center;
    gap: var(--hf-space-1);
    width: 110px;
    flex: 0 0 110px;
    min-height: 26px;
}

.quick-cut {
    appearance: none;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: var(--hf-space-1) var(--hf-space-3);
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border);
}

.quick-cut:hover { border-color: var(--hf-accent); color: var(--hf-text-bright); }
#cut-a:hover { color: var(--hf-blue); border-color: var(--hf-blue); }
#cut-b:hover { color: var(--hf-red); border-color: var(--hf-red); }

/* Record */
.rec-group { gap: var(--hf-space-2); }

.record-button {
    appearance: none;
    display: flex;
    align-items: center;
    gap: var(--hf-space-1);
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: var(--hf-space-1) var(--hf-space-3);
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    cursor: pointer;
    transition: var(--hf-transition-color), var(--hf-transition-border), var(--hf-transition-bg);
    white-space: nowrap;
    flex: 0 0 auto;
}

.record-dot {
    width: 10px;
    height: 10px;
    background: var(--hf-red);
    border-radius: var(--hf-radius-full);
    flex: 0 0 auto;
}

.record-button:hover { border-color: var(--hf-red); }
.record-button[data-state="on"] {
    background: var(--hf-red);
    color: white;
    border-color: var(--hf-red);
}

.record-button[data-state="on"] .record-dot {
    background: white;
    animation: blink 1s ease-in-out infinite;
}

@keyframes blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}

.record-time { color: var(--hf-text-dim); font-size: var(--hf-size-xs); }
.record-button[data-state="on"] .record-time { color: white; }

/* ─────────────── settings drawer ─────────────── */
.settings-drawer {
    position: fixed;
    top: 0;
    right: 0;
    width: 480px;
    height: 100vh;
    background: var(--hf-bg-surface);
    border-left: var(--hf-border-width) solid var(--hf-border-subtle);
    transform: translateX(100%);
    transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 100;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--hf-border-subtle) transparent;
}

.settings-drawer[aria-hidden="false"] { transform: translateX(0); }

.settings-drawer::-webkit-scrollbar { width: 6px; }
.settings-drawer::-webkit-scrollbar-thumb { background: var(--hf-border-subtle); border-radius: 3px; }

.settings-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--hf-space-4) var(--hf-space-5);
    border-bottom: var(--hf-border-width) solid var(--hf-border-subtle);
    position: sticky;
    top: 0;
    background: var(--hf-bg-surface);
    z-index: 1;
}

.settings-head h2 {
    font-family: var(--hf-font-family);
    font-size: var(--hf-size-lg);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    color: var(--hf-text-bright);
}

.settings-section {
    padding: var(--hf-space-4) var(--hf-space-5);
    border-bottom: var(--hf-border-width) solid var(--hf-border-subtle);
}

.settings-section h3 {
    font-size: var(--hf-size-xs);
    letter-spacing: var(--hf-tracking-wide);
    text-transform: uppercase;
    color: var(--hf-text-dim);
    margin-bottom: var(--hf-space-3);
    font-family: var(--hf-font-family-mono);
}

.settings-row {
    display: flex;
    align-items: center;
    gap: var(--hf-space-3);
    margin-bottom: var(--hf-space-3);
    flex-wrap: wrap;
}

.settings-row label:not(.checkbox-row) {
    font-size: var(--hf-size-sm);
    color: var(--hf-text-dim);
    min-width: 90px;
}

.settings-row select,
.settings-row input[type="number"],
.settings-row input[type="text"] {
    flex: 1 1 120px;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-normal);
    border-radius: var(--hf-radius-sm);
    padding: var(--hf-space-2) var(--hf-space-3);
    font-size: var(--hf-size-sm);
    font-family: inherit;
    outline: none;
}

.settings-row input[type="number"] { max-width: 80px; }
.settings-row select:focus,
.settings-row input:focus { border-color: var(--hf-accent); }

.settings-row input[type="range"] { flex: 1 1 120px; }

.checkbox-row {
    display: flex;
    align-items: center;
    gap: var(--hf-space-2);
    cursor: pointer;
    color: var(--hf-text-normal);
    font-size: var(--hf-size-sm);
}
.checkbox-row input { cursor: pointer; }

.settings-hint {
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    line-height: var(--hf-leading-normal);
    margin-top: var(--hf-space-1);
}

.settings-credit {
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    line-height: var(--hf-leading-normal);
    text-align: center;
}

.settings-credit a { color: var(--hf-accent); text-decoration: none; }
.settings-credit a:hover { text-decoration: underline; }

.shortcut-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--hf-space-2) var(--hf-space-3);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
}

kbd {
    display: inline-block;
    background: var(--hf-bg-base);
    color: var(--hf-text-normal);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    padding: 1px 5px;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    margin-right: 4px;
}

.midi-learn-list {
    margin-top: var(--hf-space-3);
    padding-top: var(--hf-space-3);
    border-top: var(--hf-border-width) dashed var(--hf-border-subtle);
}

.midi-learn-list h4 {
    font-size: var(--hf-size-xs);
    letter-spacing: var(--hf-tracking-wide);
    text-transform: uppercase;
    color: var(--hf-text-dim);
    margin-bottom: var(--hf-space-2);
}

.midi-learn-rows {
    display: flex;
    flex-direction: column;
    gap: var(--hf-space-1);
    margin-bottom: var(--hf-space-2);
}

.midi-learn-row {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: var(--hf-space-2);
    align-items: center;
    padding: 5px 8px;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    font-size: var(--hf-size-xs);
    font-family: var(--hf-font-family-mono);
}

.midi-learn-row.learning {
    border-color: var(--hf-accent);
    animation: pulse-soft 1s ease-in-out infinite;
}

@keyframes pulse-soft {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.6; }
}

.midi-learn-row .ml-target { color: var(--hf-text-normal); }
.midi-learn-row .ml-cc { color: var(--hf-accent); }

.midi-learn-row button {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--hf-text-dim);
    cursor: pointer;
    font-size: var(--hf-size-sm);
}
.midi-learn-row button:hover { color: var(--hf-red); }

/* ─────────────── scenes list ─────────────── */
.scenes-list { display: flex; flex-direction: column; gap: var(--hf-space-1); }

.scene-row {
    display: grid;
    grid-template-columns: 28px 1fr auto auto;
    align-items: center;
    gap: var(--hf-space-2);
    padding: 6px 10px;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-sm);
    font-size: var(--hf-size-sm);
    font-family: var(--hf-font-family-mono);
    cursor: pointer;
    transition: var(--hf-transition-border);
}

.scene-row:hover { border-color: var(--hf-accent); }

.scene-row .sr-key {
    color: var(--hf-accent);
    font-size: var(--hf-size-xs);
    text-align: center;
    background: var(--hf-bg-elevated);
    border-radius: var(--hf-radius-sm);
    padding: 1px 0;
}

.scene-row .sr-name {
    color: var(--hf-text-bright);
    font-weight: var(--hf-weight-semibold);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.scene-row .sr-meta { color: var(--hf-text-dim); font-size: var(--hf-size-xs); }
.scene-row .sr-actions { display: flex; gap: var(--hf-space-1); }

.scene-row .sr-actions button {
    appearance: none;
    background: transparent;
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-dim);
    border-radius: var(--hf-radius-sm);
    padding: 2px 8px;
    font-size: var(--hf-size-xs);
    font-family: inherit;
    cursor: pointer;
}

.scene-row .sr-actions button:hover { color: var(--hf-text-normal); border-color: var(--hf-border-hover); }
.scene-row .sr-delete:hover { color: var(--hf-red); border-color: var(--hf-red); }

.scenes-empty {
    color: var(--hf-text-dim);
    font-size: var(--hf-size-xs);
    text-align: center;
    padding: var(--hf-space-4);
}

/* ─────────────── toast ─────────────── */
.toast {
    position: fixed;
    bottom: calc(var(--controls-h) + var(--hf-space-4));
    left: 50%;
    transform: translateX(-50%) translateY(20px);
    background: var(--hf-bg-elevated);
    color: var(--hf-text-normal);
    padding: var(--hf-space-2) var(--hf-space-4);
    border-radius: var(--hf-radius-md);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    font-size: var(--hf-size-sm);
    font-family: var(--hf-font-family-mono);
    opacity: 0;
    pointer-events: none;
    transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 200;
    box-shadow: var(--hf-shadow-md);
}

.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ─────────────── boot overlay ─────────────── */
/* Backdrop blur + brightness reduction so the running app (decks + main
   canvas already rendering by the time this overlay shows) dims to a
   moody live-monitor behind the start card. The boot card itself keeps a
   solid surface so text stays legible. */
.boot-overlay {
    position: fixed;
    inset: 0;
    background: color-mix(in srgb, var(--hf-bg-base) 55%, transparent);
    backdrop-filter: blur(18px) brightness(0.45) saturate(0.85);
    -webkit-backdrop-filter: blur(18px) brightness(0.45) saturate(0.85);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    transition: opacity 0.4s ease, visibility 0.4s ease;
}

.boot-overlay.hidden { opacity: 0; visibility: hidden; pointer-events: none; }

.boot-card {
    text-align: center;
    max-width: 480px;
    padding: var(--hf-space-10);
    background: var(--hf-bg-surface);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    border-radius: var(--hf-radius-lg);
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
}

.boot-title {
    font-family: var(--hf-font-family);
    font-size: 56px;
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    margin-bottom: var(--hf-space-2);
    background: linear-gradient(90deg, var(--hf-blue), var(--hf-yellow), var(--hf-red));
    background-size: 200% 200%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: gradient-shift 6s ease-in-out infinite;
}

.boot-tagline {
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-sm);
    letter-spacing: var(--hf-tracking-wide);
    color: var(--hf-text-dim);
    margin-bottom: var(--hf-space-8);
}

.boot-detail {
    font-size: var(--hf-size-base);
    color: var(--hf-text-normal);
    line-height: var(--hf-leading-relaxed);
    margin-bottom: var(--hf-space-8);
}

.boot-button {
    appearance: none;
    background: var(--hf-accent);
    color: var(--hf-bg-base);
    border: none;
    border-radius: var(--hf-radius-md);
    padding: var(--hf-space-4) var(--hf-space-12);
    font-family: var(--hf-font-family);
    font-size: var(--hf-size-md);
    font-weight: var(--hf-weight-bold);
    letter-spacing: var(--hf-tracking-wide);
    cursor: pointer;
    transition: var(--hf-transition-bg);
}

.boot-button:hover { background: var(--hf-accent-hover); }

.boot-hint {
    margin-top: var(--hf-space-6);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    font-family: var(--hf-font-family-mono);
}

.boot-small-screen-warning {
    display: none;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-red);
    color: var(--hf-text-normal);
    border-radius: var(--hf-radius-md);
    padding: var(--hf-space-3);
    margin: 0 0 var(--hf-space-5);
    font-size: var(--hf-size-sm);
    font-family: var(--hf-font-family-mono);
    line-height: var(--hf-leading-normal);
}

/* ─────────────── fullscreen state ─────────────── */
#app.fullscreen-main .main {
    position: fixed;
    inset: 0;
    z-index: 50;
    border-radius: 0;
    border: none;
    background: black;
}

#app.fullscreen-main .main-head,
#app.fullscreen-main .main-fx { display: none; }

#app.fullscreen-main .main-canvas-wrap {
    height: 100vh;
    width: 100vw;
}

/* ─────────────── theme picker ─────────────── */
.hf-theme-select {
    flex: 1 1 auto;
    background: var(--hf-bg-base);
    border: var(--hf-border-width) solid var(--hf-border-subtle);
    color: var(--hf-text-normal);
    border-radius: var(--hf-radius-sm);
    padding: var(--hf-space-2) var(--hf-space-3);
    font-size: var(--hf-size-sm);
    font-family: inherit;
    outline: none;
    cursor: pointer;
}
.hf-theme-select:focus { border-color: var(--hf-accent); }

/* ─────────────── responsive ─────────────── */
@media (max-width: 1100px) {
    :root { --library-w: 220px; }
    .stage { grid-template-columns: 1fr 1.6fr 1fr; }
}

@media (max-width: 900px) {
    #app {
        grid-template-columns: 1fr;
        grid-template-rows: var(--topbar-h) auto 1fr auto;
        grid-template-areas:
            "top"
            "lib"
            "stage"
            "ctl";
    }
    .library { max-height: 120px; }
    .library-grid { grid-auto-flow: column; grid-auto-columns: 200px; grid-template-columns: none; }
    .stage { grid-template-columns: 1fr; grid-template-rows: 1fr 2fr 1fr; }
}

@media (max-width: 600px) {
    .boot-small-screen-warning { display: block !important; }
    .boot-title { font-size: 38px; }
}

/* ──────────────────────────────────────────────────────────────
   Mixer controls panel — noisedeck-style per-effect control rows
   rendered below the main canvas, one row per non-driver parameter
   of the active mixer effect. Two-column grid like noisedeck:
   label (fixed width) | control (handfish web component) flexing.
   ────────────────────────────────────────────────────────────── */
.mixer-controls {
    padding: var(--hf-space-3);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--hf-space-2) var(--hf-space-4);
    background: var(--hf-bg-elevated);
}

.mixer-controls[hidden] { display: none; }

/* 2-column row: label | control. Control is either a slider-value
   (which we flex inline so the value-display sits next to the
   slider, not below it on a new row) or a select-dropdown. */
.mixer-control-row {
    display: grid;
    grid-template-columns: 5.5em 1fr;
    align-items: center;
    gap: var(--hf-space-2);
    min-width: 0;
}

.mixer-control-label {
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--hf-text-dim);
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Flatten slider-value's stacked children (slider track + value-
   display) into a single horizontal row. min-height accommodates
   the 22px thumb so the knob centers vertically without getting
   clipped by the 14px-tall flex line height. */
.mixer-control-row slider-value {
    display: flex;
    align-items: center;
    gap: var(--hf-space-1);
    min-width: 0;
    width: 100%;
    min-height: 28px;
}
.mixer-control-row slider-value .slider,
.mixer-control-row slider-value input[type="range"] {
    flex: 1 1 auto;
    min-width: 0;
}

/* slider-value's input.slider uses handfish's own thumb + track
   metrics straight from the injected handfish CSS — no override
   needed here; the global `input[type="range"]:not(.slider)`
   selector keeps our custom thumb styling out of slider-value. */
.mixer-control-row slider-value .value-display {
    flex: 0 0 auto;
    min-width: 3em;
    font-family: var(--hf-font-family-mono);
    font-size: var(--hf-size-xs);
    color: var(--hf-text-dim);
    text-align: right;
    line-height: 28px;
}

/* Select-dropdown gets a visibly distinct fill from the panel bg
   so it reads as an interactive control rather than blending in.
   The dropdown MENU also needs a solid bg — when it opens over the
   live canvas its options were rendering with the canvas bleeding
   through, which read to the user as the content shifting upward
   into the dropdown space. */
.mixer-control-row select-dropdown {
    min-width: 0;
    width: 100%;
    background: var(--hf-bg-base);
    border-radius: var(--hf-radius-sm);
}
.mixer-control-row select-dropdown .inline-dropdown {
    background: var(--hf-bg-elevated);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
    border-radius: var(--hf-radius-sm);
}

.mixer-control-disabled {
    opacity: 0.4;
    pointer-events: none;
}

/* ──────────────────────────────────────────────────────────────
   Noisedeck visual-language overrides
   ──────────────────────────────────────────────────────────────
   With --hf-border-width:0 globally, all the .foo:hover {
   border-color: var(--hf-accent) } rules above become no-ops.
   This block re-establishes the missing affordances using the
   noisedeck pattern: bg-color contrast between layers + filled
   hover states + glow box-shadows where a stroke used to live.
*/

/* Section "heads" — elevated bg one tier above their parent panel.
   Replaces the now-zero `border-bottom: 1px solid border-subtle`
   that used to separate a header from its panel body. */
.panel-head,
.deck-head,
.library-footer,
.main-head,
.main-fx,
.deck-meta {
    background: var(--hf-bg-elevated);
}

/* The deck panel itself sits on bg-surface; head + meta become
   slightly elevated (above) for in-panel separation. Same idea for
   the main pane and its head/fx strips. */

/* Live deck indicator — colored glow border, blue for deck A and
   red for deck B (matches the .deck-label coloring). The pulse
   variant fires on each quarter-note beat (app.js toggles the
   .beat-pulse class on the live deck inside scheduler.onBeat());
   keyframes ramp the box-shadow from a saturated punch back to
   the steady-state glow over ~600ms.

   Two CSS custom props (--deck-glow-rgb, --deck-glow-rgb-hot) let
   the keyframe animate the same channel for both decks without
   duplicating the @keyframes. */
.deck.live {
    --deck-glow-base: color-mix(in srgb, var(--deck-glow-color) 70%, transparent);
    --deck-glow-soft: color-mix(in srgb, var(--deck-glow-color) 60%, transparent);
    box-shadow:
        0 0 0 1px var(--deck-glow-base),
        0 0 32px var(--deck-glow-soft);
}
.deck.deck-a.live { --deck-glow-color: var(--hf-blue); }
.deck.deck-b.live { --deck-glow-color: var(--hf-red); }

.deck.live.beat-pulse {
    animation: deck-beat-pulse 450ms ease-out;
}

/* Subtle pulse — small step up from the steady glow, decays back
   over 450ms. Gives a gentle quarter-note throb without dominating
   the screen. */
@keyframes deck-beat-pulse {
    0% {
        box-shadow:
            0 0 0 1px color-mix(in srgb, var(--deck-glow-color) 82%, transparent),
            0 0 42px color-mix(in srgb, var(--deck-glow-color) 72%, transparent);
    }
    100% {
        box-shadow:
            0 0 0 1px color-mix(in srgb, var(--deck-glow-color) 70%, transparent),
            0 0 32px color-mix(in srgb, var(--deck-glow-color) 60%, transparent);
    }
}

/* Hover bg-fills for interactive surfaces. Each rule lifts the
   element a step toward the accent so the hover reads even without
   a stroke. color-mix keeps the tint consistent across themes. */
.tap-button:hover,
.bpm-divider:hover,
.icon-button:hover,
button.status-pill:hover,
.deck-edit-toggle:hover,
.deck-density:hover,
.deck-load-random:hover,
.fx-button:hover,
.big-toggle:hover,
.quick-cut:hover,
.ghost-button:hover,
.mixer-select:hover,
.status-field select:hover {
    background: color-mix(in srgb, var(--hf-accent) 18%, var(--hf-bg-elevated));
    color: var(--hf-text-bright);
}

.record-button:hover {
    background: color-mix(in srgb, var(--hf-red) 18%, var(--hf-bg-elevated));
    color: var(--hf-text-bright);
}

/* "On" state pills (audio-on / midi-on / AUTO-VJ engaged) — was
   accent border, now accent fill. */
.status-pill[data-state="on"] {
    background: color-mix(in srgb, var(--hf-green) 22%, var(--hf-bg-elevated));
    color: var(--hf-green);
}
.status-pill[data-state="warn"] {
    background: color-mix(in srgb, var(--hf-red) 22%, var(--hf-bg-elevated));
    color: var(--hf-red);
}
button.status-pill[data-state="on"]:hover {
    background: color-mix(in srgb, var(--hf-green) 32%, var(--hf-bg-elevated));
}

/* Density-button manual override needs *some* visual cue — was a
   coloured border. Now a tinted bg + a 1px ring via box-shadow
   inset so it doesn't shift the layout. */
.deck-density[data-mode="manual"] {
    background: color-mix(in srgb, var(--hf-accent) 12%, var(--hf-bg-elevated));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hf-accent) 50%, transparent);
}

/* Program card hover — bg lift instead of border. */
.program-card:hover {
    background: color-mix(in srgb, var(--hf-accent) 8%, var(--hf-bg-base));
}

/* Library search + per-effect "mode" dropdowns: focus ring as a
   box-shadow so we don't need a border. */
#library-search:focus,
.mixer-select:focus,
.status-field select:focus,
.bpm-divider:focus {
    outline: none;
    box-shadow: inset 0 0 0 1px var(--hf-accent);
}
