/* The editor's stylesheet, on this repo's shared dark tool palette. The manual
   has its own (help.css) — it's a document, not this fixed-height application
   shell. */

/* Every color below is a var(--xxx) against this palette rather than a literal
   hex, so the whole UI recolors from one place. --accent is the one the user can
   change (theme.js's #theme-accent input in #top); the rest is a fixed, high-
   contrast dark palette. --bg-selected/--bg-currow are color-mix()es of --accent,
   so a picked-cell or current-row tint follows a new accent for free rather than
   needing a second set of hardcoded colors. panels/scope.js draws canvas pixels,
   which can't inherit a CSS variable -- it reads theme.js's getAccent() and
   mirrors --bg0 as a literal for its background fill. */
:root {
  --bg0: #0d0e11;    /* page background */
  --bg1: #1c1f25;    /* panel background */
  --bg2: #262a32;    /* card/button/input background */
  --bg3: #333944;    /* hover/active background */
  --border: #545c68;
  --border-dim: #383d46;
  --text: #f3f5f8;
  --text-dim: #b0b8c4;
  --text-dim2: #7d8492;
  --accent: #ff8a3d;
  --accent-text: #1a0f04;
  --bg-selected: color-mix(in srgb, var(--accent) 24%, var(--bg1));
  --bg-currow: color-mix(in srgb, var(--accent) 12%, var(--bg1));
  --bg-beat: color-mix(in srgb, white 5%, var(--bg1));
  color-scheme: dark;
}
* { box-sizing: border-box; }
body { margin: 0; display: flex; flex-direction: column; height: 100vh;
       font: 13px/1.4 system-ui, sans-serif; background: var(--bg0); color: var(--text); }

h3 { margin: 0 0 4px; font-size: 11px; text-transform: uppercase; color: var(--text-dim); }
.mono { font-family: monospace; color: var(--text-dim); }
.row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.spacer { flex: 1; }

button { background: var(--bg2); color: inherit; border: 1px solid var(--border); border-radius: 4px;
         padding: 4px 8px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
         font: inherit; }
button:hover { border-color: var(--accent); }
button:active { background: var(--bg3); }
button svg { flex-shrink: 0; }
input[type=checkbox] { accent-color: var(--accent); }
/* Custom sliders, because the native accent-color thumb (~16-20px) reads
   oversized on a track this short once dozens of them are packed into the
   instrument panel -- and Chromium ignores ::-webkit-slider-thumb sizing
   entirely while the control is still natively themed, so `appearance: none` on
   the *host* is what makes a thumb override take effect at all.

   Going custom gives up accent-color's free "filled up to the thumb" look, which
   instrument.js's paintFill() paints back as a background gradient on every
   value change. There is deliberately no ::-webkit-slider-runnable-track rule:
   without one the host's own box *is* the visual track, so the geometry the
   browser centers the thumb against is the same box paintFill() paints, and both
   engines need no re-alignment hack. That also means the host is 6px against a
   9px thumb, which is why the thumb visibly overhangs the track.

   The thumb is white rather than --accent so it reads as a neutral handle on top
   of the accent fill instead of disappearing into it, and the unfilled track is
   --bg0 (the page background) rather than --bg2, which is .instr-card's own
   color and would make a slider near zero invisible. instrument.js mirrors that
   --bg0 value as a literal, since a gradient stop can't read a CSS variable. */
input[type=range] { -webkit-appearance: none; appearance: none; height: 6px;
  background: var(--bg0); border-radius: 3px; cursor: pointer; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: 9px; height: 9px; border-radius: 50%; background: #fff; cursor: pointer; }
input[type=range]::-moz-range-thumb { width: 9px; height: 9px; border: none; border-radius: 50%; background: #fff; cursor: pointer; }
input[type=range]::-moz-range-track { height: 6px; background: transparent; border-radius: 3px; }

.theme-picker { color: var(--text-dim); font-size: 10px; }
.theme-picker input[type=color] { width: 28px; height: 22px; padding: 0; margin-left: 2px;
  border: 1px solid var(--border); border-radius: 4px; background: none; cursor: pointer; vertical-align: middle; }

/* Master volume control + horizontal peak meter in #top's transport bar.
   The meter shows live output level with a smooth decay (falls gradually
   rather than instantly) and color zones: green (safe, -18dB to -6dB),
   yellow (hot, -6dB to 0dB), red (clipping, above 0dB). The fill is a
   linear-gradient that transitions through those zones, so the meter's
   width directly shows which zone the peak landed in. */
.volume-control { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 10px; }
.volume-control input[type=range] { width: 80px; }
.level-meter { width: 120px; height: 6px; background: var(--bg0); border-radius: 3px;
  border: 1px solid var(--border-dim); overflow: hidden; position: relative; }
.level-meter-fill { height: 100%; width: 0%; border-radius: 2px; transition: width 0.05s ease-out;
  background: linear-gradient(to right,
    #34c759 0%, #34c759 50%,      /* green: -inf to -6dB */
    #ffcc00 50%, #ffcc00 83.33%,  /* yellow: -6dB to 0dB */
    #ff3b30 83.33%, #ff3b30 100%  /* red: 0dB+ */
  );
}

/* Full-width bars top and bottom (file/transport, instrument/keyboard);
   the tracks row between them is where the screen's real estate goes —
   sequencer + as many pattern+fx columns as fit, scrolling horizontally
   rather than being squeezed into a narrow center column. */
#top, #bottom { flex-shrink: 0; display: flex; align-items: center; gap: 16px;
                padding: 8px 10px; border-bottom: 1px solid var(--border-dim); overflow-x: auto; }
/* Fixed (not min-) height: the instrument panel has enough controls to grow
   #bottom indefinitely if allowed to size to content. A fixed height plus
   .panel's own overflow: auto below keeps #tracks' share of the screen
   predictable and pushes any overflow into an internal scrollbar. */
#bottom { border-bottom: none; border-top: 1px solid var(--border-dim); align-items: stretch; height: 320px; }
#top .title { font-size: 15px; text-transform: none; color: var(--text); letter-spacing: 0.5px;
              flex-shrink: 0; }

/* #tracks (sequencer + patterns, "the pattern view") is the only row without
   a fixed/JS-managed height, so it is also the only one that can shrink --
   and TRACKS_MIN below is what stops it shrinking all the way to nothing at a
   short viewport or high browser/OS zoom, which is what used to happen: with
   #keyboard-row and #bottom both flex-shrink: 0 (never shrink) and #tracks
   the sole flexible item, negative space landed entirely on #tracks with no
   floor. panels/layout.js mirrors this same 140px as TRACKS_MIN so its own
   resize-drag math agrees with what CSS will actually render. */
#tracks { flex: 1; display: flex; gap: 1px; background: var(--border-dim); overflow: auto; min-height: 140px; }

/* min-height: 0 counters the same flex intrinsic-sizing default that bit
   .instr-grid below (there for width, here for height): a flex item's
   min-height defaults to its content size, which can force .panel taller
   than #bottom's fixed height / #tracks' flex height and spill out past the
   box instead of activating this rule's own overflow: auto. */
.panel { background: var(--bg1); padding: 10px; overflow: auto; min-height: 0; }
#tracks .panel { flex: 1; min-width: 0; }
/* The sequencer always shows all engine.MAX_CHANNELS=16 columns (see
   panels/tracker.js), with its own internal horizontal scroll (.trk-scroll
   below) for anything past this width rather than growing further and starving
   #patterns-panel. */
#tracks #sequencer-panel { flex: 0 0 320px; display: flex; flex-direction: column; }
/* The panel itself doesn't scroll: its <h3> and each column's Ch/Pat header have
   to stay put while the pattern rows run under them, so the scroller is
   .pat-scroll inside it (the shape #sequencer-panel has with .trk-scroll).
   overflow: hidden so .panel's own auto doesn't add a second scrollbar around
   the whole thing. */
#tracks #patterns-panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
#bottom .panel { flex: 1; min-width: 0; }
/* The piano gets its own full-width row directly under #top, and its height
   comes from the fixed-height .kb-piano below rather than from a shared row, so
   it stays compact. */
#keyboard-row { flex-shrink: 0; display: flex; gap: 1px; background: var(--border-dim); border-bottom: 1px solid var(--border-dim); }
#keyboard-panel { flex: 1; min-width: 0; }
/* #bottom splits two thirds / one third at any window width: the instrument
   panel neither grows nor shrinks off 66% (`flex: 0 0 66%`), which is what keeps
   .instr-grid's four columns roomy enough for a 52px label + slider + valuetip
   each, and the scope takes the remainder (`flex: 1 1 0`). The 16px gap comes
   out of the scope's share, so it lands a touch under 34%. */
#bottom #instrument-panel { flex: 0 0 66%; }
#bottom #scope-panel { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
.panel.wip { display: flex; flex-direction: column; }
.wip-note { margin: auto; color: var(--text-dim2); font-size: 11px; text-align: center; max-width: 16em; }

/* Collapse toggle + drag handle for #keyboard-row and #bottom
   (panels/layout.js). #tracks in between them has no toggle of its own --
   see the #tracks comment above -- so it only ever grows by these two
   rows collapsing or shrinking, never by collapsing itself.
   position: relative on the rows lets .row-toggle sit absolutely in their
   corner without becoming a flex item that eats into the row's own layout. */
#keyboard-row, #bottom { position: relative; }
.row-toggle { position: absolute; top: 3px; right: 4px; z-index: 3; padding: 2px; display: flex; }
.row-toggle svg { transition: transform .15s; }
.row-collapsed .row-toggle svg { transform: rotate(180deg); }
/* !important because #bottom's own height: 320px rule below otherwise ties
   with this on specificity depending on source order -- collapsed always has
   to win. */
#keyboard-row.row-collapsed, #bottom.row-collapsed { height: 27px !important; }
.row-collapsed > .panel { display: none; }

.row-handle { flex-shrink: 0; height: 6px; cursor: row-resize; background: var(--border-dim); }
.row-handle:hover, .row-handle.dragging { background: var(--accent); }
/* visibility, not display: none -- collapsing a row hides its own handle
   (nothing to resize into a fixed collapsed strip) while keeping the layout's
   total handle height constant, which is what panels/layout.js's maxFor()
   assumes when computing how far the other row is allowed to grow. */
.row-handle.row-handle-hidden { visibility: hidden; pointer-events: none; }

/* sequencer + pattern/fx grids */
.trk-controls { margin: 4px 0 6px; }
/* Wide enough for 3 digits *plus* the spinner arrows (BPM's max is 1000, Rows'
   is 256); at 4em the last digit clips. */
.trk-controls input[type=number] { width: 5.5em; background: var(--bg2); color: inherit;
                                    border: 1px solid var(--border); border-radius: 4px;
                                    padding: 2px 4px; font: inherit; font-size: 11px; }
.trk-scroll { flex: 1; overflow: auto; min-height: 0; min-width: 0; }
.trk-table { border-collapse: collapse; font-family: monospace; font-size: 11px; white-space: nowrap; }
.trk-table th, .trk-table td { padding: 1px 4px; text-align: center; border: 1px solid var(--border-dim); }
.trk-table thead th { position: sticky; top: 0; background: var(--bg1); color: var(--text-dim);
                       font-weight: normal; z-index: 1; }
.trk-table thead th.focused { color: var(--accent); }
.trk-table tbody th { color: var(--text-dim2); font-weight: normal; text-align: right; position: sticky;
                       left: 0; background: var(--bg1); }
.trk-table tr.beat td, .trk-table tr.beat th { background: var(--bg-beat); }
.trk-table tr.curRow td, .trk-table tr.curRow th { background: var(--bg-currow); }
.trk-table td { color: var(--text); cursor: pointer; }
.trk-table td.trk-fx { color: var(--text-dim); border-left: 2px solid var(--border-dim); }
/* tr td.selected/.cursor (not the shorter .trk-table td.selected) so these
   beat a beat/curRow row's own "tr.beat td"/"tr.curRow td" background rule
   above -- same class+element count (2+2), and this way wins the tie by
   coming later in the file, instead of quietly losing to it by having one
   fewer element selector (a cursor cell on a beat/curRow row was rendering
   with the row's plain background, no visible highlight, until this was
   written the same "shape" as the rules it needs to beat). */
.trk-table tr td.selected { background: var(--bg-selected); }
.trk-table tr td.cursor { background: var(--accent); color: var(--accent-text); }

/* Pattern/fx cells reserve space for their widest possible content (a note like
   "C-4", an fx byte pair like "0F:3A") so columns don't visibly widen and narrow
   as cells go from empty to filled while editing. Scoped to #pat-scroll; the
   sequencer's cells are always 0-1 char. */
/* Keeps scrollCursorIntoView()/moveFollowRow()'s block: 'nearest'
   from parking the row it just scrolled to somewhere it can't be clicked --
   under the now-sticky .pat-col-head at the top (the header is painted over it
   and isn't a grid cell), or under this scroller's own always-present
   horizontal scrollbar at the bottom, which swallows presses over the lower
   half of a row flush with the bottom edge. Both selectors because the two
   callers scroll a <td> and a <tr> respectively. */
#pat-scroll .trk-table tr, #pat-scroll .trk-table td { scroll-margin: 18px 0 20px; }
#pat-scroll .trk-table td:not(.trk-fx) { min-width: 3ch; }
#pat-scroll .trk-table td.trk-fx { min-width: 5ch; }

.pat-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; gap: 10px; align-items: flex-start; }
.pat-col { flex: 0 0 auto; opacity: 0.55; }
.pat-col.focused { opacity: 1; }
.pat-col.no-pattern { opacity: 0.25; cursor: not-allowed; }
.pat-col.no-pattern * { pointer-events: none; }
/* Sticky inside .pat-scroll, so which channel and pattern a column
   is showing stays readable however far down the rows you've scrolled -- the
   same trick .trk-table thead th uses for the sequencer's channel numbers.
   z-index beats the cells it overlaps; the background stops them showing
   through it. */
.pat-col-head { position: sticky; top: 0; z-index: 2; background: var(--bg1);
                font-size: 10px; color: var(--text-dim); padding-bottom: 2px;
                cursor: pointer; white-space: nowrap; }
.pat-col.focused .pat-col-head { color: var(--accent); }

/* piano roll */
.pianoroll-header { display: flex; align-items: center; gap: 8px; padding-bottom: 4px; }
.pianoroll-header h3 { margin: 0; }
.pianoroll-header .hint { color: var(--text-dim); font-size: 10px; }
.pianoroll-header .spacer { flex: 1; }
.zoom-control { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 10px; }
.zoom-control input[type=range] { width: 60px; }
#pianoroll-canvas { width: 100%; height: 100%; display: block; cursor: crosshair; }

/* view mode toggle */
.view-toggle { display: flex; align-items: center; gap: 4px; color: var(--text-dim); font-size: 10px; }
.view-toggle select { background: var(--bg2); color: inherit; border: 1px solid var(--border);
                       border-radius: 4px; padding: 2px 5px; font: inherit; font-size: 11px; }

/* instrument panel */
#instrument-panel { display: flex; flex-direction: column; gap: 6px; }
.instr-header { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.instr-header h3 { margin: 0; }
.instr-header label { display: flex; align-items: center; gap: 4px; color: var(--text-dim); font-size: 10px; }
.instr-header select { background: var(--bg2); color: inherit; border: 1px solid var(--border);
                        border-radius: 4px; padding: 2px 5px; font: inherit; font-size: 11px; }
/* Grid, not flex-wrap: a flex row's items default to min-width: auto, which
   floors a shrinking <input type=range> at its native intrinsic width
   (~130px in Chromium) rather than the 100px this layout wants -- every
   card ended up stretched to fit that oversized row, so only 2 fit per
   line where 3+ should. Grid's minmax(…, 1fr) sizes columns from the track
   itself, sidestepping the per-item min-width floor entirely.
   A flat repeat(4, 1fr) rather than auto-fill: there are exactly four
   .instr-card elements (Osc, Noise/Arpeggio, Envelope/LFO, FX) and this
   guarantees them one row of four equal columns, where auto-fill can wrap the
   last card onto a second row that overflows #bottom's fixed height.
   #instrument-panel is sized to give each of the four room (see #bottom
   #instrument-panel below).

   flex: 1/min-height: 0 lets the grid claim leftover height in
   #instrument-panel rather than sizing to its content; grid-auto-rows:
   minmax(min-content, 1fr) hands that height to the rows instead of leaving it
   blank below the grid; and align-items at its stretch default is what makes
   each .instr-card's background fill its row rather than hugging its own
   possibly-shorter content. */
.instr-grid { display: grid; grid-template-columns: repeat(4, 1fr);
              grid-auto-rows: minmax(min-content, 1fr); gap: 8px; align-content: start;
              overflow: auto; flex: 1; min-height: 0; }
.instr-card { background: var(--bg2); border: 1px solid var(--border-dim); border-radius: 6px;
              padding: 6px 8px; min-width: 0; }
/* Osc1/Osc2, Noise/Arpeggio and Envelope/LFO each share one .instr-card as two
   stacked .instr-sub sections -- see instrument.js's comment above
   initInstrumentPanel(). */
.instr-sub + .instr-sub { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border-dim); }
/* The FX card has twice as many sliders as any other, but it is still one
   column of them, so it needs no rule of its own and .fx-sliders is just a
   wrapper. Spanning it across two grid tracks with an internal 2-column
   sub-grid to stay square is tempting and does not work: it pushes the card
   onto a second row, which overflows #bottom's fixed height. */
.instr-card h4 { margin: 0 0 4px; font-size: 10px; text-transform: uppercase; color: var(--text-dim); }
.ctl-row { display: flex; align-items: center; gap: 6px; margin: 1px 0; }
.ctl-row label { width: 52px; flex-shrink: 0; color: var(--text-dim); font-size: 10px; }
.ctl-row input[type=range] { flex: 1; min-width: 0; }
.ctl-row .valuetip { width: 2.2em; text-align: right; font-family: monospace;
                      color: var(--text-dim); font-size: 10px; }
.ctl-check { display: flex; align-items: center; gap: 6px; margin-top: 2px;
             font-size: 10px; color: var(--text-dim); }
.icon-group { display: flex; gap: 2px; }
.icon-group button { padding: 2px; display: flex; }
.icon-group button svg { width: 14px; height: 14px; }
.icon-group button.active { color: var(--accent); border-color: var(--accent); }

/* On-screen piano keyboard. The engine's full octave range (1-8 => 56 white + 40
   black keys, 96 in all), laid out as percentages of the panel's width (see
   panels/keyboard.js) so it fills whatever width the window gives it.
   .kb-label holds up to two stacked spans: the bottom-row (ZXCVB...) and
   QWERTY-row physical keys both currently mapped to that note (they overlap
   across a 5-note span -- see panels/keyboard.js's BOTTOM_ROW comment), both
   shown rather than one hiding the other. Re-anchored to whichever notes
   tracker.js's NOTE_KEYS currently maps in refreshKeyboardPanel() every time
   the octave selector moves. The key colors themselves are literal (real
   piano keys are white/black regardless of UI theme), not var(--bg*). */
/* padding-right clears #keyboard-row's .row-toggle (panels/layout.js),
   absolutely positioned in the row's top-right corner -- without it the
   octave buttons at this header's own right end sit directly under it. */
.kb-header { margin-bottom: 2px; gap: 10px; padding-right: 28px; }
.kb-header h3 { margin: 0; }
.kb-header button { padding: 2px 9px; }
.kb-header .mono { min-width: 1.2em; text-align: center; }
/* The note-input controls (scale / root transpose / chord + its voicing
   toggles), sharing the header with the octave buttons. .off is the "this setting has nothing to act on right now"
   state -- dimmed rather than disabled, so it still reads at a glance. */
.kb-header label, .kb-header .kb-root, .kb-header .kb-follow, .kb-header .kb-voicing {
  display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-dim); }
.kb-header .kb-voicing { gap: 7px; }
.kb-header .kb-voicing label { gap: 3px; }
.kb-header select { background: var(--bg2); color: inherit; border: 1px solid var(--border);
                    border-radius: 4px; padding: 2px 4px; font: inherit; font-size: 11px; }
.kb-header .off { opacity: 0.4; }
/* The edit-step field. Narrower than .trk-controls' number inputs
   (its max is 16, two digits) but otherwise the same control. */
.kb-header input[type=number] { width: 3.6em; background: var(--bg2); color: inherit;
                                border: 1px solid var(--border); border-radius: 4px;
                                padding: 2px 4px; font: inherit; font-size: 11px; }
.kb-chord-name { color: var(--accent); min-width: 4.5em; font-size: 11px; }
.kb-follow-name { color: var(--accent); min-width: 3.2em; font-size: 11px; }
/* The chord pads (panels/keyboard.js, data in chords.js): one button per chord
   in the selected flavor, on their own row between the header and the piano.
   They share the row's width equally (flex: 1 1 0) and shrink rather than wrap
   -- a pad that has moved to a second row no longer sits where the digit
   printed on it says it does, and the digit row is the point of them.
   `hidden` on the unused slots rather than not rendering them: the buttons are
   built once and relabelled, so a flavor change never replaces an element the
   pointer is on (see panels/keyboard.js). */
.kb-chords { gap: 10px; margin-bottom: 3px; flex-wrap: nowrap; }
.kb-chords > label { display: flex; align-items: center; gap: 5px;
                     font-size: 10px; color: var(--text-dim); white-space: nowrap; }
.kb-chords select { background: var(--bg2); color: inherit; border: 1px solid var(--border);
                    border-radius: 4px; padding: 2px 4px; font: inherit; font-size: 11px; }
.kb-pads { display: flex; gap: 4px; flex: 1; min-width: 0; }
.kb-pad { position: relative; flex: 1 1 0; min-width: 0; padding: 3px 4px 4px;
          display: flex; flex-direction: column; align-items: center; justify-content: center;
          border-radius: 5px; cursor: pointer; overflow: hidden; }
/* Explicit, because the display above outranks the user-agent's
   [hidden] { display: none } -- without this the slots past the flavor's pad
   count draw as empty boxes. */
.kb-pad[hidden] { display: none; }
.kb-pad:active { background: var(--accent); }
/* The chord's name is what you read; the roman numeral is what you learn from.
   Both truncate rather than wrap -- two lines is the pad's whole height. */
.kb-pad b { font-size: 12px; font-weight: 600; line-height: 1.25;
            max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-pad i { font-style: normal; font-size: 9px; line-height: 1.25; color: var(--text-dim2);
            max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-pad kbd { position: absolute; top: 0; right: 3px; font: inherit; font-size: 8px;
              color: var(--text-dim2); }
.kb-pad:active b, .kb-pad:active i, .kb-pad:active kbd { color: var(--accent-text); }
/* The pads that tend to follow the one just played (chords.js's rankNext).
   An accent bar along the top edge rather than a border color: the generic
   button:hover already owns the border, and a suggestion that looked like a
   hover would read as the pointer being somewhere it isn't. */
.kb-pad.suggest { box-shadow: inset 0 2px 0 var(--accent); }
.kb-pads-hint { font-size: 10px; color: var(--text-dim2); align-self: center; }
.kb-chords .kb-smooth, .kb-chords .kb-shape { display: flex; align-items: center; gap: 4px;
                        font-size: 10px; color: var(--text-dim); white-space: nowrap; }
.kb-chords .off { opacity: 0.4; }
.kb-piano { position: relative; height: 68px; }
.kb-white { position: absolute; top: 0; height: 65px; box-sizing: border-box; background: #dfe1e6;
            border: 1px solid #111318; border-radius: 0 0 2px 2px; cursor: pointer; }
/* Three signals can land on one key at once, so each gets its own visual
   channel: the active scale tints the key (a fixed cool blue -- deliberately
   *not* derived from --accent, which is what a struck/sounding key uses), a
   hovered chord's
   notes get an outline rather than a fill, and a lit key fills with the
   accent. The tint rules come before .active so a struck key still wins the
   fill at equal specificity; .kb-shadow is an outline, so it never competes
   for one. */
.kb-white.kb-in-scale { background: #9fc4e8; }
.kb-white.active { background: var(--accent); }
.kb-black { position: absolute; top: 0; height: 40px; z-index: 1; box-sizing: border-box;
            background: #14161b; border: 1px solid #000; border-radius: 0 0 2px 2px;
            cursor: pointer; }
.kb-black.kb-in-scale { background: #2f5476; }
/* The chord being followed (panels/tracker.js's followedChord), a step
   stronger than the scale tint it sits on top of -- same hue family, so the
   two read as "in the key" and "in the chord" rather than as two unrelated
   states. After the .kb-in-scale rules so it wins at equal specificity, and
   still before .active, which a struck key needs to win outright. */
.kb-white.kb-in-chord { background: #57a0e2; }
.kb-black.kb-in-chord { background: #1d6bb0; }
.kb-black.active { background: var(--accent); }
/* The chord a hovered key would enter, previewed on its own
   notes (panels/keyboard.js's showShadow). An inset ring plus a soft glow --
   it has to stay readable on top of both the scale tint and the accent fill,
   which is why it takes neither.
   No z-index of its own: raising a shadowed key would lift white keys over
   the black ones that overlap them (.kb-black's z-index: 1 is the whole
   reason the keyboard looks like a keyboard), so a shadowed white key stays
   under its neighbouring black keys and only a shadowed *black* key gets
   lifted -- above the white keys it already sits on top of, nothing else. */
.kb-shadow { box-shadow: inset 0 0 0 2px #c07bff, 0 0 7px rgba(192, 123, 255, 0.55); }
.kb-black.kb-shadow { z-index: 2; }
.kb-label { position: absolute; bottom: 2px; left: 0; right: 0; display: flex; flex-direction: column;
            text-align: center; font-size: 7px; line-height: 1.3; pointer-events: none; }
.kb-white .kb-label { color: #111; }
.kb-black .kb-label { color: #eee; }

/* Oscilloscope column. No fixed height -- the canvas fills whatever #bottom
   #scope-panel's flex column leaves after the header, and panels/scope.js's
   initScopePanel() reads its clientWidth/clientHeight after layout to size the
   backing bitmap to match, so the box stays crisp instead of stretching a small
   bitmap. #scope-canvas's background is var(--bg0); scope.js's own BG constant
   is a literal copy of that value for its fillRect calls, since canvas pixels
   can't inherit a CSS variable. */
/* padding-right clears #bottom's .row-toggle the same way .kb-header does
   above -- #scope-panel is #bottom's rightmost child, so its header's own
   #scope-toggle button is the one that would otherwise sit under it. */
.scope-header { margin-bottom: 2px; padding-right: 28px; }
.scope-header h3 { margin: 0; }
#scope-canvas { width: 100%; flex: 1; min-height: 0; background: var(--bg0); border: 1px solid var(--border-dim); border-radius: 2px; }

/* Startup audio-unlock gate. Sits above #picker, which is only ever open later,
   after the gate is gone. */
#audio-gate { position: fixed; inset: 0; background: rgba(0,0,0,.85);
              display: grid; place-items: center; z-index: 10; }
#audio-gate.closed { display: none; }
#audio-gate-panel { background: var(--bg1); border: 1px solid var(--border); border-radius: 6px;
                     padding: 20px 26px; text-align: center;
                     display: flex; flex-direction: column; gap: 10px; align-items: center; }
#audio-gate-panel p { color: var(--text-dim); margin: 0; }
/* The app's wordmark: text rather than an image, with wide letter-spacing doing
   the work a logotype would. White, not accented -- the accent is user-adjustable
   (theme.js), and this is the one piece of chrome that shouldn't change identity
   with the theme. Lowercase via text-transform, not a lowercased string, so
   copy/paste and the accessibility tree still get the real name. */
#audio-gate-title { margin: 0; font-size: 34px; font-weight: 700; letter-spacing: 4px;
                    text-transform: lowercase; color: #fff; }
#audio-gate-panel .gate-sub { font-size: 11px; max-width: 26em; margin-bottom: 6px; }
#audio-gate-panel .gate-sub a { color: var(--text-dim); }
#audio-gate-panel .gate-sub a:hover { color: var(--accent); }

#picker { position: fixed; inset: 0; background: rgba(0,0,0,.6);
          display: none; place-items: center; z-index: 9; }
#picker.open { display: grid; }
#picker-panel { background: var(--bg1); border: 1px solid var(--border); border-radius: 6px;
                padding: 14px; max-width: 90vw; max-height: 82vh;
                display: flex; flex-direction: column; gap: 10px; }
/* #picker-grid/.pick-card style the open dialog's built-in song library
   (tools/soundbox/songs/), split into Songs/SFX sections. .pick-section is a
   sticky header inside the same scrolling column, so the section a card belongs
   to stays visible while scrolling a long list. max-height keeps a 30-plus-entry
   library from pushing the import controls off the bottom of the panel --
   #picker-panel's own max-height: 70vh has to cover the hint and buttons too. */
#picker-grid { display: flex; gap: 16px; overflow: auto;
               max-width: 86vw; max-height: 64vh; }
.pick-col { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 340px; }
.pick-section { position: sticky; top: 0; z-index: 1; background: var(--bg1);
                padding: 4px 0 2px; font-size: 10px; text-transform: uppercase;
                letter-spacing: 0.5px; color: var(--accent); }
.pick-card { display: flex; flex-direction: column; gap: 2px; padding: 6px 8px;
             border: 1px solid var(--border-dim); border-radius: 4px; cursor: pointer; color: var(--text-dim); }
.pick-card:hover { border-color: var(--accent); color: var(--text); }
.pick-card .name { color: var(--text); font-weight: 600; }
.pick-card .desc { font-size: 11px; }
/* Import affordance/trust note in the open dialog. */
#picker-body .hint { margin: 0; max-width: 52ch; font-size: 11px; color: var(--text-dim); line-height: 1.5; }
#picker-body .hint b { color: var(--text); font-weight: 600; }
/* The render-progress dialog (main.js's progressModal). The bar is
   a plain filled div rather than a <progress> element -- <progress> is styled
   through vendor-prefixed pseudo-elements that differ per engine, and this one
   has to match the accent color everything else in the tool follows. */
.gen-row { display: flex; align-items: center; gap: 8px; }
.gen-track { flex: 1; min-width: 32ch; height: 8px; background: var(--bg2);
             border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.gen-track > div { height: 100%; width: 0; background: var(--accent); transition: width .1s linear; }
#gen-pct { font-size: 11px; color: var(--text-dim); min-width: 4ch; text-align: right; }

/* The share dialog's link field. Wide textarea to show most of an 8000-character
   data URL without excessive scrolling. */
#share-url { width: 100%; min-width: 700px; background: var(--bg2); color: var(--text-dim);
             border: 1px solid var(--border); border-radius: 4px; padding: 6px 8px;
             font-family: monospace; font-size: 10px; line-height: 1.4;
             resize: vertical; word-break: break-all; }

/* Drag-and-drop overlay -- see index.html's #drop-hint. Tops the
   stack (above #picker, since a song can be dropped while the open dialog is
   up, and above #audio-gate, since importing needs no audio). `pointer-
   events: none` matters: the overlay appears on the dragenter it would
   otherwise swallow, and a covered element fires an immediate dragleave --
   which would flicker the overlay straight back off. */
#drop-hint { position: fixed; inset: 0; background: rgba(0,0,0,.75); pointer-events: none;
             display: none; place-items: center; z-index: 11; }
#drop-hint.open { display: grid; }
#drop-hint-panel { background: var(--bg1); border: 2px dashed var(--accent); border-radius: 6px;
                   padding: 20px 26px; text-align: center; }
#drop-hint-panel p { color: var(--text-dim); margin: 6px 0 0; font-size: 12px; }

/* Generic popup menu (menu.js), used for the pattern grid's
   right-click transpose menu. Overlay + positioned panel: the full-viewport
   layer is what catches the click that dismisses it. */
.menu-overlay { position: fixed; inset: 0; z-index: 11; }
.menu { position: absolute; min-width: 200px; padding: 4px; background: var(--bg1);
        border: 1px solid var(--border); border-radius: 6px;
        box-shadow: 0 6px 20px rgba(0, 0, 0, .5); display: flex; flex-direction: column; }
.menu-item { justify-content: space-between; gap: 18px; background: none; border: none;
             border-radius: 4px; padding: 5px 8px; font-size: 12px; text-align: left;
             color: var(--text); }
.menu-item:hover { background: var(--bg3); border-color: transparent; }
.menu-hint { color: var(--text-dim2); font-size: 10px; font-family: monospace; }
.menu-item:hover .menu-hint { color: var(--text-dim); }
.menu-sep { height: 1px; margin: 4px 2px; background: var(--border-dim); }

/* Channel enable/disable toggles in sequencer header (piano roll mode) */
#seq-thead th.channel-toggleable { cursor: pointer; user-select: none; }
#seq-thead th.channel-toggleable:hover { background: var(--bg3); }
#seq-thead th.channel-disabled { opacity: 0.35; text-decoration: line-through; }
