/*
 * app.css — the M04 front panel: the header, the transport strip, the step grid, the
 * MIXER, the Timing Lab, the PATTERN BAY, the SONG BAY, the PROJECT BAY, the tour
 * chapter picker and the manual modal. base.css owns the tokens and the reusable
 * control shapes; everything here is layout and the pieces specific to this
 * milestone. Later milestones append to this file, they do not rewrite it.
 *
 * M03 added one panel and moved nothing. The grid gained a cursor state and a silenced
 * state on its rows; everything else was exactly as M02 left it, which is why eight
 * tracks appearing in the rack cost this file four lines.
 *
 * M04 appends four blocks and still moves nothing — see "patterns, song and project"
 * below. Two of them are worth knowing about before you read them:
 *
 *   .patbar is `repeat(auto-fit, minmax(54px, 1fr))` rather than sixteen fixed
 *   columns, and that is not taste. Sixteen fixed pads overflow the panel on a
 *   1366 x 768 laptop, an overflowing panel pushes the whole page sideways, and gate
 *   assertion 9 is exactly that check: scrollWidth must not exceed clientWidth. The
 *   bar wraps to two rows instead, and nothing else on the page moves.
 *
 *   .tourpicker is position:fixed at --z-modal because the tour SPOTLIGHTS it: it has
 *   to sit inside the hole the spotlight cuts rather than under the dim. It is a
 *   panel and not a modal — DM.Manual owns the only focus trap in DRUMLAB, and a
 *   second one would fight it into an infinite focus loop.
 */

.app {
  max-width: 1240px;
  margin: 0 auto;
  padding: 18px 20px 26px;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

/* ------------------------------------------------------------------ header */

.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
}

.brand { display: flex; align-items: center; gap: 9px; }

/* The lit power lamp: the one place the accent colour is allowed to just sit there. */
.brand__mark {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ffb199 0%, var(--accent) 55%, #7a1c0c 100%);
  box-shadow: 0 0 12px rgba(255, 90, 60, .65);
}

.brand__name {
  font: 700 13px/1 var(--font);
  letter-spacing: .28em;
  color: var(--muted);
}

.topbar__title {
  font: 600 17px/1 var(--font-ui);
  letter-spacing: .01em;
  padding-left: 14px;
  border-left: 1px solid var(--edge);
}

.topbar__progress {
  padding: 4px 9px;
  border: 1px solid var(--edge);
  border-radius: 999px;
  font: 11px/1 var(--font);
  color: var(--muted);
}

.topbar__actions { margin-left: auto; display: flex; gap: 8px; }

/* --------------------------------------------------------------- transport */

.transport {
  display: flex;
  align-items: stretch;
  gap: 18px;
  padding: 12px 16px;
  flex-wrap: wrap;
}

.unit { display: flex; flex-direction: column; gap: 7px; }
.unit + .unit { padding-left: 18px; border-left: 1px solid var(--edge); }
.unit__body { display: flex; align-items: center; gap: 8px; }
.unit--tempo { flex: 1 1 300px; min-width: 280px; }
.unit--tempo .slider { flex: 1 1 auto; min-width: 110px; }
.unit--swing { flex: 1 1 240px; min-width: 220px; }
.unit--swing .slider { flex: 1 1 auto; min-width: 96px; }

/* TAP is a momentary button, so it is short and square-ish rather than a label with
   a verb on it — you hit it in time with the music, you do not read it. */
.btn--tap {
  padding: 9px 12px;
  font: 700 12px/1 var(--font);
  letter-spacing: .12em;
}

/* Label beside the field instead of above it: "Steps 16" reads as one control. */
.field--inline { flex-direction: row; align-items: center; gap: 8px; }
.field__input--tiny { width: 58px; text-align: center; }

/* Swing off is not a fault, so the readout only lights up once it is engaged. */
.lcd__value.is-engaged { color: var(--warn); }

/* Play latches: while the transport runs, the button reads as pressed-in. */
.btn--play.is-active {
  background: linear-gradient(180deg, #ff7a5f 0%, #d33c22 100%);
  border-color: #8f2a17;
  color: #140603;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, .45), 0 0 16px rgba(255, 90, 60, .35);
}
.btn--warn { border-color: rgba(255, 204, 68, .45); color: var(--warn); }

/* ------------------------------------------------------------- step grid */

.rack { padding: 12px 16px 16px; }

.rack__head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}
.rack__title {
  font: 600 12px/1 var(--font);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
}
.rack__hint { font-size: 12px; color: var(--muted); }
.rack__hint strong { color: var(--text); font-weight: 600; }
/* The pattern bay's hint is rewritten from state (it says what a click will do RIGHT
   now), so its emphasis cannot be markup. The whole clause after the last separator is
   the answer, and it is the sentence's only job — so the line as a whole reads at full
   strength rather than one bold fragment inside a dim one. */
.patbay .rack__hint { color: var(--text); }

.grid { display: flex; flex-direction: column; gap: 6px; }

/*
 * One row = one track. The head is fixed width so every row's pads line up; the
 * pad strip is the flexible part and is the only thing allowed to scroll
 * horizontally on a narrow screen (min-width:0 is what makes that work inside a
 * flex row — without it the strip refuses to shrink and the PAGE scrolls instead).
 */
.row { display: flex; align-items: center; gap: 12px; }

.row__head {
  flex: 0 0 176px;
  min-width: 0;              /* without this a long name widens ONE row's head and
                                knocks that row's pads out of line with the others */
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: rgba(0, 0, 0, .22);
  border: 1px solid var(--edge);
  border-radius: 8px;
}

.row__lamp {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--track-hue);
  box-shadow: 0 0 8px var(--track-hue);
  flex: 0 0 auto;
}

.row__name {
  font: 600 12px/1.2 var(--font-ui);
  flex: 1 1 auto;
  min-width: 0;              /* a flex item's default min-width is its content, so
                                text-overflow never fires until you say this */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row__voice {
  font: 10px/1 var(--font);
  color: var(--muted);
  letter-spacing: .06em;
}

/* Audition: hear one voice on its own, without running the sequencer. */
.row__audition {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: rgba(255, 255, 255, .04);
  color: var(--muted);
  font-size: 9px;
  cursor: pointer;
}
.row__audition:hover { color: var(--track-hue); border-color: var(--track-hue); }

.row.is-selected .row__head { border-color: var(--track-hue); }

.row__steps,
.ruler__steps {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(16, minmax(28px, 1fr));
  gap: 5px;
  overflow-x: auto;
  padding-bottom: 2px;
}

/*
 * The beat marker. Every fourth column gets a rule down the gap to its left, so the
 * eye can count in fours without reading the ruler. It is drawn as a pseudo-element
 * in the GAP rather than as a border on the pad, because a border would eat into the
 * pad's rounded corner and shift the column by a pixel. Column 1 is skipped: the
 * panel edge already marks the start of the bar.
 */
.cell.is-beat:not(:first-child)::before,
.ruler__tick.is-beat:not(:first-child)::before {
  content: '';
  position: absolute;
  left: -3px;
  top: -3px;
  bottom: -3px;
  border-left: 1px solid rgba(138, 144, 166, .6);
}
.ruler__tick { position: relative; }
.ruler__tick.is-beat { color: var(--text); }

/*
 * A pad. Off = unlit rubber. On = backlit in the track's hue. Playing = the
 * column under the playhead, which brightens whether or not the pad is lit, so a
 * silent step still shows you where the bar is.
 */
.cell {
  position: relative;
  height: 34px;
  padding: 0;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: linear-gradient(180deg, #232735 0%, #171a24 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
  cursor: pointer;
  transition: background-color .08s linear, box-shadow .08s linear;
}
.cell:hover { background: linear-gradient(180deg, #2c3143 0%, #1d212d 100%); }

.cell.on {
  background: var(--track-hue);
  border-color: var(--track-hue);
  box-shadow: 0 0 14px -2px var(--track-hue), inset 0 1px 0 rgba(255, 255, 255, .4);
}

/*
 * Accent. A brighter pad would be ambiguous next to the playhead's brightening, so
 * an accent also gets a hard white bar across its top edge: level is carried by the
 * glow, and the marker is what you actually scan for when you read a pattern.
 */
.cell.accent {
  box-shadow: 0 0 18px -1px var(--track-hue), inset 0 1px 0 rgba(255, 255, 255, .6);
  filter: brightness(1.18) saturate(1.08);
}
.cell.accent::after {
  content: '';
  position: absolute;
  left: 5px;
  right: 5px;
  top: 3px;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .92);
}

/*
 * Past the pattern length. The pad keeps its hit and its colour, it is simply never
 * reached — so it dims rather than clearing, and setting the length back to 16
 * brings the whole bar straight back.
 */
.cell.is-beyond,
.ruler__tick.is-beyond { opacity: .26; }
.cell.is-beyond { cursor: pointer; }

.cell.playing {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
}
.cell.on.playing {
  filter: brightness(1.45) saturate(1.1);
  box-shadow: 0 0 20px 1px var(--track-hue), inset 0 0 0 1px #fff;
}
.cell.on.accent.playing {
  filter: brightness(1.6) saturate(1.15);
  box-shadow: 0 0 26px 2px var(--track-hue), inset 0 0 0 1px #fff;
}

/* The ruler carries the step numbers and the same beat marks as the pads. */
.ruler { display: flex; align-items: center; gap: 12px; margin-bottom: 2px; }
.ruler__spacer { flex: 0 0 176px; }
.ruler__tick {
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 10px/1 var(--font);
  color: #565c72;
}
.ruler__tick.playing { color: var(--accent); }

/*
 * The step cursor (new in M03). Arrow keys move it and it takes DOM focus with it, so
 * Space then places a hit purely because the pad is a focused <button> — no key had
 * to be stolen from the transport. The dashed ring is what tells you the keyboard is
 * pointing here even when the pad is lit.
 */
.cell.is-cursor {
  outline: 2px dashed var(--accent-2);
  outline-offset: 1px;
}

/*
 * A row whose track cannot be heard right now — muted, or not soloed while another
 * track is. It is dimmed rather than hidden: you still need to edit a muted part.
 *
 * The dim is applied to the LABELS and the PADS, never to the containers holding
 * them. `opacity` composites the whole subtree, outline included, so dimming
 * `.row__head` would render the :focus-visible ring at 38 % as well — and on a pad
 * past the loop end the two would multiply to about 10 %, on exactly the row a
 * keyboard user is most likely to be working. The audition button keeps full
 * strength for the same reason: a silenced track is still one you can hear on its own.
 */
.row.is-silent .row__lamp,
.row.is-silent .row__name,
.row.is-silent .row__voice,
.row.is-silent .cell { opacity: .38; }

/* Past the loop end is already the dimmest a pad gets. Silencing the row must not
   multiply the two dims together into something unreadable. */
.row.is-silent .cell.is-beyond { opacity: .26; }

/* Whatever has keyboard focus comes back to full strength, so the ring is always
   drawn at the contrast base.css intended. */
.row.is-silent .cell:focus-visible { opacity: 1; }

/* ------------------------------------------------------------------ mixer */

.mixer { padding: 12px 16px 14px; }

/* The head carries the kit bank and the mix A/B beside the title, because both are
   "what the whole rack sounds like" controls rather than per-channel ones. */
.mixer .rack__head { flex-wrap: wrap; align-items: flex-end; row-gap: 10px; }
.kitbay { display: flex; flex-direction: column; gap: 6px; }
.kitbay:first-of-type { margin-left: auto; }

.strips {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* A mixer needs width. Below about 760px of usable panel the strips scroll
     sideways INSIDE this box rather than pushing the page wider — the same
     containment rule the step grid uses. */
  overflow-x: auto;
  padding-bottom: 2px;
}

/*
 * One channel. A grid rather than a flex row, because the whole point of a mixer is
 * that the eight faders line up: with flex, a longer track name would shove one
 * channel's fader out of column and the eye could no longer compare levels.
 *
 * id | voice knobs | pan | fader + readout | meter | mute/solo
 */
.strip {
  display: grid;
  /* id | voice knobs | pan | fader + readout | meter | mute/solo.
     The knob bay is FIXED at three knobs' worth even on a two-knob voice, so PAN
     sits in the same place on every channel and the eye can sweep the column. */
  grid-template-columns: 150px 152px 46px minmax(180px, 1fr) 116px auto;
  align-items: center;
  gap: 10px;
  min-width: 740px;
  padding: 5px 8px;
  background: rgba(0, 0, 0, .22);
  border: 1px solid var(--edge);
  border-left: 3px solid var(--track-hue);
  border-radius: 8px;
}

.strip.is-selected { background: rgba(255, 255, 255, .045); }

/*
 * Silenced by its own mute, or by somebody else's solo. Same dimming as the grid row
 * above it, so the two views never disagree about what you can hear.
 *
 * And the same rule about where the dim goes. `.strip.is-silent { opacity }` on the
 * container would fade the M and S buttons — including the M you have to find in
 * order to un-mute — and their focus rings with them, at the one moment you need
 * them most. So the dim lands on the parts that only report a value, the channel
 * buttons stay at full strength (their lit amber state already says "silenced"), and
 * anything holding keyboard focus comes back to 1.
 */
.strip.is-silent .strip__id,
.strip.is-silent .knob,
.strip.is-silent .strip__fader,
.strip.is-silent .meter { opacity: .42; }

.strip.is-silent .knob:focus-within,
.strip.is-silent .strip__fader:focus-within { opacity: 1; }

.strip__id { display: flex; align-items: center; gap: 8px; min-width: 0; }
.strip__lamp {
  width: 9px;
  height: 9px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--track-hue);
  box-shadow: 0 0 8px var(--track-hue);
}
.strip__name {
  font: 600 12px/1.2 var(--font-ui);
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.strip__voice { font: 9px/1 var(--font); letter-spacing: .08em; color: var(--muted); }

.strip__knobs { display: flex; align-items: flex-start; gap: 4px; }

.strip__fader { display: flex; align-items: center; gap: 8px; }
.strip__fader .slider { flex: 1 1 auto; min-width: 80px; }

.strip__buttons { display: flex; gap: 5px; }

/* ================================================ patterns, song and project (M04) */

.patbay { padding: 12px 16px 14px; }

/*
 * Sixteen pads on one line at 1280 px, wrapping to two on a laptop. `auto-fit` with a
 * minimum rather than a fixed sixteen columns, because a bar that overflowed the panel
 * horizontally would push the whole page sideways — and gate assertion 9 is exactly
 * that check.
 */
.patbar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(54px, 1fr));
  gap: 6px;
  margin-bottom: 12px;
}

/* The bank break: a stronger gap between A8 and B1, which is the same trick the step
   grid plays every four columns. You navigate by shape before you read a label. */
.patbar .patpad:nth-child(9) { margin-left: 6px; box-shadow: inset 2px 0 0 -1px var(--edge); }

.patbay__body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px 18px;
  margin-bottom: 12px;
}
.patbay__body .unit + .unit { padding-left: 18px; border-left: 1px solid var(--edge); }

.songbay { padding: 12px 16px 14px; }
.songbay .rack__head { flex-wrap: wrap; align-items: flex-end; row-gap: 10px; }
.songbay .kitbay { margin-left: auto; }

.songrows {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}

/*
 * Rows recede until Song mode is armed. An arrangement you cannot hear yet is
 * reference material; the moment the transport can run it, it is the thing you are
 * looking at. Same idea as the mixer dimming a silenced channel.
 */
.songrows:not(.is-armed) { opacity: .78; }
/* Focus always comes back to full strength, so the ring is never drawn on a dimmed
   control — the same rule the mixer applies to a silenced channel. */
.songrows:not(.is-armed):focus-within { opacity: 1; }

.songrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-left: 3px solid var(--edge);
  border-radius: 8px;
}
.songrow.is-playing {
  border-left-color: var(--accent);
  background: rgba(255, 90, 60, .09);
}

.songrow__num {
  min-width: 18px;
  font: 600 12px/1 var(--font);
  color: var(--muted);
  text-align: right;
}
.songrow__pick { width: 118px; }
.songrow__repeats .field__label { color: var(--muted); }
.songrow__progress { min-width: 74px; text-align: center; }
.songrow__buttons { display: flex; gap: 5px; margin-left: auto; }

/* Square, like the mixer's M and S — they are the same class of control. The glyphs
   are arrows rather than letters, and a thin arrow at --muted is genuinely hard to
   see, so these carry full text colour. */
.chanbtn--row { min-width: 26px; padding: 0 6px; color: var(--text); font-weight: 400; }
.chanbtn--row:hover { color: var(--accent-2); border-color: var(--accent-2); }

.songbay__foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.projectbay {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px 18px;
  padding: 12px 16px;
}
.projectbay .unit + .unit { padding-left: 18px; border-left: 1px solid var(--edge); }

/* --------------------------------------------------------- the tour picker */

/*
 * Centred, above the app but BELOW the tour overlay's own chrome — the tour
 * spotlights this panel, so it has to sit inside the hole the spotlight cuts rather
 * than on top of the dim.
 */
.tourpicker {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-modal);
  width: min(560px, calc(100vw - 48px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  background: linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
}

.tourpicker__all { align-self: flex-start; }
.tourpicker__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 6px;
  max-height: 40vh;
  overflow-y: auto;
}
.tourpicker__chapter { justify-content: flex-start; text-align: left; }

/* ---------------------------------------------------------- master section */

.master {
  display: flex;
  align-items: flex-end;
  gap: 14px 18px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--edge);
}

.master .unit + .unit { padding-left: 18px; border-left: 1px solid var(--edge); }

.master__id { display: flex; flex-direction: column; gap: 5px; }
/* The signal path, silk-screened onto the panel the way a hardware mixer prints its
   block diagram next to the master fader. */
.master__path {
  font: 10px/1.4 var(--font);
  letter-spacing: .04em;
  color: var(--accent-2);
  opacity: .75;
}

/* -------------------------------------------------------------- timing lab */

.clockbay { padding: 12px 16px 14px; }

.clockbay__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

/* Six instruments on one bench. They wrap onto as many rows as the window needs;
   nothing here is allowed to force the page itself to scroll sideways. */
.clockbay__body {
  display: flex;
  align-items: flex-end;
  gap: 16px 18px;
  flex-wrap: wrap;
}

.stressbay { display: flex; flex-direction: column; gap: 7px; }

/*
 * The jitter trace: one bar per scheduled step, up for late and down for early,
 * centred on a line that means "exactly on the grid". It is a canvas rather than
 * DOM because it redraws once per step and a canvas costs one paint instead of
 * thirty-two style recalculations.
 */
.jitter {
  width: 100%;
  max-width: 260px;
  height: 56px;
  background: #0a0c11;
  border: 1px solid #000;
  border-radius: 6px;
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, .8);
}

/* Label across the top, value and its coach mark side by side underneath. */
.readout {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  gap: 6px;
}
.readout__label { grid-column: 1 / -1; }

/* Drift is signed: late is the common case, but which way a machine leans depends
   on how its timer and its sound card disagree, so both colours exist. */
.lcd.is-late { color: var(--warn); text-shadow: 0 0 12px rgba(255, 204, 68, .35); }
.lcd.is-early { color: var(--accent); text-shadow: 0 0 12px rgba(255, 90, 60, .3); }

.verdict {
  flex: 1 1 320px;
  min-width: 260px;
  margin: 0;
  padding: 9px 12px;
  border: 1px dashed var(--edge);
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--muted);
}
.verdict.is-answered { border-style: solid; border-color: var(--accent-2); color: var(--text); }

.footer {
  padding: 2px 4px;
  font-size: 12px;
  color: var(--muted);
}

/* ------------------------------------------------------------ manual modal */

.manual { position: fixed; inset: 0; z-index: var(--z-modal); }
.manual[hidden] { display: none; }

.manual__scrim { position: absolute; inset: 0; background: rgba(6, 7, 10, .68); }

.manual__dialog {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(760px, calc(100vw - 32px));
  max-height: min(640px, calc(100vh - 48px));
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .65);
}

.manual__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--edge);
}
.manual__title {
  font: 600 12px/1 var(--font);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
}
.manual__close { margin-left: auto; }

.manual__tabs { display: flex; gap: 6px; padding: 10px 16px 0; flex-wrap: wrap; }
.manual__tab {
  padding: 7px 12px;
  background: transparent;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
  color: var(--muted);
  font: 600 12px/1 var(--font-ui);
  cursor: pointer;
}
.manual__tab[aria-selected="true"] {
  color: var(--text);
  background: rgba(255, 255, 255, .04);
  border-color: var(--edge);
}

.manual__body {
  padding: 14px 16px 18px;
  overflow-y: auto;
  border-top: 1px solid var(--edge);
  margin-top: -1px;
}
/* The panel is a scroll container and therefore a real tab stop (tabindex="0" in
   manual.js). Its ring goes INSIDE, or the dialog's own edge clips it. */
.manual__body:focus-visible { outline-offset: -2px; }
.manual__body h3 {
  font: 600 12px/1 var(--font);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent-2);
  margin: 0 0 8px;
}
.manual__body p { margin: 0 0 10px; }

.manual__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.manual__list li {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 12px;
  align-items: baseline;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 255, 255, .05);
}
.manual__list dfn,
.manual__list .keys {
  font: 600 12px/1.4 var(--font);
  color: var(--warn);
  font-style: normal;
}
.manual__list .keys { color: var(--accent-2); }
.manual__note { color: var(--muted); font-size: 12.5px; }

/* ===================================================== the sound lab (M05) */

.lab { padding: 12px 16px 14px; }
.lab .rack__head { flex-wrap: wrap; align-items: flex-end; row-gap: 10px; }
.lab .kitbay:first-of-type { margin-left: auto; }

/* ------------------------------------------------------ the flow diagram */

/*
 * The diagram is the panel's headline, so it gets a recessed well of its own rather
 * than sitting flush with the knobs — the same inset-shadow treatment the meters and
 * the jitter graph use, which is what makes a screen area read as "instrument" instead
 * of "web page".
 */
.flowbay {
  margin: 0 0 12px;
  padding: 10px 12px 8px;
  background: #0a0c11;
  border: 1px solid #000;
  border-radius: 8px;
  box-shadow: inset 0 2px 10px rgba(0, 0, 0, .8);
  overflow-x: auto;
}

/*
 * A minimum width plus the scroll above it. The SVG scales with its viewBox, but below
 * roughly 520 px the block labels stop being legible — and an unreadable diagram that
 * fits is worse than a readable one you nudge sideways.
 */
/*
 * The max-width is doing real work. An SVG with a viewBox scales to whatever box you
 * give it, so on a 1280 px panel a four-block diagram rendered at 2.6x and the labels
 * came out larger than the page's headings — a diagram shouting over the panel it is
 * describing. Capping it keeps the blocks at roughly the size of the knobs beside them,
 * which is the size they mean.
 */
.flow {
  display: block;
  width: 100%;
  min-width: 520px;
  max-width: 780px;
  height: auto;
  margin: 0 auto;
}

.flowbay__cap {
  margin: 6px 2px 0;
  font-size: 11.5px;
  color: var(--muted);
}
.flowbay__cap code { color: var(--accent-2); }

.flow__box {
  fill: var(--panel-2);
  stroke: var(--edge);
  stroke-width: 1;
  transition: fill .12s ease, stroke .12s ease;
}
.flow__label {
  fill: var(--text);
  font: 600 11px/1 var(--font);
  letter-spacing: .06em;
  text-anchor: middle;
}
.flow__sub {
  fill: var(--muted);
  font: 400 9.5px/1 var(--font);
  text-anchor: middle;
}
.flow__link { fill: none; stroke: var(--edge); stroke-width: 1.5; }

/* The stage under the knob you are touching. Both the box and its section header
   light, so the panel and the picture agree about where you are. */
.flow__block.is-live .flow__box {
  fill: rgba(51, 209, 160, .16);
  stroke: var(--accent-2);
}
.flow__block.is-live .flow__label { fill: var(--accent-2); }

/* ------------------------------------------------------------ knob racks */

.lab__racks {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
  margin-bottom: 12px;
}

.labsec {
  flex: 1 1 200px;
  min-width: 200px;
  padding: 9px 11px 10px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-radius: 8px;
  transition: border-color .12s ease;
}
.labsec.is-live { border-color: var(--accent-2); }

.labsec__head { margin-bottom: 6px; }
.labsec__label {
  font: 600 10.5px/1 var(--font);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.labsec.is-live .labsec__label { color: var(--accent-2); }

.labsec__knobs { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 4px 2px; }

/*
 * The Lab's parameter names are longer than a channel strip's — RESONANCE against PAN
 * — and base.css sizes a knob at 46 px with a `white-space: nowrap` label, which is
 * correct for the mixer and one word too tight here: PITCH ENV and ENV TIME collided.
 *
 * Widened in this panel only. Editing the shared .knob would have relaid out all eight
 * mixer strips to fix a problem the mixer does not have.
 */
.labsec__knobs .knob { width: 62px; }
.labsec__knobs .knob__label { font-size: 8.5px; letter-spacing: .05em; }
.labsec__knobs .knob__value { font-size: 9.5px; }

.labsec__note {
  margin: 8px 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* ----------------------------------------------------------- the sample bay */

.samplebay {
  padding: 11px 12px 12px;
  margin-bottom: 12px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-radius: 8px;
}
.samplebay[hidden] { display: none; }

.samplebay__head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 9px;
}

/*
 * The drop target. The dashed edge is the affordance — it is the one convention every
 * learner already reads as "you may drop something here" — and it goes solid and green
 * while a file is over it.
 */
.dropzone {
  position: relative;
  padding: 8px 8px 6px;
  background: #0a0c11;
  border: 1px dashed var(--edge);
  border-radius: 8px;
  box-shadow: inset 0 2px 10px rgba(0, 0, 0, .8);
  transition: border-color .12s ease, background .12s ease;
}
.dropzone.is-over {
  border-style: solid;
  border-color: var(--accent-2);
  background: #0d1a16;
}

/* Sized in CSS; js/lab.js matches the backing store to devicePixelRatio so the
   waveform is crisp rather than a blurry smear on a HiDPI screen. */
.wave {
  display: block;
  width: 100%;
  height: 130px;
  cursor: ew-resize;
}

.dropzone__hint {
  margin: 4px 0 0;
  text-align: center;
  font-size: 11.5px;
  color: var(--muted);
}

.samplebay__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px 18px;
  margin-top: 11px;
}
.samplebay__controls .unit + .unit {
  padding-left: 18px;
  border-left: 1px solid var(--edge);
}

.field--wide { flex: 1 1 220px; min-width: 190px; }
.field--wide .slider { width: 100%; }

.unit--grow { flex: 1 1 220px; }

/* A single-bar meter for the mic input. It shares the meter chrome but is driven by a
   --level custom property rather than by the rAF ballistics the mixer meters use:
   there is no peak-hold question to answer, only "is anything arriving". */
.meter--mic { width: 74px; }
.meter--mic .meter__rms { clip-path: inset(0 calc(100% - var(--level, 0) * 100%) 0 0); }

.note {
  margin: 11px 0 0;
  padding: 9px 12px;
  border-left: 2px solid var(--warn);
  background: rgba(255, 204, 68, .05);
  border-radius: 0 6px 6px 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}
.note strong { color: var(--warn); }
.note code { color: var(--text); }

/* -------------------------------------------------------- kits and tools */

.lab__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px 18px;
  margin-bottom: 10px;
}
.lab__foot .unit + .unit { padding-left: 18px; border-left: 1px solid var(--edge); }

.userkit { display: inline-flex; align-items: center; gap: 3px; margin-right: 6px; }

/* A row head that a file is hovering over. Same language as the drop zone, so the two
   targets teach each other. */
.row__head.is-over {
  border-color: var(--accent-2);
  background: #0d1a16;
}

/* --------------------------------------------------------------- narrower */

@media (max-width: 1100px) {
  .row__head, .ruler__spacer { flex-basis: 136px; }
  .unit + .unit { padding-left: 12px; }
  .kitbay:first-of-type { margin-left: 0; }
  .songbay .kitbay { margin-left: 0; }
  .songrow { flex-wrap: wrap; }
  .songrow__buttons { margin-left: 0; }
  .lab .kitbay:first-of-type { margin-left: 0; }
  .samplebay__controls .unit + .unit { padding-left: 12px; }
  .lab__foot .unit + .unit { padding-left: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .cell { transition: none; }
  .flow__box, .labsec, .dropzone { transition: none; }
}
