/*
 * 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;
}

/* =========================================================================
 * M07 — the five pad markers.
 *
 * A pad now carries up to five pieces of information beyond on/off/accent, and the
 * design rule is that each one occupies a different PART of the pad so they can all be
 * true at once: a corner for probability, the opposite corner for a lock, the top edge
 * for a condition, the whole face for a ratchet, and the horizontal position for
 * micro-timing. None of them is a child node — they are pseudo-elements, custom
 * properties and one background-image — because syncCells() touches a hundred and
 * twenty-eight pads on every change and the tutorial spotlights these elements.
 *
 * `--prob`, `--nudge` (-1..1) and `--ratchet` are written by js/ui.js's syncCells().
 * ========================================================================= */

/*
 * The marker layer. One inert overlay per pad, added by buildRow(), sitting above the
 * pad's own colour and below nothing. Every marker on it carries a dark edge as well as
 * a bright one, because a pad's background is a different hue on every row and a yellow
 * wedge on the yellow snare row would otherwise be invisible on exactly the track
 * people put probability on first.
 */
.cell__mark {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  pointer-events: none;
}

/*
 * A RATCHET — one hairline per sub-hit, straight across the face, so four ratchets look
 * like four hits because that is what they are. The repeating gradient's period is the
 * pad's width divided by --ratchet, so the lines land where the sub-hits land.
 */
.cell.is-ratcheted .cell__mark {
  background-image: repeating-linear-gradient(90deg,
    rgba(10, 10, 14, .75) 0 2px,
    rgba(255, 255, 255, .8) 2px 3px,
    transparent 3px calc(100% / var(--ratchet, 1)));
}

/*
 * A CONDITIONAL TRIG — a bar down the LEFT edge, not the top: the top belongs to the
 * accent marker and two rules stacked there stop being two facts. The inset shadow is
 * the dark half of the pair.
 */
.cell.has-cond .cell__mark {
  border-left: 3px solid var(--warn);
  box-shadow: inset 3px 0 0 rgba(10, 10, 14, .6);
}

/*
 * PROBABILITY — a wedge in the bottom-left corner, its size the odds against. The
 * hairline of near-black between the wedge and the pad is what keeps it readable on
 * the two yellow tracks.
 */
.cell.is-chancy .cell__mark::before {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: calc(7px + (1 - var(--prob, 1)) * 13px);
  height: calc(7px + (1 - var(--prob, 1)) * 13px);
  border-radius: 0 0 0 5px;
  background: linear-gradient(45deg,
    var(--warn) 0 48%,
    rgba(10, 10, 14, .9) 48% 62%,
    transparent 62%);
}

/*
 * A PARAMETER LOCK — one mint dot, top right, ringed in near-black. Deliberately the
 * smallest marker on the pad: it says "look in the step editor", it does not try to say
 * what was locked.
 */
.cell.is-locked .cell__mark::after {
  content: '';
  position: absolute;
  top: 3px;
  right: 3px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-2);
  box-shadow: 0 0 0 1.5px rgba(10, 10, 14, .85), 0 0 7px var(--accent-2);
}

/*
 * MICRO-TIMING — a bright tick that slides across the pad to where the hit actually
 * lands. The pad itself does NOT move: a grid whose columns wandered would stop being a
 * grid, and the whole point of a nudge is that the step is still that step, played a
 * little early or late. This one is on the pad rather than the overlay, because the
 * overlay is carrying the ratchet's gradient and a step can be both.
 */
.cell.is-nudged {
  background-image: linear-gradient(90deg,
    rgba(10, 10, 14, .8) 0 1px,
    rgba(255, 255, 255, .95) 1px 4px,
    rgba(10, 10, 14, .8) 4px 5px);
  background-size: 5px 100%;
  background-repeat: no-repeat;
  background-position: calc(50% + var(--nudge, 0) * 42%) 0;
  border-color: var(--accent-2);
}

/*
 * The pad the step editor is pointing at. A solid mint ring, distinct from the
 * keyboard cursor's dashed one: the two are frequently on the same pad and
 * frequently are not, and telling them apart is the difference between
 * "what will Space do" and "what will the panel below edit".
 */
.cell.is-editing {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

/* 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;
}

/* ====================================================== the FX rack (M06) */

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

/*
 * This head carries more than any other panel's — a title, a hint, two readouts and
 * the A/B — so the hint is made the flexible one. Letting it absorb the slack keeps
 * all four groups on a single row at 1280 and hands the wrap to the sentence, which
 * is the only item here that reads fine on two lines.
 */
.fxrack .rack__hint { flex: 1 1 260px; min-width: 210px; }

/*
 * While the A/B is held down the whole rack dims. It is the cheapest possible way to
 * answer "which side am I listening to right now", and a comparison you can lose track
 * of is a comparison that proves nothing.
 */
.fxrack.is-bypassed .fxband { opacity: .5; }
.fxrack.is-bypassed .flow__block--insert .flow__box,
.fxrack.is-bypassed .flow__block--send .flow__box,
.fxrack.is-bypassed .flow__block--master .flow__box { opacity: .35; }

.btn--ab { min-width: 178px; letter-spacing: .06em; }
.btn--ab.is-lit {
  color: var(--bg);
  background: var(--warn);
  border-color: var(--warn);
}

/*
 * The whole-machine diagram is wider and taller than the Sound Lab's single voice, so
 * it gets its own ceiling. Wider than this and the four bands drift so far apart that
 * the send taps stop reading as taps.
 */
.flowbay--wide .flow { max-width: 940px; min-width: 620px; }
.flowbay__key { color: var(--accent-2); }

/*
 * The visual argument, and it is carried by the line style rather than the labels: a
 * send tap is dashed because it is a COPY leaving, while the signal itself carries on
 * down the solid line. Somebody who never reads the caption still sees it.
 */
.flow__link--tap {
  stroke: var(--accent-2);
  stroke-dasharray: 4 3;
  opacity: .75;
}

.flow__block--send .flow__box { stroke: var(--accent-2); stroke-dasharray: 4 3; }
.flow__block--source .flow__box { fill: #12151d; }
.flow__block--safety .flow__box { stroke: rgba(255, 204, 68, .5); }
.flow__block--out .flow__box { fill: #0d1017; stroke-dasharray: 2 2; }

/* --------------------------------------------------------------- the bands */

.fxband {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
  margin-bottom: 12px;
  transition: opacity .12s ease;
}

.fxband--experiment {
  align-items: flex-end;
  gap: 14px 22px;
  padding: 10px 12px;
  background: rgba(51, 209, 160, .05);
  border: 1px solid rgba(51, 209, 160, .3);
  border-radius: 8px;
}
.fxband--experiment .field { flex: 0 1 280px; }

.fxband--master { border-top: 1px solid var(--edge); padding-top: 12px; }

.fxband--presets { align-items: flex-start; gap: 12px 20px; margin-bottom: 0; }
.fxband--presets .unit { flex: 0 1 auto; }
.fxband--presets .note { flex: 1 1 320px; margin-top: 0; }

.note--quiet {
  border-left-color: var(--edge);
  background: rgba(255, 255, 255, .022);
}
.note--quiet strong { color: var(--text); }

/* --------------------------------------------------------- one unit's panel */

/*
 * Deliberately the same construction as .labsec next door: a bordered card, an
 * uppercase label, a row of knobs and one sentence. Two panels that taught the same
 * interaction with two different looks would be two things to learn.
 */
.fxsec {
  flex: 1 1 210px;
  min-width: 190px;
  display: flex;
  flex-direction: column;
  padding: 9px 11px 10px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-radius: 8px;
  transition: border-color .12s ease;
}
.fxsec.is-live { border-color: var(--accent-2); }

/*
 * The sends card is drawn with the same dashed accent edge the send taps have in the
 * diagram above it, so the two agree without a caption. It sits in the same row as the
 * three insert cards on purpose — the comparison is the lesson — and it would be a
 * poor lesson if the two kinds of routing looked identical.
 */
.fxsec--send {
  border-style: dashed;
  border-color: rgba(51, 209, 160, .45);
  background: rgba(51, 209, 160, .045);
}
.fxsec--send .fxsec__label { color: var(--accent-2); }

/* The master chain's preset card holds one bank and a sentence, so it does not need
   the width the four unit cards beside it are asking for. */
.fxsec--presets { flex: 0 1 250px; }

/*
 * A hairline between switch options, inside the FX rack only.
 *
 * The shared .switch in base.css draws no divider, which is correct everywhere else:
 * "Pattern | Song" and "On | Off" cannot be misread. The FX rack has five-way banks of
 * very short labels, and `1/8.  1/16  1/16T` without separators reads as one string.
 * Scoped here rather than changed in base.css, because touching the shared control
 * would relay out every switch in six milestones to fix a problem only this panel has.
 */
.fxrack .switch__opt + .switch__opt .switch__face {
  border-left: 1px solid var(--edge);
}

.fxsec__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 7px;
}
.fxsec__label {
  font: 600 10.5px/1 var(--font);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.fxsec.is-live .fxsec__label { color: var(--accent-2); }

.fxsec__knobs {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px 2px;
}
.fxsec__knobs .knob { width: 62px; }
.fxsec__knobs .knob__label { font-size: 8.5px; letter-spacing: .05em; }
.fxsec__knobs .knob__value { font-size: 9.5px; }
.fxsec__knobs .switch { align-self: center; }

.fxsec__aside {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 0 4px;
}

.fxsec__note {
  margin: 8px 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
  /* Pushes the sentence to the bottom of the card so a row of cards with different
     knob counts still has its prose on one baseline. */
  margin-top: auto;
  padding-top: 8px;
}
.fxsec__note--warn.is-warn { color: var(--warn); }

/* ------------------------------------------------------- the power switches */

/*
 * The LED on a pedal. A real <button> with aria-pressed rather than a checkbox,
 * because the state is "engaged / bypassed" and not "checked" — and because a screen
 * reader should say "pressed", which is what the two states actually are.
 */
.fxpower {
  min-width: 42px;
  padding: 3px 7px;
  font: 600 9.5px/1.5 var(--font);
  letter-spacing: .1em;
  color: var(--muted);
  background: #0d1017;
  border: 1px solid var(--edge);
  border-radius: 4px;
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.fxpower:hover { color: var(--text); border-color: var(--muted); }
.fxpower.is-on {
  color: var(--accent-2);
  border-color: var(--accent-2);
  box-shadow: inset 0 0 8px rgba(51, 209, 160, .22);
}
.fxpower--wide { min-width: 56px; }

/* ------------------------------------------------------------- preset banks */

.fxpresets { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.fxpresets .switch--wide { flex: 0 1 auto; }

.fxpresets__del {
  padding: 2px 6px;
  font: 400 9.5px/1.5 var(--font);
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 4px;
  cursor: pointer;
}
.fxpresets__del:hover { color: var(--warn); border-color: var(--warn); }

.field__input--pick { width: auto; padding-right: 6px; }

/* ==================================================== M07 — parameter locks */

/*
 * Three bays across, and they do not divide evenly on purpose: the step editor is the
 * milestone and gets the width, the LFO needs a graph wide enough to read two bars of
 * dots, and the tools are buttons. `minmax(0, …)` on every track is what stops the
 * knob rows from forcing the grid wider than the panel.
 */
.steplab { padding: 12px 16px 14px; }

.steplab__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
}

.stepbay {
  grid-row: span 2;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px 12px;
  background: rgba(0, 0, 0, .18);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  transition: border-color .15s ease;
}
/* The bay lights when the step it is pointing at carries anything at all, so you can
   tell "this step is plain" from "this step is loaded" without reading a value. */
.stepbay.is-loaded { border-color: var(--accent-2); }

.stepbay__head {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
}

.stepshape,
.stepchips {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 4px;
  align-items: flex-start;
}
.stepshape .unit__body,
.stepchips .unit__body { flex-wrap: wrap; gap: 8px; }
.stepshape .knob,
.stepchips .knob { width: 66px; }
.stepshape .knob__label,
.stepchips .knob__label { font-size: 8.5px; letter-spacing: .05em; }
.stepshape .knob__value,
.stepchips .knob__value { font-size: 9.5px; }
.stepshape .field__input--pick { min-width: 92px; }

/*
 * A lock chip: the knob and its LOCK button, boxed together so the pair reads as one
 * control. Unlocked it is quiet and shows the value the step would inherit; locked it
 * is mint, which is the same colour the pad's dot uses — one idea, one hue.
 */
.chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: 8px;
  transition: border-color .12s ease, background-color .12s ease;
}
.chip .knob__value { color: var(--muted); }
.chip.is-locked {
  border-color: var(--accent-2);
  background: rgba(51, 209, 160, .09);
}
.chip.is-locked .knob__value { color: var(--accent-2); }
.chip .fxpower { min-width: 44px; }

/*
 * The resolution-order readout. It is a list rather than a sentence because it is a
 * pipeline, and reading it top to bottom is reading the order.
 */
.resolve {
  padding: 8px 10px;
  background: rgba(0, 0, 0, .3);
  border: 1px solid var(--edge);
  border-radius: 8px;
}
.resolve__law {
  margin: 0 0 6px;
  font: 10px/1.4 var(--font);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--accent-2);
}
.resolve__list { margin: 0; padding: 0; list-style: none; }
.resolve__list li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
  font: 11px/1.5 var(--font);
  border-top: 1px solid rgba(43, 47, 64, .7);
}
.resolve__list li:first-child { border-top: 0; }
.resolve__stage {
  flex: 0 0 108px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: 9.5px;
}
.resolve__list strong { color: var(--text); }
.resolve__detail { color: var(--muted); }

/* ------------------------------------------------------------------- the LFO */

.lfobay,
.toolbay {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px 12px;
  background: rgba(0, 0, 0, .18);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
}

/*
 * The graph. Its height is fixed and its width is fluid, and the canvas backing store
 * is a fixed 620x96 — the drawing is dots and hairlines, so a little scaling is
 * invisible and a resize observer would be three times the code for no gain.
 */
.lfograph {
  display: block;
  width: 100%;
  height: 96px;
  border: 1px solid var(--edge);
  border-radius: 8px;
  background: rgba(0, 0, 0, .32);
}

.lfoctl { display: flex; flex-wrap: wrap; gap: 10px 4px; }
.lfoctl .unit__body { flex-wrap: wrap; gap: 8px; }
.lfoctl .knob { width: 66px; }
.lfoctl .knob__label { font-size: 8.5px; letter-spacing: .05em; }
.lfoctl .knob__value { font-size: 9.5px; }

/* ----------------------------------------------------------------- the tools */

.toolbay .unit__body { flex-wrap: wrap; gap: 8px; }
.toolbay .knob { width: 70px; }
.toolbay .unit--wide { flex: 1 1 100%; }
/* The verdict carries `flex: 1 1 320px` so it can share a ROW with a control in the
   panels that put it there. This bay is a column, where growing means growing
   downwards — a dashed box four hundred pixels tall around one sentence. */
.toolbay .verdict { margin: 0; flex: 0 0 auto; }

/*
 * FILL is held, not toggled, so it is styled as a momentary switch: unlit until your
 * finger is down and dark again the instant it lifts. The lit state is driven by
 * aria-pressed rather than by :active, because the F key holds it too and a key press
 * is not a pointer press.
 */
.btn--fill {
  min-width: 76px;
  justify-content: center;
  letter-spacing: .12em;
}
.btn--fill[aria-pressed="true"] {
  background: var(--warn);
  border-color: var(--warn);
  color: #16161c;
  box-shadow: 0 0 18px -4px var(--warn);
}

/* The hits-per-bar readout turns yellow at the point DECISIONS §2.11 measured as the
   edge of the budget, so the warning arrives before the audio does. */
.lcd.is-warn { color: var(--warn); }

/* ==================================================== M08 — performance mode */

/*
 * The view switch, in the header. It is the only control on the machine that changes
 * what the machine looks like, so it sits with the Manual and the tour button rather
 * than inside any of the panels it hides.
 */
.switch--view { margin-left: auto; }

/*
 * Perform view: every editing panel folds away and the stage grows. Nothing is
 * destroyed — see setView() in js/ui.js — so meters keep their ballistics, the tour
 * keeps its targets and a half-open <select> keeps its choice.
 *
 * The grid deliberately stays. It is the machine's face, and in Perform view it is
 * what shows a recorded hit landing on the step you played it on.
 */
.app.is-perform #p-locks,
.app.is-perform #sound-lab,
.app.is-perform #fx-rack,
.app.is-perform #patterns,
.app.is-perform #song,
.app.is-perform .projectbay,
.app.is-perform #mixer,
.app.is-perform #timing-lab { display: none; }

/*
 * …and the two that stay swap places. `.app` has been a column flex since M01, so
 * this is six `order` declarations and no markup change at all: in Perform view the
 * pads are the instrument and the grid is the read-out, which is the opposite of
 * what they are in Edit view. Explicit numbers rather than one `order: -1`, because
 * a single negative order would jump the panel above the header as well.
 */
.app.is-perform .topbar { order: 0; }
.app.is-perform .transport { order: 1; }
.app.is-perform #performance { order: 2; }
.app.is-perform #grid-panel { order: 3; }
.app.is-perform #midi { order: 4; }
.app.is-perform .footer { order: 5; }

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

.stage__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap);
  margin-top: var(--gap);
}

.stagebay {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px 0;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
}
.stagebay--wide { grid-column: 1 / -1; }
.stagebay .unit__body { flex-wrap: wrap; gap: 8px; }
.stagebay .knob { width: 70px; }
.stagebay .knob__label { font-size: 8.5px; letter-spacing: .05em; }
.stagebay .knob__value { font-size: 9.5px; }

/* ------------------------------------------------------------------ the pads */

/*
 * Backlit rubber, not buttons. They are wide enough for a finger and tall enough to
 * carry three labels — the track, the home-row key and the MIDI note — because all
 * three address the same voice.
 */
.padgrid {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 8px;
  margin-top: var(--gap);
}

.pad {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  min-height: 72px;
  padding: 9px 10px;
  color: var(--text);
  text-align: left;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(0, 0, 0, .25)),
    var(--panel-2);
  border: 1px solid var(--edge);
  border-left: 3px solid var(--track-hue, var(--edge));
  border-radius: 8px;
  cursor: pointer;
  transition: transform .06s ease, box-shadow .12s ease, background-color .12s ease;
}
.pad:hover { border-color: var(--muted); }
.pad__name {
  font: 600 12px/1.2 var(--font-ui);
  letter-spacing: .02em;
}
.pad__keys {
  font: 400 9.5px/1.3 var(--font);
  color: var(--muted);
  letter-spacing: .04em;
}
/* Struck: the pad lights in its own track colour and sinks a pixel, which is the
   whole of "this is a physical control" as far as the eye is concerned. */
.pad.is-down {
  transform: translateY(1px);
  background: var(--track-hue, var(--accent));
  color: #14151b;
  box-shadow: 0 0 22px -6px var(--track-hue, var(--accent));
}
.pad.is-down .pad__keys { color: rgba(20, 21, 27, .7); }
.pad.is-muted { opacity: .45; }

.app.is-perform .padgrid { gap: 10px; }
.app.is-perform .pad { min-height: 108px; }
.app.is-perform .pad__name { font-size: 15px; }

/* -------------------------------------------------------------- the recorder */

/*
 * REC is red and latching, unlike FILL and ROLL beside it, because arming a recorder
 * is a decision and holding a fill is a gesture. The pulse is the one animation in
 * DRUMLAB that exists to be noticed from across a room.
 */
.btn--rec { min-width: 84px; justify-content: center; letter-spacing: .12em; }
.btn--rec.is-armed {
  background: var(--accent);
  border-color: var(--accent);
  color: #16161c;
  box-shadow: 0 0 20px -4px var(--accent);
  animation: rec-pulse 1.4s ease-in-out infinite;
}
@keyframes rec-pulse {
  50% { box-shadow: 0 0 6px -2px var(--accent); }
}

/* ------------------------------------------------------------------ scenes */

.scenebar { display: flex; flex-wrap: wrap; gap: 6px; }

.scene { display: flex; flex-direction: column; gap: 3px; }

.scene__pad {
  min-width: 46px;
  padding: 7px 8px;
  font: 600 11px/1.2 var(--font);
  letter-spacing: .06em;
  color: var(--muted);
  background: #0d1017;
  border: 1px solid var(--edge);
  border-radius: 6px;
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.scene__pad:hover { color: var(--text); border-color: var(--muted); }
/* Three states, and they are three different facts: this slot HOLDS something, this
   slot is what you are hearing, this slot is waiting for the bar line. */
.scene__pad.is-filled { color: var(--text); border-color: var(--muted); }
.scene__pad.is-current {
  color: #14151b;
  background: var(--accent-2);
  border-color: var(--accent-2);
}
.scene__pad.is-pending {
  color: var(--warn);
  border-color: var(--warn);
  box-shadow: inset 0 0 10px rgba(255, 204, 68, .22);
}

.scene__save {
  padding: 2px 4px;
  font: 400 8px/1.4 var(--font);
  letter-spacing: .08em;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 4px;
  cursor: pointer;
}
.scene__save:hover { color: var(--accent-2); border-color: var(--accent-2); }

/* -------------------------------------------------------------- mute groups */

.groupbar { display: flex; flex-wrap: wrap; gap: 6px; }

.groupbtn {
  min-width: 58px;
  padding: 7px 9px;
  font: 600 10px/1.2 var(--font);
  letter-spacing: .1em;
  color: var(--muted);
  background: #0d1017;
  border: 1px solid var(--edge);
  border-radius: 6px;
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease;
}
.groupbtn:hover { color: var(--text); border-color: var(--muted); }
.groupbtn.is-muted {
  color: #14151b;
  background: var(--accent);
  border-color: var(--accent);
}
/* Which group the membership checkboxes below are editing. Right-click to move it. */
.groupbtn.is-editing { box-shadow: inset 0 0 0 1px var(--accent-2); }

/* ===================================================== M08 — the MIDI bay */

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

.midi__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
  margin-top: var(--gap);
}

.midibay__bay {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
}
.midibay__bay--wide { grid-column: 1 / -1; }
/* .unit--grow is `flex: 1 1 220px`, written in M05 for a bay that lays its units out
   in a ROW, where 220px is a width. These bays stack vertically, so the same
   declaration reads as a 220px HEIGHT and leaves a hole under the bindings list.
   Grow to fill; do not reserve. */
.midibay__bay .unit--grow { flex: 1 1 auto; }
.midibay__bay .unit { border: 0; padding: 0; }
.midibay__bay .unit + .unit { padding-left: 0; }
.midibay__bay .unit__body { flex-wrap: wrap; gap: 8px; }

/* The fake controller is marked out in the tutorial colour, because it is a teaching
   device rather than part of the instrument. */
.midibay__bay--virtual { border-color: rgba(51, 209, 160, .35); }

.midibay .note--warn { color: var(--warn); }

.binding {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px;
  background: #0d1017;
  border: 1px solid var(--edge);
  border-radius: 5px;
}
.binding__text { font: 400 10px/1.5 var(--font); color: var(--text); }
.binding__drop {
  padding: 0 5px;
  font: 400 12px/1.2 var(--font);
  color: var(--muted);
  background: transparent;
  border: 0;
  cursor: pointer;
}
.binding__drop:hover { color: var(--warn); }

.btn.is-armed,
.btn--warn.is-armed {
  background: var(--warn);
  border-color: var(--warn);
  color: #16161c;
}
.btn--warn.is-on { border-color: var(--accent-2); color: var(--accent-2); }

/* ------------------------------------------------------------ the byte monitor */

/*
 * The bytes, in hex, as they go past. Fixed height with its own scroll so a busy
 * clock cannot push the panels below it down the page — SPEC §6 assertion 9 is about
 * horizontal overflow, but a log that grows without bound is the vertical version of
 * the same discourtesy.
 */
.bytelog {
  max-height: 132px;
  overflow-y: auto;
  padding: 6px 8px;
  background: #0a0c11;
  border: 1px solid var(--edge);
  border-radius: 6px;
}
.bytelog__empty {
  margin: 0;
  font: 400 10px/1.6 var(--font);
  color: var(--muted);
}
.bytelog__line {
  display: grid;
  grid-template-columns: 30px 148px minmax(0, 1fr);
  gap: 8px;
  margin: 0;
  padding: 1px 0;
  font: 400 10px/1.6 var(--font);
}
.bytelog__dir { color: var(--muted); }
.bytelog__line--in .bytelog__dir { color: var(--accent-2); }
.bytelog__line--out .bytelog__dir { color: var(--accent); }
.bytelog__hex { color: var(--text); letter-spacing: .08em; }
.bytelog__text { color: var(--muted); overflow: hidden; text-overflow: ellipsis; }

/*
 * The monitor's live region: one sentence, updated a couple of times a second, so a
 * screen reader hears "note on ch1 note 36 vel 118" instead of the whole hex dump
 * being re-announced on every byte. See syncLog() in js/stage.js.
 */
.bytelog__last {
  margin: 5px 0 0;
  font: 400 10px/1.6 var(--font);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

@media (max-width: 1100px) {
  .stage__grid { grid-template-columns: minmax(0, 1fr); }
  .midi__grid { grid-template-columns: minmax(0, 1fr); }
  .padgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .bytelog__line { grid-template-columns: 30px minmax(0, 1fr); }
  .bytelog__text { grid-column: 1 / -1; }
  .steplab__grid { grid-template-columns: minmax(0, 1fr); }
  .stepbay { grid-row: auto; }
  .fxrack .kitbay:first-of-type { margin-left: 0; }
  .flowbay--wide .flow { min-width: 620px; }
  .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; }
  .fxsec, .fxpower, .fxband { transition: none; }
  .stepbay, .chip { transition: none; }
  .pad, .scene__pad, .groupbtn { transition: none; }
  /* The REC lamp still reads as armed: it keeps its colour and loses only the pulse. */
  .btn--rec.is-armed { animation: none; }
}
