:root {
  --bg: #0e0f12;
  --bg-2: #15171c;
  --bg-3: #1d2027;
  --fg: #e8e6df;
  --fg-dim: #8a8c93;
  --accent: #c2f04a;
  --accent-2: #6ee7ff;
  --danger: #ff6b6b;
  --border: #262933;
  --step: #2a2e38;
  --step-on: #c2f04a;
  --step-now: #ffffff;
}

* { box-sizing: border-box; }

/* Disable double-tap zoom site-wide on touch devices. `manipulation` permits
   panning + pinch zoom but suppresses the 300ms double-tap-to-zoom gesture,
   which otherwise fires when a user taps the step grid / pads fast. The step
   grid and roll grid still override this with their own touch-action values
   (none / pan-y) where they capture pointer events directly. */
html, body, button, input, select, textarea, label, a,
.sq-step, .sq-pattern__cell, .sq-se__pad {
  touch-action: manipulation;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  letter-spacing: 0.01em;
}

.sq-transport {
  position: sticky;
  /* The account bar above is pinned too (app/ui.module.css), so the transport
     sticks UNDER it rather than on top of it. The offset is the bar's measured
     height, written onto <html> by AccountBar.tsx; the fallback is 0, which is
     what the legacy static server and a page with no bar want. */
  top: var(--sq-topbar-h, 0px);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  z-index: 10;
  flex-wrap: wrap;
}

/* An anchor, not a heading: it goes to the homepage (main.js intercepts the
   plain click to ask first when the session has work in it to lose). */
.sq-logo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding-right: 6px;
  color: var(--fg);
  font-weight: 700;
  letter-spacing: 0.02em;
  user-select: none;
  text-decoration: none;
  cursor: pointer;
}
.sq-logo:hover span { color: var(--accent); }
.sq-logo img { width: 30px; height: 30px; }
.sq-logo span { font-size: 15px; }

.sq-transport .sq-spacer { flex: 1; }
/* Main controls fill the row and wrap internally; the right cluster (metronome +
   clock + master meter) stays beside them, pinned to the right and never bumped
   to its own line. */
.sq-transport__main { flex: 1 1 0; min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
.sq-transport__right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
/* Scale → octave → chord → arp are one keyboard-performance cluster; flex-basis
   100% drops them onto their own line under transport + bpm + swing. */
.sq-transport__kbd-row { flex: 1 0 100%; display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }

.sq-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--fg-dim);
}

.sq-field label { text-transform: lowercase; }

input[type="number"],
input[type="text"],
select {
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  font: inherit;
  outline: none;
}

input[type="number"] { width: 64px; }
/* Ranges that knob.js hasn't upgraded (the wavetable's harmonic bars) keep the
   native look. Everything else is drawn by the .sq-knob block below. */
input[type="range"] { accent-color: var(--accent); }

/* ---------- KNOBS ----------------------------------------------------------
   knob.js wraps every range input in .sq-knob and writes two custom properties
   on it: --knob-v (the value, 0..1) and --knob-a (the same as an angle). It
   knows nothing about circles — all the shape is here, which is what lets a
   rack that reads badly as dials become bars by overriding one selector.

   The native input stays inside, transparent and stretched over the whole
   dial: it is still the value, still the focus target, and still what the
   pointer hits, so the parameter menu's DOM walk and the mod/aut dots keep
   working without knowing knobs exist. */

/* The four properties knob.js writes are REGISTERED, and on mobile that is
   load-bearing rather than tidy. An untyped custom property is a token list,
   and WebKit does not reliably re-resolve the two places these are read from
   when one changes: a gradient stop inside a cached background-image, and a
   pseudo-element inheriting the new value from its originating element. Both
   describe this knob exactly — the arc is a conic-gradient stop, the pointer
   is `.sq-knob__dial::after`'s transform — so on iOS the value moved, the
   readout and the sound moved with it, and the dial went on drawing where the
   parameter used to be. Typed, they are real properties with real
   invalidation. Nothing here transitions, so making them animatable costs
   nothing; a browser without @property (iOS < 16.4) ignores the block and is
   no worse off than before. */
@property --knob-v  { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --knob-a  { syntax: "<angle>";  inherits: true; initial-value: -135deg; }
@property --knob-m  { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --knob-ma { syntax: "<angle>";  inherits: true; initial-value: -135deg; }

.sq-knob {
  --knob-size: 36px;
  /* The unfilled arc has to read against the panel behind it, or a knob at 20%
     looks identical to one at 80% and the rack stops being scannable — which is
     the one thing dials are worse at than bars. */
  --knob-track: #3a4050;
  --knob-fill: var(--accent);
  --knob-thickness: 5px;
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  width: var(--knob-size);
  height: var(--knob-size);
  -webkit-tap-highlight-color: transparent;
}

/* --knob-hit is the minimum target size. The input below is inset by a
   negative amount to reach it, so a knob drawn at 26px is still grabbable at
   44px on a phone — the drawing shrinks for density, the target does not.
   Applied to the input rather than a pseudo-element on the wrapper: the input
   is what carries the drag handlers, so anything else would just be paint. */
.sq-knob { --knob-hit: 30px; }
/* `any-pointer` rather than `pointer`, so a laptop with a touchscreen gets the
   big target too — its primary pointer is the trackpad, but the finger is the
   one that needs the room. The 768px arm is there because this stylesheet
   decides what "phone" means by width everywhere else; a layout that has gone
   to its mobile arrangement should not still be carrying desktop-sized targets
   because a media feature disagreed. */
@media (any-pointer: coarse), (max-width: 768px) {
  .sq-knob { --knob-hit: 44px; }
}

/* The value arc. A conic gradient from --knob-v, masked to a ring, over a 270°
   sweep that starts at 7 o'clock — the gap sits at the bottom where a rack of
   knobs has nothing to say. The arc is deliberately thick and high-contrast:
   angle is a weaker visual channel than length, so a wall of these only stays
   scannable if the filled part is unmissable. */
.sq-knob__dial {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    conic-gradient(from 225deg,
      var(--knob-fill) 0deg,
      var(--knob-fill) calc(var(--knob-v, 0) * 270deg),
      var(--knob-track) calc(var(--knob-v, 0) * 270deg),
      var(--knob-track) 270deg,
      transparent 270deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
    transparent calc(50% - var(--knob-thickness)), #000 calc(50% - var(--knob-thickness) + 1px));
  mask: radial-gradient(circle at 50% 50%,
    transparent calc(50% - var(--knob-thickness)), #000 calc(50% - var(--knob-thickness) + 1px));
  pointer-events: none;
}

/* The body and the pointer line. The line is what you read at a glance when
   the arc is short; the body gives the control a grabbable-looking mass. */
.sq-knob__dial::before {
  content: "";
  position: absolute;
  inset: calc(var(--knob-thickness) + 2px);
  border-radius: 50%;
  background: var(--bg-2);
  border: 1px solid var(--border);
}
/* The pointer line. Its transform-origin is the knob's centre expressed as a
   distance from the line's own top edge — percentages here would resolve
   against the line's box, not the dial's. */
.sq-knob__dial::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--knob-thickness) + 3px);
  width: 2px;
  height: calc(var(--knob-size) * 0.22);
  margin-left: -1px;
  border-radius: 1px;
  background: var(--fg);
  transform-origin: 50% calc(var(--knob-size) / 2 - var(--knob-thickness) - 3px);
  transform: rotate(var(--knob-a, -135deg));
}

/* A control that runs through zero reads from twelve o'clock, so you can see
   at a glance which side of centre it is on — the silverbox's tuning, the hexop's
   pitch envelope, the contagion's filter-2 offset. */
.sq-knob[data-bipolar] .sq-knob__dial {
  background:
    conic-gradient(from 225deg,
      var(--knob-track) 0deg,
      var(--knob-track) min(135deg, calc(var(--knob-v, 0.5) * 270deg)),
      var(--knob-fill) min(135deg, calc(var(--knob-v, 0.5) * 270deg)),
      var(--knob-fill) max(135deg, calc(var(--knob-v, 0.5) * 270deg)),
      var(--knob-track) max(135deg, calc(var(--knob-v, 0.5) * 270deg)),
      var(--knob-track) 270deg,
      transparent 270deg);
}

/* The input itself: invisible, but present, focusable and hit-testable. */
.sq-knob input[type="range"] {
  position: absolute;
  /* Never smaller than the dial, larger when --knob-hit asks for it. Sized
     explicitly and centred rather than stretched with insets: a range input has
     an intrinsic width, so `left` and `right` together leave it over-constrained
     and it keeps its own size. */
  left: 50%;
  top: 50%;
  width: max(var(--knob-size), var(--knob-hit));
  height: max(var(--knob-size), var(--knob-hit));
  transform: translate(-50%, -50%);
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: grab;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
/* The second needle: where an lfo or an automation lane has actually pushed
   this parameter, drawn over the knob's own pointer without moving it. Its
   colour is --motion-dot, set on the field wrapper by refreshParamIndicators
   for the dot beside the label, so the needle and the dot always agree about
   which of the two is driving the control.

   On .sq-knob's own ::after rather than a third element: there are over a
   thousand knobs in a full session, and a span each — hidden or not — is a
   thousand nodes bought to draw a handful of needles. It comes after the input
   in the box order, so it paints over the dial without a stacking context.

   Longer than the base pointer and starting further out, so you can still tell
   the two apart when the modulation is resting exactly on the slider. */
.sq-knob[data-moving]::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--knob-thickness) - 1px);
  width: 2px;
  height: calc(var(--knob-size) * 0.36);
  margin-left: -1px;
  border-radius: 1px;
  background: var(--motion-dot, var(--accent));
  /* A hairline of the panel behind it, so the needle still separates when it
     crosses the value arc — which is exactly where it spends its time. */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
  transform-origin: 50% calc(var(--knob-size) / 2 - var(--knob-thickness) + 1px);
  transform: rotate(var(--knob-ma, -135deg));
  pointer-events: none;
}
.sq-knob input[type="range"]:active { cursor: grabbing; }
.sq-knob:hover { --knob-track: color-mix(in oklab, var(--bg-3) 60%, var(--fg-dim)); }
.sq-knob:has(input:focus-visible) { --knob-track: var(--accent-2); }
.sq-knob:has(input:focus-visible) .sq-knob__dial::before { border-color: var(--accent-2); }

/* Above the knob, because on a phone everything below it is under the finger. */
.sq-knob__readout {
  position: fixed;
  z-index: 400;
  transform: translate(-50%, calc(-100% - 8px));
  padding: 3px 7px;
  border-radius: 4px;
  background: var(--bg-3);
  border: 1px solid var(--accent);
  color: var(--fg);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  white-space: nowrap;
}
.sq-knob__readout[hidden] { display: none; }

/* ---------- KNOBS ON A PHONE: VERTICAL SLIDERS ------------------------------
   The same wrapper, the same two custom properties, a different shape. On a
   touch screen a dial is the wrong control: the finger hides the pointer it
   is reading, and 270° of arc packed into 44px asks for a precision a thumb
   does not have. A vertical bar reads from across the room — length is a far
   stronger visual channel than angle — and its gesture is the one every phone
   already teaches: drag up for more.

   knob.js sees none of this. It measures the dial at the start of each drag
   and, finding it taller than it is wide, maps the bar's own height to the
   full range so the thumb tracks the finger 1:1 (see `travelFor`). The drag
   is still relative — the value moves only when the finger does — because a
   control you play must never jump to where you happened to land on it.

   Keyed on the PRIMARY pointer being a finger (phones, tablets), or on the
   layout having gone to its phone arrangement — not `any-pointer`, which the
   hit-size rule above uses: a touchscreen laptop wants a bigger target, not a
   different instrument.

   Sizing: --knob-size is the bar's column width (the mobile rules already set
   it to 40-44px per context, and that is the width the target needs anyway);
   --knob-len is its height and --knob-bar the width of the drawn track. */
@media (pointer: coarse), (max-width: 768px) {
  .sq-knob {
    --knob-len: 72px;
    --knob-bar: 14px;
    height: var(--knob-len);
  }
  /* The track. Inset 3px top and bottom so the thumb, centred ON the value,
     has somewhere to sit at 0 and at 100 without leaving the wrapper. Its
     height is therefore the whole travel, which is what knob.js measures. */
  .sq-knob > .sq-knob__dial {
    inset: 3px auto;
    left: 50%;
    width: var(--knob-bar);
    /* The dial is a span, and a panel's `span { min-width }` rule for its
       labels (the fx rack's) would widen the bar to whatever it asks. */
    min-width: 0;
    margin-left: calc(var(--knob-bar) / -2);
    border-radius: calc(var(--knob-bar) / 2);
    box-sizing: border-box;
    border: 1px solid var(--border);
    background:
      linear-gradient(to top,
        var(--knob-fill) 0%,
        var(--knob-fill) calc(var(--knob-v, 0) * 100%),
        var(--knob-track) calc(var(--knob-v, 0) * 100%),
        var(--knob-track) 100%);
    -webkit-mask: none;
    mask: none;
  }
  /* No body: a bar has no mass to draw. */
  .sq-knob__dial::before { content: none; }
  /* The thumb — a bar across the track, wider than it, centred on the value.
     `(1 - v) * 100%` because CSS measures from the top and the slider from
     the bottom. */
  .sq-knob__dial::after {
    left: 50%;
    top: calc((1 - var(--knob-v, 0)) * 100%);
    width: calc(var(--knob-bar) + 10px);
    height: 4px;
    margin-left: calc((var(--knob-bar) + 10px) / -2);
    border-radius: 2px;
    background: var(--fg);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55);
    transform-origin: 50% 50%;
    transform: translateY(-50%);
  }
  /* Through-zero controls fill from the middle, as the dial does from twelve. */
  .sq-knob[data-bipolar] .sq-knob__dial {
    background:
      linear-gradient(to top,
        var(--knob-track) 0%,
        var(--knob-track) min(50%, calc(var(--knob-v, 0.5) * 100%)),
        var(--knob-fill) min(50%, calc(var(--knob-v, 0.5) * 100%)),
        var(--knob-fill) max(50%, calc(var(--knob-v, 0.5) * 100%)),
        var(--knob-track) max(50%, calc(var(--knob-v, 0.5) * 100%)),
        var(--knob-track) 100%);
  }
  /* The target covers the whole column: the bar's height, and never less than
     the 44px the hit rule asks for across. */
  .sq-knob input[type="range"] {
    width: max(var(--knob-size), var(--knob-hit));
    height: max(var(--knob-len), var(--knob-hit));
  }
  /* The second needle, where an lfo or a lane has pushed the parameter: a
     thinner, wider marker in the motion colour, on the same scale as the
     thumb. It sits on the wrapper, so the 3px track inset is applied here. */
  .sq-knob[data-moving]::after {
    left: 50%;
    top: calc(3px + (1 - var(--knob-m, 0)) * (100% - 6px));
    width: calc(var(--knob-bar) + 18px);
    height: 3px;
    margin-left: calc((var(--knob-bar) + 18px) / -2);
    border-radius: 2px;
    transform-origin: 50% 50%;
    transform: translateY(-50%);
  }
  .sq-knob:has(input:focus-visible) .sq-knob__dial { border-color: var(--accent-2); }
  /* Denser contexts get a shorter bar. The hexop's 6x8 operator grid is six
     rows of these; the transport bar has the play button beside its one. */
  .sq-hexop__ops .sq-hexop__f .sq-knob { --knob-len: 56px; }
  .sq-transport .sq-knob { --knob-len: 48px; }
}

/* Fields hold a wide slider beside their label; a knob is square, so the label
   goes under it. Flagged by knob.js per field so any slider left unupgraded
   keeps its original layout. */
.sq-field[data-knob],
.sq-fx__ctl[data-knob],
.sq-contagion__f[data-knob],
.sq-hexop__f[data-knob],
.sq-guitar__f[data-knob],
.sq-bass__f[data-knob],
.sq-sub__f[data-knob],
.sq-drone__f[data-knob],
.sq-vox__f[data-knob],
.sq-lancet__f[data-knob],
.sq-halo__f[data-knob],
.sq-siege__f[data-knob],
.sq-oracle__f[data-knob],
.sq-ladder__freq[data-knob],
.sq-ladder__noise[data-knob] {
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
.sq-field[data-knob] > label,
.sq-fx__ctl[data-knob] > span,
.sq-contagion__f[data-knob] > span,
.sq-guitar__f[data-knob] > span,
.sq-bass__f[data-knob] > span,
.sq-sub__f[data-knob] > span,
.sq-drone__f[data-knob] > span,
.sq-vox__f[data-knob] > span,
.sq-lancet__f[data-knob] > span,
.sq-halo__f[data-knob] > span,
.sq-siege__f[data-knob] > span,
.sq-oracle__f[data-knob] > span,
.sq-ladder__freq[data-knob] > span,
.sq-ladder__noise[data-knob] > span {
  font-size: 10px;
  line-height: 1.1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

button {
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 7px 12px;
  font: inherit;
  cursor: pointer;
  text-transform: lowercase;
}

button:hover { border-color: var(--fg-dim); }
button:active { transform: translateY(1px); }

button.sq-play {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 600;
  min-width: 68px;
}

#set-share {
  align-self: stretch;
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
  font-weight: 600;
  text-transform: none;
  padding: 0 14px;
  min-width: 68px;
}

button.sq-play.is-playing {
  background: var(--accent-2);
  border-color: var(--accent-2);
}

button.sq-btn--ghost { background: transparent; }
button.sq-btn--danger { color: var(--danger); }

.sq-status {
  color: var(--fg-dim);
  font-size: 12px;
  min-width: 120px;
  text-align: right;
}

.sq-meter {
  position: relative;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 2px;
  overflow: hidden;
  flex-shrink: 0;
}
.sq-meter__bar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  background: #4ade80;
  transition: width 40ms linear;
}
.sq-meter__bar.is-clip { background: #ef4444; }

.sq-beat-indicator { flex-shrink: 0; display: block; }
.sq-beat-indicator .sq-beat__dot { fill: var(--bg-3); stroke: var(--border); stroke-width: 0.6; transition: fill 40ms linear; }
.sq-beat-indicator .sq-beat__dot.is-beat-strong { stroke: color-mix(in oklab, var(--accent) 55%, var(--border)); }
.sq-beat-indicator .sq-beat__dot.is-now { fill: var(--accent); stroke: var(--accent); }
.sq-beat-indicator .sq-beat__dot.is-beat-strong.is-now { fill: var(--accent-2); stroke: var(--accent-2); }
.sq-beat-indicator .sq-beat__label {
  font-family: ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  fill: var(--fg);
  letter-spacing: 0.02em;
}

#metronome[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
#metronome[aria-pressed="true"] .sq-btn-icon { color: #0b0d10; }
/* The metronome button doubles as the click's volume, the way the dice does
   density: while it is on it fills from the bottom and drags up/down to set
   it. Off, it is a plain toggle with no fill. On, the button is the accent
   colour, so the fill goes dark. */
#metronome { position: relative; overflow: hidden; touch-action: none; }
#metronome[aria-pressed="true"]::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--metro-level, 70%);
  background: rgba(11, 13, 16, 0.22);
  pointer-events: none;
  transition: height 60ms linear;
}
#metronome .sq-btn-icon { position: relative; }
/* Palette toggle: full color when on, desaturated when off. */
#note-colors .sq-btn-icon { transition: filter 80ms linear, opacity 80ms linear; }
#note-colors[aria-pressed="false"] .sq-btn-icon { filter: grayscale(1); opacity: 0.55; }

.sq-track__oct { display: inline-flex; gap: 2px; }
.sq-track__oct button { padding: 3px 8px; font-size: 11px; min-width: 44px; }
.sq-track__len-extend { display: inline-flex; gap: 2px; }
/* The slot badge (clips.js): only drawn while a clip has the track off the
   scene's pattern, or stopped. */
.sq-track__slot { padding: 2px 7px; font-size: 11px; border-radius: 10px; border: 1px solid var(--accent-2); color: var(--accent-2); background: color-mix(in oklab, var(--accent-2) 14%, transparent); }
.sq-track__slot.is-off { border-color: var(--fg-dim); color: var(--fg-dim); background: transparent; text-decoration: line-through; }
.sq-track__len-extend button { padding: 3px 8px; font-size: 11px; min-width: 36px; }
.sq-track__len-extend .track-shift-left { margin-left: 6px; }
/* Phone-only quick row (render.js's applyQuickRow): fx / filter / +1 / /2 /
   speed, pulled out of the head and dropped in right after it — a sibling,
   not a head child, so none of the head's own mobile hiding rules (which
   funnel these into the "more" menu on desktop-shaped markup) reach them
   here. It only ever exists on a phone; there is nothing to style for
   desktop, which never gets this element at all. */
.sq-track__quickrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
}
/* fx / filter / +1 / /2 match the generator row's own square icon-button
   style (dice / euclid / chance / roll / more) — same 56px icon-over-caption
   button, same [data-label] naming trick (render.js's applyQuickRow swaps
   each one's class and innerHTML on the way in and back out), so the two
   rows read as one family. speed is the one non-button here and keeps its
   own field styling. */
.sq-track__quickrow .sq-icon-btn {
  position: relative;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  /* Two lines, on purpose. The six sound buttons (inst / fx / filter /
     eq / mod / aut) share the first, each an equal share of it rather than a
     fixed 56px — seven of those plus the speed field were a line and a half
     on a 390px phone, the break landing wherever it fell. The pattern's own
     controls (semi − / +, shift ◂ / ▸, +1, /2) take the second, after the
     zero-height break the track head uses (`::before`, ordered between the
     two groups), sharing it the same way: two rows of six equal squares.
     Speed is not here: it sits in the head beside p-lock. */
  flex: 1 1 44px;
  min-width: 44px;
  width: auto;
  height: auto;
  min-height: 56px;
  padding: 5px 2px;
  line-height: 1.1;
}
.sq-track__quickrow::before {
  content: "";
  order: 1;
  flex: 0 0 100%;
  height: 0;
}
.sq-track__quickrow > .track-semi-down,
.sq-track__quickrow > .track-semi-up,
.sq-track__quickrow > .track-shift-left,
.sq-track__quickrow > .track-shift-right,
.sq-track__quickrow > .track-len-plus1,
.sq-track__quickrow > .track-len-half { order: 2; }
/* The len-extend cluster gives shift ◂ a margin to stand apart from /4; here
   it is one square among the others. */
.sq-track__quickrow > .track-shift-left { margin-left: 0; }
.sq-track__quickrow .sq-icon-btn .sq-btn-icon { width: 18px; height: 18px; }
.sq-track__quickrow [data-label]::after {
  content: attr(data-label);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: lowercase;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The dot that says something is on behind a button (data-on, the same
   refreshPanelBadges stamp the desktop panel buttons wear): drawn with
   ::before here, because on a phone ::after is the caption under the icon.
   The "more" button, whose caption took its dot the same way, gets one too. */
.sq-track__quickrow > button[data-on]::before,
.sq-track__head .sq-track__more[data-on]::before {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--badge, var(--accent));
  box-shadow: 0 0 0 1.5px var(--bg-3);
  pointer-events: none;
}
.sq-track__quickrow > button[aria-pressed="true"][data-on]::before,
.sq-track__head .sq-track__more[aria-pressed="true"][data-on]::before {
  background: #0b0d10;
  box-shadow: none;
}
.sq-meter--master { width: 80px; height: 12px; }
.sq-track__meter  { width: 100%; height: 100%; }

/* Vol slider + meter: native slider on top, thin meter strip immediately below.
   Both children stretch to the same width (140px minus any flex-cross-axis trim). */
.sq-vol-combo {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  width: 140px;
  gap: 3px;
}
.sq-vol-combo > * { box-sizing: border-box; }
/* Volume sits above its level meter. It is the most-reached-for control on the
   track, so it gets the biggest knob, centred over the meter. */
.sq-track__head .sq-field.sq-vol__field .sq-vol-combo .sq-knob {
  --knob-size: 44px;
  align-self: center;
}
.sq-vol-combo .sq-track__meter {
  width: 100%;
  height: 4px;
  border-radius: 2px;
  border: 0;
}
.sq-status.is-err { color: var(--danger); }
/* Status shares the keyboard row, taking whatever width is left over at its
   right end. A zero flex-basis is what keeps it there: sized from its content it
   would claim the width of the message and wrap the row instead of ellipsing. */
.sq-status.sq-status--bar {
  flex: 1 1 0;
  box-sizing: border-box;
  text-align: right;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sq-pattern-bar {
  display: flex;
  gap: 10px;
  padding: 8px 16px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  align-items: center;
}
.sq-pattern__grid {
  display: grid;
  grid-template-columns: repeat(16, minmax(0, 1fr));
  grid-auto-rows: 22px;
  gap: 3px;
  flex: 1;
}
.sq-pattern__cell {
  background: var(--step);
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
  font-size: 10px;
  color: var(--fg-dim);
  display: grid;
  place-items: center;
  padding: 0;
  min-height: 22px;
}
.sq-pattern__cell.is-filled { background: color-mix(in oklab, var(--step-on) 35%, var(--step)); color: var(--fg); }
.sq-pattern__cell.is-active { border-color: var(--step-now); box-shadow: 0 0 0 1px var(--step-now) inset; }
.sq-pattern__cell.is-queued { border-color: var(--accent-2); }
.sq-pattern__cell.is-looped { box-shadow: inset 0 -3px 0 var(--accent-2); }
.sq-pattern__cell.is-drag-over { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 20%, var(--step)); }
.sq-pattern__cell.is-drop-before { box-shadow: -3px 0 0 0 var(--accent); }
.sq-pattern__cell.is-drop-after { box-shadow: 3px 0 0 0 var(--accent); }
.sq-repeat__wrap { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; color: var(--fg-dim); font-size: 11px; }
.sq-repeat__wrap input { width: 48px; text-align: center; }
.sq-repeat__wrap select { width: 64px; text-align: center; }
#pattern-mode[aria-pressed="true"] { background: var(--accent-2); color: #0b0d10; border-color: var(--accent-2); }
#pattern-switch[aria-pressed="true"] { background: var(--accent-2); color: #0b0d10; border-color: var(--accent-2); }
#set-save, #set-load, #set-export, #set-import, #set-share { text-transform: none; }
#set-save, #set-load, #set-export, #set-import,
#pattern-dup { padding: 3px 10px; font-size: 12px; min-width: 64px; }
.sq-icon-btn {
  padding: 0;
  min-width: 32px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sq-icon-btn .sq-btn-icon { display: block; }
/* Undo / redo sit as a pair — closer to each other than to anything else in the
   transport, so the two read as one control with two directions. */
.sq-history { display: inline-flex; align-items: center; gap: 2px; }
#undo:disabled, #redo:disabled { opacity: 0.3; cursor: not-allowed; }
/* The dice button doubles as the density control: it fills from the bottom to
   show how full the next roll will be, and drags up/down to set it. */
.track-dice { position: relative; overflow: hidden; touch-action: none; }
.track-dice::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--dice-level, 50%);
  background: color-mix(in oklab, var(--accent) 26%, transparent);
  pointer-events: none;
  transition: height 60ms linear;
}
.track-dice .sq-btn-icon { position: relative; }
.track-euclid[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
/* roll + clear: text on desktop, icon on mobile (media block flips these) */
.sq-track__roll .sq-btn-icon,
.sq-track__clear .sq-btn-icon,
#macro-pads .sq-btn-icon { display: none; }
.sq-ai__btn { display: inline-flex; align-items: center; gap: 5px; }
.sq-ai__btn .sq-ai__icon { display: inline-flex; color: inherit; }
.sq-ai__btn:disabled .sq-ai__icon { color: var(--fg-dim); }

.sq-dl__btn { display: inline-flex; align-items: center; gap: 5px; }
.sq-dl__btn .sq-dl__icon { display: inline-flex; color: inherit; }
.sq-dl__btn:disabled .sq-dl__icon { color: var(--fg-dim); }
.sq-set__stack, .sq-mode__stack, .sq-len__stack { display: flex; flex-direction: column; gap: 4px; }
.sq-len__stack button { min-width: 36px; }
.sq-mode__row { display: flex; gap: 4px; }

.sq-master__prompt {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}
.sq-master__prompt input[type="text"] { flex: 1; }
#master-prompt {
  flex: 1;
  font: inherit;
  resize: none;
  min-height: 32px;
  max-height: 180px;
  overflow-y: auto;
  line-height: 1.35;
  padding: 6px 8px;
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
}
.sq-master__toggle,
.sq-gen__toggle { display: inline-flex; align-items: center; gap: 6px; color: var(--fg-dim); white-space: nowrap; }
.sq-gen__toggle-btn[aria-pressed="true"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}
.sq-gen__toggle-btn[data-state="on"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}
.sq-gen__toggle-btn[data-state="all"] {
  background: #ffa94d;
  color: #0b0d10;
  border-color: #ffa94d;
}

.sq-track__drumkit[aria-pressed="true"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}
#master-prompt-go {
  background: #ff6fa3;
  color: #0b0d10;
  border-color: #ff6fa3;
  font-weight: 600;
}
#master-prompt-go:disabled { opacity: 0.5; cursor: progress; }
#master-variate-go {
  background: #a68bff;
  color: #0b0d10;
  border-color: #a68bff;
  font-weight: 600;
}
#master-variate-go:disabled { opacity: 0.5; cursor: progress; }
.sq-gen__toggle-btn {
  background: transparent;
  color: var(--fg-dim);
  border-color: var(--border);
  font-weight: normal;
}
.sq-gen__count-wrap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--fg-dim);
}
.sq-gen__count-wrap input { width: 50px; text-align: center; }
.sq-gen__sound-engine { padding: 4px 8px; font-size: 12px; }

main {
  padding: 16px;
  display: grid;
  gap: 12px;
  max-width: 1400px;
  margin: 0 auto;
}

/* Add-track button lives under the track list. */
.sq-add-track-row {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 16px 24px;
}
.sq-add-track-row #add-track { width: 100%; padding: 12px; border-style: dashed; }

.sq-track {
  position: relative;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  display: grid;
  gap: 10px;
}

/* Track identity colour — a coloured edge running the full height of the
   card, so a track stays identifiable by colour alone while its step grid
   or roll scrolls the header out of view, and two long tracks stacked one
   after another read as two tracks rather than one continuous block. Cyclic
   by DOM position (nth-child) rather than track id or index in JS: `#tracks`
   holds nothing but `.sq-track` cards and its child order always IS the
   visual order (state.tracks and the DOM move together — see placeBusesLast
   above), so the colouring needs no bookkeeping of its own and never goes
   stale across a reorder, a duplicate or a remove. */
.sq-track::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  border-radius: 8px 0 0 8px;
}
/* The 8 hues are picked ~135° apart around the wheel (not sequential steps
   around it), so any two NEIGHBOURING cards — including the wrap from the
   8th back to the 1st — land far apart in hue. Sequential 45°-apart hues put
   two greens (or two blues) back to back, which is the bug this replaced.
   Each rule also stamps the raw hue onto --track-hue, so anything else that
   wants to read as "this track's colour" (the inline fx/mod borders below)
   can build its own hsl() off the same number instead of repeating it. */
#tracks > .sq-track:nth-child(8n+1) { --track-hue: 35; }
#tracks > .sq-track:nth-child(8n+2) { --track-hue: 232; }
#tracks > .sq-track:nth-child(8n+3) { --track-hue: 297; }
#tracks > .sq-track:nth-child(8n+4) { --track-hue: 120; }
#tracks > .sq-track:nth-child(8n+5) { --track-hue: 253; }
#tracks > .sq-track:nth-child(8n+6) { --track-hue: 318; }
#tracks > .sq-track:nth-child(8n+7) { --track-hue: 155; }
#tracks > .sq-track:nth-child(8n+8) { --track-hue: 275; }
#tracks > .sq-track:nth-child(8n+1)::before { background: hsl(35 85% 58%); }
#tracks > .sq-track:nth-child(8n+2)::before { background: hsl(232 70% 68%); }
#tracks > .sq-track:nth-child(8n+3)::before { background: hsl(297 55% 68%); }
#tracks > .sq-track:nth-child(8n+4)::before { background: hsl(120 45% 52%); }
#tracks > .sq-track:nth-child(8n+5)::before { background: hsl(253 70% 72%); }
#tracks > .sq-track:nth-child(8n+6)::before { background: hsl(318 65% 68%); }
#tracks > .sq-track:nth-child(8n+7)::before { background: hsl(155 45% 50%); }
#tracks > .sq-track:nth-child(8n+8)::before { background: hsl(275 55% 70%); }

.sq-track.is-muted { opacity: 0.45; }

/* ---------- Jam activity: whose edit is that? -----------------------------
   jamActivity.js sets --jam-color (a peer's own colour, the same hash
   JamPanel.tsx's presence dots use) and this class on the track, the
   specific knob, and the pattern slot a peer's patch touched — and it STAYS,
   as "who last changed this" rather than a flash, until this screen's own
   edit of that exact control takes it back off. This only says how it looks
   while it's on; the transition is what turns landing and leaving into a
   fade instead of a flicker. */
.is-jam-touched { transition: box-shadow 0.25s ease; }
.sq-track.is-jam-touched { box-shadow: 0 0 0 2px var(--jam-color, var(--accent)) inset; }
.sq-pattern__cell.is-jam-touched { box-shadow: 0 0 0 2px var(--jam-color, var(--accent)); }
.sq-knob.is-jam-touched { box-shadow: 0 0 0 2px var(--jam-color, var(--accent)); border-radius: 50%; }
.sq-field.is-jam-touched,
.sq-fx__ctl.is-jam-touched,
.sq-contagion__f.is-jam-touched,
.sq-hexop__f.is-jam-touched { box-shadow: 0 0 0 2px var(--jam-color, var(--accent)); border-radius: 4px; }

/* An fx bus (engine `bus`): a track other tracks are routed into, so one rack,
   one mod matrix and one set of automation lanes shape all of them at once.
   There is no instrument in it, so everything that writes or plays a note goes
   away — the step grid, the roll, the dice, the octave shifts, the patch
   buttons, and the envelope, which only ever fires on a note. What is left is
   the signal chain and the two modulation panels, which is the whole point. */
.sq-track.is-bus { border-color: color-mix(in srgb, var(--accent-2) 45%, var(--border)); }
.sq-track.is-bus .sq-steps,
.sq-track.is-bus .sq-track__roll,
.sq-track.is-bus .sq-track__env,
.sq-track.is-bus .sq-track__clear,
.sq-track.is-bus .sq-track__copy,
.sq-track.is-bus .sq-track__paste,
.sq-track.is-bus .track-dice,
.sq-track.is-bus .track-euclid,
.sq-track.is-bus .sq-track__oct,
.sq-track.is-bus .sq-track__len-extend,
/* +1 / /2 leave .sq-track__len-extend for the phone-only quick row
   (render.js's applyQuickRow) — hidden by their own class too, since the
   container-level rule above no longer reaches them there. */
.sq-track.is-bus .track-len-plus1,
.sq-track.is-bus .track-len-half,
.sq-track.is-bus .track-shift-left,
.sq-track.is-bus .track-shift-right,
.sq-track.is-bus .sq-track__save,
.sq-track.is-bus .sq-track__load-patch,
.sq-track.is-bus .sq-track__wav { display: none !important; }

/* What is feeding a bus, written on the bus. Without it the routing is only
   legible from the sending end. */
.sq-track__bus-in {
  font-size: 11px;
  color: var(--accent-2);
  opacity: 0.85;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sq-track__bus-in[hidden] { display: none; }
.sq-track__out-field[hidden] { display: none !important; }

.sq-track__head {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.sq-track__synth-row {
  flex-basis: 100%;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: stretch;
}
.sq-track__synth-row:empty { display: none; }
.sq-track__synth-row > .sq-param-group[hidden] + .sq-param-group[hidden] { /* no-op, just a placeholder */ }

.sq-track__head .sq-field .sq-knob { --knob-size: 40px; }
.sq-track__head .sq-field label { font-size: 11px; }

.sq-param-group {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: rgba(255,255,255,0.015);
}
.sq-param-group .sq-field .sq-knob { --knob-size: 36px; }
.sq-param-group--timbre { border-color: color-mix(in oklab, var(--accent) 40%, var(--border)); }
.sq-param-group--timbre[hidden] { display: none !important; }
.sq-param-group--osc-mix { border-color: color-mix(in oklab, var(--accent-2) 40%, var(--border)); }
.sq-param-group--osc-mix[hidden] { display: none !important; }
.sq-param-group--osc-mod { border-color: color-mix(in oklab, #d5a6ff 40%, var(--border)); }
.sq-param-group--osc-mod[hidden] { display: none !important; }
.sq-param-group--ladder { border-color: color-mix(in oklab, #e0a868 40%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--ladder[hidden] { display: none !important; }
.sq-param-group--ladder .sq-ladder__osc-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
}
.sq-param-group--ladder .sq-ladder__osc-lbl { min-width: 32px; color: var(--fg-dim); }
.sq-param-group--ladder .sq-ladder__freq { display: inline-flex; align-items: center; gap: 4px; }
.sq-param-group--ladder .sq-ladder__freq .sq-knob { --knob-size: 38px; }
.sq-param-group--ladder select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--ladder .sq-ladder__noise { display: flex; align-items: center; gap: 6px; }
.sq-param-group--ladder .sq-ladder__noise .sq-knob { --knob-size: 38px; }
.sq-param-group--contagion { border-color: color-mix(in oklab, #6ec6ff 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--contagion[hidden] { display: none !important; }
.sq-param-group--contagion .sq-contagion__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--contagion .sq-contagion__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--contagion .sq-contagion__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--contagion .sq-contagion__f .sq-knob { --knob-size: 34px; }
.sq-param-group--contagion select { padding: 2px 6px; font-size: 11px; }
/* Hexop. The operator rows are a grid rather than a flex row: six operators by
   eight controls only reads as an instrument if the columns line up, which is
   how the machine's own operator page was laid out too. */
.sq-param-group--hexop { border-color: color-mix(in oklab, #b48cff 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--hexop[hidden] { display: none !important; }
.sq-param-group--hexop .sq-hexop__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--hexop .sq-hexop__lbl { color: var(--fg-dim); min-width: 40px; }
.sq-param-group--hexop .sq-hexop__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--hexop .sq-hexop__row .sq-hexop__f .sq-knob { --knob-size: 34px; }
.sq-param-group--hexop select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--hexop .sq-hexop__alg { color: var(--accent); font-size: 11px; letter-spacing: 0.02em; }
.sq-param-group--hexop .sq-hexop__alg-fb { color: var(--fg-dim); }
.sq-hexop__ops { display: grid; gap: 2px; overflow-x: auto; }
.sq-hexop__ophead, .sq-hexop__oprow {
  display: grid;
  grid-template-columns: 34px repeat(8, minmax(32px, 1fr)) 54px;
  align-items: center;
  gap: 3px;
  min-width: 400px;
}
.sq-hexop__ophead > span { font-size: 10px; color: var(--fg-dim); text-align: center; opacity: 0.75; }
.sq-hexop__opn { font-size: 10px; color: var(--fg-dim); }
.sq-hexop__oprow.is-carrier .sq-hexop__opn { color: var(--accent); }
.sq-hexop__oprow.is-feedback .sq-hexop__opn::after { content: "↻"; margin-left: 2px; color: var(--accent-2); }
/* Cells in the operator grid carry no label of their own — the column heads
   name them — so they just centre their knob. */
.sq-hexop__ops .sq-hexop__f { display: flex; align-items: center; justify-content: center; min-width: 0; }
.sq-hexop__ops .sq-hexop__f .sq-knob { --knob-size: 26px; --knob-thickness: 4px; }
.sq-hexop__ops select { padding: 1px 4px; font-size: 10px; }
/* Electric guitar: four rows, one per link in the chain — tone, string, amp,
   cab — because that is the order the signal goes in. */
.sq-param-group--guitar { border-color: color-mix(in oklab, #e8a33d 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--guitar[hidden] { display: none !important; }
.sq-param-group--guitar .sq-guitar__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--guitar .sq-guitar__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--guitar .sq-guitar__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--guitar .sq-guitar__f .sq-knob { --knob-size: 34px; }
.sq-param-group--guitar select { padding: 2px 6px; font-size: 11px; }
/* Electric bass: hand, dirt, amp — the chain again, one row per link. */
.sq-param-group--bass { border-color: color-mix(in oklab, #8fbf6a 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--bass[hidden] { display: none !important; }
.sq-param-group--bass .sq-bass__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--bass .sq-bass__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--bass .sq-bass__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--bass .sq-bass__f .sq-knob { --knob-size: 34px; }
.sq-param-group--bass select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--sub { border-color: color-mix(in oklab, #6a7fbf 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--sub[hidden] { display: none !important; }
.sq-param-group--sub .sq-sub__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--sub .sq-sub__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--sub .sq-sub__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--sub .sq-sub__f .sq-knob { --knob-size: 34px; }
.sq-param-group--sub select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--drone { border-color: color-mix(in oklab, #9a6abf 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--drone[hidden] { display: none !important; }
.sq-param-group--drone .sq-drone__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--drone .sq-drone__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--drone .sq-drone__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--drone .sq-drone__f .sq-knob { --knob-size: 34px; }
.sq-param-group--drone select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--vox { border-color: color-mix(in oklab, #d77a9a 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--vox[hidden] { display: none !important; }
.sq-param-group--vox .sq-vox__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--vox .sq-vox__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--vox .sq-vox__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--vox .sq-vox__f .sq-knob { --knob-size: 34px; }
.sq-param-group--vox select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--vox .sq-vox__lyric { flex: 1 1 160px; min-width: 120px; padding: 3px 6px; font: inherit; font-size: 11px; color: var(--fg); background: var(--bg); border: 1px solid var(--border); border-radius: 3px; }
/* under 16px, iOS zooms the page onto a text field when it takes focus */
@media (pointer: coarse) { .sq-param-group--vox .sq-vox__lyric { font-size: 16px; } }
/* what the phonetic box makes of the lyric, under its row */
.sq-param-group--vox .sq-vox__said { font-size: 11px; color: var(--fg-dim); padding-left: 52px; overflow-wrap: anywhere; }
.sq-param-group--lancet { border-color: color-mix(in oklab, #e0b24a 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--lancet[hidden] { display: none !important; }
.sq-param-group--lancet .sq-lancet__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--lancet .sq-lancet__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--lancet .sq-lancet__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--lancet .sq-lancet__f .sq-knob { --knob-size: 34px; }
.sq-param-group--lancet select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--halo { border-color: color-mix(in oklab, #9fc4d8 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--halo[hidden] { display: none !important; }
.sq-param-group--halo .sq-halo__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--halo .sq-halo__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--halo .sq-halo__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--halo .sq-halo__f .sq-knob { --knob-size: 34px; }
.sq-param-group--halo select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--siege { border-color: color-mix(in oklab, #c9533a 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--siege[hidden] { display: none !important; }
.sq-param-group--siege .sq-siege__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--siege .sq-siege__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--siege .sq-siege__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--siege .sq-siege__f .sq-knob { --knob-size: 34px; }
.sq-param-group--siege select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--oracle { border-color: color-mix(in oklab, #6f7fd9 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--oracle[hidden] { display: none !important; }
.sq-param-group--oracle .sq-oracle__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--oracle .sq-oracle__lbl { color: var(--fg-dim); min-width: 44px; }
.sq-param-group--oracle .sq-oracle__f { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--oracle .sq-oracle__f .sq-knob { --knob-size: 34px; }
.sq-param-group--oracle select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--silverbox { border-color: color-mix(in oklab, #ff7a4d 45%, var(--border)); }
.sq-param-group--silverbox[hidden] { display: none !important; }
.sq-param-group--silverbox select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--granular { border-color: color-mix(in oklab, #ffd35a 45%, var(--border)); flex-direction: column; align-items: stretch; gap: 4px; }
.sq-param-group--granular[hidden] { display: none !important; }
.sq-param-group--granular .sq-gran__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-param-group--granular .sq-gran__lbl { color: var(--fg-dim); }
.sq-param-group--granular .sq-gran__sync { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-param-group--granular select { padding: 2px 6px; font-size: 11px; }
.sq-param-group--granular .sq-field .sq-knob { --knob-size: 36px; }
.sq-param-group .sq-field[hidden] { display: none !important; }
.sq-filter__group { border-color: color-mix(in oklab, var(--accent-2) 40%, var(--border)); }

.sq-panel__btn-group {
  display: inline-flex;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.sq-panel__btn-group > button {
  border: none;
  border-radius: 0;
  border-right: 1px solid var(--border);
  padding: 7px 10px;
}
.sq-panel__btn-group > button:last-child { border-right: none; }

/* The dot on a panel button: something is switched on behind it (a stage in
   the rack, a closed filter, an enabled LFO or lane, the compressor's switch),
   stamped as `data-on` by refreshPanelBadges (paramTargets.js). Each button
   carries its panel's colour, the same one it turns when pressed, and on a
   pressed button the dot goes dark so it stays visible on that background. */
.sq-panel__btn-group > button,
.sq-track__menu-panels > button,
.sq-track__more { position: relative; }
.sq-panel__btn-group > button[data-on]::after,
.sq-track__menu-panels > button[data-on]::after,
.sq-track__more[data-on]::after {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--badge, var(--accent));
  box-shadow: 0 0 0 1.5px var(--bg-3);
  pointer-events: none;
}
.sq-panel__btn-group > button[aria-pressed="true"][data-on]::after,
.sq-track__menu-panels > button[aria-pressed="true"][data-on]::after,
.sq-track__more[aria-pressed="true"][data-on]::after {
  background: #0b0d10;
  box-shadow: none;
}
.sq-track__filter { --badge: var(--accent-2); }

/* A panel with something on in it is shown on the track (`is-live`, from the
   same refreshPanelBadges), with only the rows that are on — the rack's
   stages not on the track (picked from the fx button) and the mod matrix's
   adder wait in the modal. Scoped to .sq-track: opened as a modal the panel is reparented
   out of the track, and there it is the whole panel again. The border is the
   button's colour (fx/mod: the track's own hue, see below), so the block
   reads as "the fx rack" or "the mod matrix" without a heading. */
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row:not(.is-live) { display: none; }
.sq-track .sq-track__mod-panel.is-live .sq-lfo__row:not(.is-live) { display: none; }
.sq-track .sq-track__filter-panel.is-live,
.sq-track .sq-track__env-panel.is-live,
.sq-track .sq-track__fx-panel.is-live,
.sq-track .sq-track__eq-panel.is-live,
.sq-track .sq-track__comp-panel.is-live,
.sq-track .sq-track__mod-panel.is-live { padding: 4px 10px; }
.sq-track .sq-track__mod-panel.is-live .sq-mod__add-row { display: none; }
.sq-track .sq-track__filter-panel.is-live { border-left: 3px solid var(--accent-2); }
.sq-track .sq-track__env-panel.is-live    { border-left: 3px solid #d5a6ff; }
.sq-track .sq-track__eq-panel.is-live     { border-left: 3px solid #ffa94d; }
.sq-track .sq-track__comp-panel.is-live   { border-left: 3px solid #66d9b4; }
/* The generators are cards while live. Their own panel rules cap the width for
   the modal (460 / 520px); in the grid the column decides. */
.sq-track .sq-track__euclid-panel.is-live { border-left: 3px solid #8ab4ff; max-width: none; }
.sq-track .sq-track__chance-panel.is-live { border-left: 3px solid #ff8fb1; max-width: none; }
/* The rack and the mod matrix read off the track's own identity colour
   (--track-hue, set per nth-child above) instead of a fixed accent, and no
   longer as a left-edge stripe: the whole border takes the colour, so a card
   reads as "this track's fx" or "this track's mod" rather than singling out
   one side. Neither sits exactly on the track's own hue — each nudges a
   little either way round the wheel, so both still read as that track's
   colour family while staying tellable apart from one another. */
.sq-track .sq-track__fx-panel.is-live  { border-color: hsl(calc(var(--track-hue, 200) - 16) 65% 60%); }
.sq-track .sq-track__mod-panel.is-live { border-color: hsl(calc(var(--track-hue, 200) + 16) 65% 60%); }
.sq-track__env    { --badge: #d5a6ff; }
.sq-track__fx     { --badge: var(--accent); }
.sq-track__eq     { --badge: #ffa94d; }
.sq-track__comp   { --badge: #66d9b4; }
.sq-track__mod    { --badge: var(--accent); }
.track-aut        { --badge: var(--accent-2); }

.sq-track__mod[aria-pressed="true"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}

.sq-track__mod-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}

.sq-track__mod-panel[hidden] { display: none; }

.sq-track__aut-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 6px;
}
.sq-track__aut-panel[hidden] { display: none; }
.sq-aut__empty { color: var(--fg-dim); font-size: 11px; padding: 4px 0; }
.sq-aut__add-row { display: flex; gap: 6px; align-items: center; margin-top: 4px; padding-top: 6px; border-top: 1px solid var(--border); }
.sq-aut__add-btn { font-size: 11px; padding: 4px 10px; }
.sq-aut__add-select { font-size: 11px; padding: 3px 6px; max-width: 220px; }
.sq-aut__lane {
  display: grid;
  grid-template-columns: 120px 28px 1fr 52px 24px;
  gap: 8px;
  align-items: center;
  padding: 2px 0;
}
.sq-aut__lane .sq-aut__label { color: var(--fg-dim); font-size: 11px; text-transform: lowercase; }
.sq-aut__lane.is-active .sq-aut__label { color: var(--accent); font-weight: 600; }
.sq-aut__lane .sq-aut__enable { accent-color: var(--accent); }
.sq-aut__lane .sq-aut__clear { font-size: 10px; padding: 2px 6px; }
.sq-aut__lane .sq-aut__remove {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-size: 13px;
  line-height: 1;
  width: 22px; height: 22px;
  border-radius: 4px;
  cursor: pointer;
  padding: 0;
}
.sq-aut__lane .sq-aut__remove:hover { color: var(--danger); }
.sq-aut__grid {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 1px;
  height: 96px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 2px;
  cursor: crosshair;
  user-select: none;
}
.sq-aut__step {
  position: relative;
  background: transparent;
  border-radius: 1px;
  overflow: hidden;
}
.sq-aut__step::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: calc(var(--v, 0) * 100%);
  background: var(--accent);
  opacity: 0.85;
}
.sq-aut__lane.is-active .sq-aut__step::after { background: var(--accent); }
.sq-aut__lane:not(.is-active) .sq-aut__step::after { background: var(--fg-dim); opacity: 0.4; }
.sq-aut__step.is-playing { box-shadow: inset 0 0 0 1px var(--accent-2); }

.sq-track__roll-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 6px;
}
.sq-track__roll-panel[hidden] { display: none; }
.sq-track__roll[aria-pressed="true"] {
  background: #8ec7ff;
  color: #0b0d10;
  border-color: #8ec7ff;
}
.sq-track__fx-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}
.sq-track__fx-panel[hidden] { display: none; }
.sq-track__fx[aria-pressed="true"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}

.sq-track__filter-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}
.sq-track__filter-panel[hidden] { display: none; }
.sq-track__filter[aria-pressed="true"] {
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
}
.sq-track__env-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}
.sq-track__env-panel[hidden] { display: none; }
.sq-track__env[aria-pressed="true"] {
  background: #d5a6ff;
  color: #0b0d10;
  border-color: #d5a6ff;
}
.sq-fx__row[data-fx="filter"] .sq-fx__title { color: var(--accent-2); }
.sq-fx__row[data-fx="env"] .sq-fx__title { color: #d5a6ff; }
.sq-fx__row[data-fx="eq"] .sq-fx__title { color: #ffa94d; }

.sq-track__eq-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}
.sq-track__eq-panel[hidden] { display: none; }
.sq-track__eq[aria-pressed="true"] {
  background: #ffa94d;
  color: #0b0d10;
  border-color: #ffa94d;
}

.sq-track__comp-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 4px;
}
.sq-track__comp-panel[hidden] { display: none; }
.sq-track__comp[aria-pressed="true"] {
  background: #66d9b4;
  color: #0b0d10;
  border-color: #66d9b4;
}
.sq-fx__row[data-fx="comp"] .sq-fx__title { color: #66d9b4; }
.sq-track__comp-panel select.sq-comp__source { min-width: 110px; }

.sq-fx__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  flex-wrap: wrap;
}
.sq-fx__row .sq-fx__title {
  color: var(--fg-dim);
  text-transform: lowercase;
  font-size: 11px;
  letter-spacing: 0.08em;
  min-width: 120px;
}
/* Effect names double-click to reset that effect (see wireFxPanel). */
.sq-fx__title.is-resettable { cursor: pointer; }
.sq-fx__title.is-resettable:hover { text-decoration: underline dotted; }

/* The × that takes a stage off the track (wireFxPanel). Only on the inline
   view: in the modal the rack is all thirteen stages whatever their level, so
   there is nothing there to remove. `margin-left: auto` puts it at the far end
   of the row rather than against the last knob, where it would read as part of
   that control. */
.sq-fx__off { display: none; }
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__off,
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__off,
.sq-track .sq-track__env-panel.is-live > .sq-fx__row > .sq-fx__off {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  min-width: 22px;
  height: 22px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--fg-dim);
  background: none;
  border-color: transparent;
}
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__off:hover,
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__off:hover,
.sq-track .sq-track__env-panel.is-live > .sq-fx__row > .sq-fx__off:hover {
  color: var(--danger);
  border-color: var(--danger);
}
/* ‹ › move a stage along the chain (addFxRowControls). Inline only, like the
   ×, and just before it; the name is also a drag handle. */
.sq-fx__move { display: none; }
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__move,
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__move {
  display: inline-flex;
  margin-left: auto;
}
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__move + .sq-fx__off,
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__move + .sq-fx__off { margin-left: 0; }
.sq-fx__mv {
  min-width: 22px;
  height: 22px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--fg-dim);
  background: none;
  border-color: transparent;
}
.sq-fx__mv:hover { color: var(--fg); border-color: var(--border); }
.sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__title[draggable="true"],
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__title[draggable="true"] { cursor: grab; }
.sq-fx__row.is-dragging { opacity: 0.5; }
.sq-fx__row[data-drop-at="before"] { box-shadow: inset 3px 0 0 var(--accent); }
.sq-fx__row[data-drop-at="after"] { box-shadow: inset -3px 0 0 var(--accent); }
/* A finger needs the room even where the row is drawn small. */
@media (any-pointer: coarse), (max-width: 768px) {
  .sq-fx__mv { min-width: 44px; height: 44px; font-size: 18px; }
  .sq-track .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__off,
  .sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row.is-live > .sq-fx__off,
  .sq-track .sq-track__env-panel.is-live > .sq-fx__row > .sq-fx__off {
    min-width: 44px;
    height: 44px;
    font-size: 18px;
  }
}
/* The inline panels — filter, env, eq, comp, the rack's engaged stages and
   the mod matrix's enabled entries — share one wrapper (`.sq-track__live`,
   studioMarkup.ts) and on desktop it is a grid of cards, three across at
   most. Stacked, a track with a filter, an envelope and two stages on it was
   four full-width lines each carrying a few knobs at its left end and
   nothing across the rest — more height than the step grid. The column
   minimum is a third of the wrapper (`auto-fill`), so a wide track never
   goes past three, a narrow one drops to two or one, and a single card gets
   one column rather than the whole track.

   Neither the rack nor the mod matrix becomes one card: each panel goes
   `display: contents` so every engaged STAGE, and every enabled LFO ROW, is
   a card of its own beside the filter's. An LFO row is a fixed eight-column
   grid everywhere else it appears (the modal, the phone block) — here alone
   it switches to the rack row's flex-wrap, since a 250px card has no room
   for eight rigid columns. Both keep their controls queried and wired the
   same; only where the title breaks and where the ×/remove sits changes:
   name on the card's top line, controls wrapping under. Scoped to the
   inline view (`.sq-track ... .is-live`), as the row filter above is: in the
   modal each panel is the whole panel again, and below 768px the six sound
   panels are not inline at all (the mobile block hides them; each is a
   modal from its button there), only a live generator's card.

   `gap: inherit` takes the track's own gap, so the wrapper is invisible in
   the stack; with nothing live in it (or every panel away in a modal) it is
   hidden outright, or its empty self would cost a gap. */
.sq-track__live { display: grid; gap: inherit; }
.sq-track__live:not(:has(> .is-live)) { display: none; }
@media (min-width: 769px) {
  .sq-track__live {
    grid-template-columns: repeat(auto-fill, minmax(max(250px, calc((100% - 20px) / 3)), 1fr));
    align-items: start;
  }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live { display: contents; }
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live { display: contents; }
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live > .sq-mod__rows { display: contents; }
  /* A card: the fx panel's own stages, the mod matrix's own rows, and the
     four one-panel cards. */
  .sq-track .sq-track__live > .sq-track__filter-panel.is-live,
  .sq-track .sq-track__live > .sq-track__env-panel.is-live,
  .sq-track .sq-track__live > .sq-track__eq-panel.is-live,
  .sq-track .sq-track__live > .sq-track__comp-panel.is-live,
  .sq-track .sq-track__live > .sq-track__euclid-panel.is-live,
  .sq-track .sq-track__live > .sq-track__chance-panel.is-live,
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live,
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live {
    position: relative;
    padding: 6px 8px;
    background: var(--bg-3);
    border-radius: 6px;
    min-height: 100%;
    box-sizing: border-box;
  }
  /* The chance card carries twelve faders and the part drawn out, which is
     more than a third of the track can hold; the ring fits in one column. */
  .sq-track .sq-track__live > .sq-track__chance-panel.is-live { grid-column: span 2; }
  /* The four panels keep the coloured border their own rules give them; a
     stage or LFO card takes the rack's / mod matrix's, since the panel that
     carried it is now `contents`. The right padding is the ×/remove's room. */
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live,
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    align-content: flex-start;
    gap: 6px 10px;
    padding-right: 30px;
    border: 1px solid var(--border);
  }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live {
    border-color: hsl(calc(var(--track-hue, 200) - 16) 65% 60%);
  }
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live {
    border-color: hsl(calc(var(--track-hue, 200) + 16) 65% 60%);
  }
  /* `.sq-field { width: 100% }` fills a grid column everywhere else the LFO
     row appears (the modal, the phone block) — inside this card's flex-wrap
     it forced each field (rate, amount, phase) onto a line of its own,
     stacking the knobs the fx cards next to it keep side by side. */
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live .sq-field {
    width: auto;
  }
  /* Length, amount and phase used to compete for whatever was left of the
     enable/shape/sync line, so a long shape name (rnd square) or target
     could leave only room for one of the three and split it from the other
     two. The same zero-height, full-basis pseudo the track head uses to
     force a line break puts them on a line of their own instead. */
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live::before {
    content: "";
    order: 4;
    flex-basis: 100%;
    height: 0;
  }
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__rate-field,
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__amt-field,
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__phase-field {
    order: 5;
  }
  .sq-track .sq-track__live > .sq-track__filter-panel.is-live > .sq-fx__row,
  .sq-track .sq-track__live > .sq-track__env-panel.is-live > .sq-fx__row,
  .sq-track .sq-track__live > .sq-track__eq-panel.is-live > .sq-fx__row,
  .sq-track .sq-track__live > .sq-track__comp-panel.is-live > .sq-fx__row { gap: 6px 10px; padding: 0; }
  /* The envelope keeps a live × like an fx stage — its row alone needs the
     right-hand room that reserves. */
  .sq-track .sq-track__live > .sq-track__env-panel.is-live > .sq-fx__row {
    padding-right: 30px;
  }
  .sq-track .sq-track__live > .sq-track__filter-panel.is-live > .sq-fx__row > .sq-fx__title,
  .sq-track .sq-track__live > .sq-track__env-panel.is-live > .sq-fx__row > .sq-fx__title,
  .sq-track .sq-track__live > .sq-track__eq-panel.is-live > .sq-fx__row > .sq-fx__title,
  .sq-track .sq-track__live > .sq-track__comp-panel.is-live > .sq-fx__row > .sq-fx__title,
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__title,
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__target {
    flex: 1 1 100%;
    min-width: 0;
  }
  /* The compressor's second row has no name: an empty title would still hold
     a line under the card's top. */
  .sq-track .sq-track__live > .sq-track__comp-panel.is-live > .sq-fx__row > .sq-fx__title:empty { display: none; }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__off,
  .sq-track .sq-track__live > .sq-track__env-panel.is-live > .sq-fx__row > .sq-fx__off {
    position: absolute;
    top: 4px;
    right: 4px;
    margin-left: 0;
  }
  .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__remove {
    position: absolute;
    top: 4px;
    right: 4px;
  }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__move {
    position: absolute;
    top: 4px;
    right: 28px;
    margin-left: 0;
  }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__title { padding-right: 40px; }
  .sq-fx__row[data-drop-at] { box-shadow: none; }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row[data-drop-at="before"] { box-shadow: -4px 0 0 var(--accent); }
  .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row[data-drop-at="after"] { box-shadow: 4px 0 0 var(--accent); }
  /* A touchscreen laptop keeps the grid and gets the 44px ×/remove from the
     rule above, which needs the room a 22px one did not. */
  @media (any-pointer: coarse) {
    .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live,
    .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live,
    .sq-track .sq-track__live > .sq-track__env-panel.is-live > .sq-fx__row { padding-right: 48px; }
    .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__move { right: 48px; }
    .sq-track .sq-track__live > .sq-track__fx-panel.is-live > .sq-fx__row.is-live > .sq-fx__title { padding-right: 88px; }
    .sq-track .sq-track__live > .sq-track__mod-panel.is-live .sq-lfo__row.is-live > .sq-lfo__remove {
      width: 44px;
      height: 44px;
      font-size: 18px;
    }
  }
}
.sq-fx__row[data-fx="gain"] .sq-fx__title { color: #f0e68c; }
.sq-fx__row[data-fx="pan"] .sq-fx__title { color: #8fd3a8; }
.sq-fx__row[data-fx="vinyl"] .sq-fx__title { color: #c79a74; }
.sq-fx__row[data-fx="cassette"] .sq-fx__title { color: #d9c07a; }
.sq-fx__row[data-fx="fuzz"] .sq-fx__title { color: #ff7a7a; }
.sq-fx__row[data-fx="ringmod"] .sq-fx__title { color: #e0a8ff; }
.sq-fx__row[data-fx="autowah"] .sq-fx__title { color: #ffd166; }
.sq-fx__row[data-fx="chorus"] .sq-fx__title { color: #9fd7ff; }
.sq-fx__row[data-fx="phaser"] .sq-fx__title { color: #b69cff; }
.sq-fx__row[data-fx="flanger"] .sq-fx__title { color: #7adfc8; }
.sq-fx__row[data-fx="pitchshift"] .sq-fx__title { color: #ffaad5; }
.sq-fx__row[data-fx="repeat"] .sq-fx__title { color: #ff9f6b; }
.sq-fx__row[data-fx="delay"] .sq-fx__title { color: var(--accent-2); }
.sq-fx__row[data-fx="reverb"] .sq-fx__title { color: var(--accent); }
.sq-fx__ctl {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--fg-dim);
}
.sq-fx__ctl span { min-width: 30px; font-size: 11px; }
.sq-fx__ctl .sq-knob { --knob-size: 38px; }
.sq-fx__sync-wrap { gap: 4px; }

.sq-lfo__row {
  display: grid;
  grid-template-columns: 52px 52px 96px 62px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 28px;
  gap: 10px;
  align-items: center;
  padding: 2px 0;
}

.sq-lfo__sync-wrap { color: var(--fg-dim); display: inline-flex; align-items: center; gap: 4px; }
/* One slot, two controls: synced, the length knob picks a cycle off the tempo;
   free-running, the rate knob picks hz. Both are knobs now, so the wrapper has
   to go with the input — hiding the input alone would leave its dial behind. */
.sq-lfo__rate-field[data-mode="sync"] .sq-lfo__rate,
.sq-lfo__rate-field[data-mode="sync"] .sq-knob:has(.sq-lfo__rate),
.sq-lfo__rate-field[data-mode="hz"] .sq-lfo__div,
.sq-lfo__rate-field[data-mode="hz"] .sq-knob:has(.sq-lfo__div) { display: none; }

/* The amount's reading and its polarity switch share a line under the knob:
   how much, and which side of the slider it sits. */
.sq-lfo__depth-line { display: inline-flex; align-items: center; gap: 6px; }
.sq-lfo__bip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--fg-dim);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.sq-lfo__bip input { margin: 0; }
.sq-lfo__bip:has(input:checked) { color: var(--accent); }

.sq-lfo__row.is-active .sq-lfo__target { color: var(--accent); font-weight: 600; }
.sq-lfo__row .sq-lfo__target { color: var(--fg-dim); text-transform: lowercase; }
.sq-lfo__row .sq-lfo__enable { color: var(--fg-dim); display: inline-flex; align-items: center; gap: 4px; }
.sq-lfo__row .sq-field { width: 100%; }
.sq-lfo__row .sq-field .sq-knob { --knob-size: 34px; }
.sq-lfo__row .sq-lfo__rate-label,
.sq-lfo__row .sq-lfo__phase-label,
.sq-lfo__row .sq-lfo__depth-label { color: var(--fg-dim); font-size: 11px; min-width: 56px; }
.sq-lfo__row .sq-lfo__depth-line .sq-lfo__depth-label { min-width: 32px; text-align: right; }
.sq-lfo__row .sq-lfo__phase-label { min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }

.sq-track__name {
  width: 120px;
  font-weight: 600;
}

.sq-track__engine { min-width: 160px; }
/* The instrument picker's button (enginePicker.js), standing in for the hidden
   engine select. Drawn like a select so it still reads as "choose one". */
.sq-track__engine-btn {
  min-width: 160px;
  max-width: 240px;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sq-track__engine-btn:hover { border-color: var(--fg-dim); }
.sq-track__engine-btn.is-jam-touched { box-shadow: 0 0 0 2px var(--jam-color, var(--accent)); }
.sq-engine-btn__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.sq-engine-btn__caret { color: var(--fg-dim); font-size: 10px; flex: none; }

/* The picker itself: search, group chips, then a grid of cards per group. */
.sq-modal.sq-engine-picker {
  width: min(780px, calc(100vw - 32px));
  max-width: none;
  max-height: min(86vh, 820px);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  min-width: 0;
}
.sq-engine-picker__top { display: flex; align-items: center; gap: 12px; }
.sq-engine-picker__top .sq-modal__title { flex: none; }
.sq-engine-picker__search {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  font: inherit;
  outline: none;
}
.sq-engine-picker__search:focus { border-color: var(--accent); }
.sq-engine-picker__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.sq-engine-picker__chip {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.sq-engine-picker__chip:hover { color: var(--fg); }
.sq-engine-picker__chip.is-on { color: #0b0d10; background: var(--accent); border-color: var(--accent); }
.sq-engine-picker__body {
  position: relative;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  gap: 14px;
  align-content: start;
  padding-right: 2px;
}
.sq-engine-picker__group[hidden],
.sq-engine-picker__divider[hidden],
.sq-engine-picker__card[hidden],
.sq-engine-picker__empty[hidden] { display: none !important; }
.sq-engine-picker__heading {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 400;
  color: var(--fg-dim);
  text-transform: lowercase;
  letter-spacing: 0.04em;
}
.sq-engine-picker__divider {
  margin: 6px 0 -4px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  text-transform: lowercase;
  letter-spacing: 0.04em;
}
.sq-engine-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 6px;
}
.sq-engine-picker__card {
  display: grid;
  gap: 3px;
  align-content: start;
  text-align: left;
  padding: 8px 10px;
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}
.sq-engine-picker__card:hover,
.sq-engine-picker__card:focus-visible { border-color: var(--accent-2); outline: none; }
.sq-engine-picker__card.is-current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.sq-engine-picker__card.is-current .sq-engine-picker__name { color: var(--accent); }
.sq-engine-picker__name { font-size: 12px; font-weight: 600; }
.sq-engine-picker__empty { color: var(--fg-dim); font-size: 12px; padding: 12px 0; }
/* "14 tones ›" on an engine card: a jump to that engine's presets. */
.sq-engine-picker__jump {
  justify-self: start;
  margin-top: 2px;
  font-size: 10.5px;
  color: var(--accent-2);
  cursor: pointer;
}
.sq-engine-picker__jump:hover { text-decoration: underline; }
.sq-engine-picker__card.is-preset { min-height: 0; background: var(--bg-2); }
.sq-engine-picker__card.is-preset .sq-engine-picker__name { font-weight: 400; }
.sq-track__len { width: 56px; }

.sq-scale__field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--fg-dim);
}
.sq-scale__toggle { display: inline-flex; align-items: center; gap: 4px; }
.sq-scale__toggle[hidden] { display: none !important; }
#scale-root, #scale-mode { padding: 4px 6px; }

.sq-track__midi {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg-dim);
  align-self: flex-start;
}
.sq-track__midi[hidden] { display: none; }
.sq-track__midi select { min-width: 180px; }
.sq-track__midi input[type="number"] { width: 56px; }


.sq-track__save:disabled { opacity: 0.35; cursor: not-allowed; }

/* Pattern lock: this track's sound belongs to the pattern (patternSound.js). */
.sq-track__plock[aria-pressed="true"] {
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
}
.sq-track.is-plocked { border-color: var(--accent-2); }

.sq-track__solo[aria-pressed="true"],
.sq-track__mute[aria-pressed="true"] {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}
.sq-track.is-soloed { border-color: var(--accent); }

/* Mute and solo say opposite things about the same track, so a track holding
   one has the other's button disabled rather than letting them contradict. */
.sq-track__solo:disabled, .sq-track__mute:disabled { opacity: 0.35; cursor: not-allowed; }

/* The computer keyboard is always active; the targeted track is highlighted. */
.sq-kbd-icon { display: inline-flex; align-items: center; color: var(--fg-dim); cursor: help; }
.sq-kbd-oct { font-size: 11px; color: var(--fg-dim); font-variant-numeric: tabular-nums; min-width: 22px; text-align: center; }
.sq-kbd-chord { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--fg-dim); }
.sq-kbd-chord[hidden] { display: none !important; }
#kbd-record:disabled, #kbd-capture:disabled, .sq-track__wav:disabled { opacity: 0.35; cursor: not-allowed; }
.sq-kbd-chord[hidden] { display: none; }
.sq-kbd-chord select { font-size: 11px; padding: 2px 4px; }
/* Each control's own label. Hidden by default: on desktop the cluster is one
   left-to-right row where "chord" covers the lot. They appear only inside the
   mobile chord modal, which stacks the controls and needs each one named. */
.sq-kbd-chord__f { display: inline-flex; align-items: center; gap: 4px; }
.sq-kbd-chord__sub { display: none; }
/* Arp settings for keyboard chords — only shown while chord mode is on, and the
   rate/range/direction only once arp itself is on (see syncKbdArpUI). */
.sq-kbd-arp, .sq-kbd-arp__opts { display: inline-flex; align-items: center; gap: 5px; }
.sq-kbd-arp[hidden], .sq-kbd-arp__opts[hidden] { display: none !important; }
.sq-kbd-arp__toggle { display: inline-flex; align-items: center; gap: 3px; cursor: pointer; }
.sq-kbd-arp select { font-size: 11px; padding: 2px 4px; }
#kbd-record .sq-btn-icon circle { fill: #e5484d; }
#kbd-record[aria-pressed="true"] { background: #e5484d; border-color: #e5484d; }
#kbd-record[aria-pressed="true"] .sq-btn-icon circle { fill: #fff; }
body.kbd-notes-on .sq-track.is-kbd-active { border-color: hsl(var(--track-hue, 200) 65% 60%); box-shadow: 0 0 0 1px hsl(var(--track-hue, 200) 65% 60%) inset; }
body.kbd-recording .sq-track.is-kbd-active { border-color: #e5484d; box-shadow: 0 0 0 1px #e5484d inset; }

.sq-track__glide { width: 70px; }

.sq-steps {
  display: grid;
  grid-template-columns: repeat(var(--count, 16), minmax(0, 1fr));
  /* Floor wrapped rows: a multi-bar pattern where a row contains only a
     single held cell has no aspect-ratio reference, so the row would
     otherwise collapse to the label height. Cells with aspect-ratio:1
     still grow rows past this floor when they need to. */
  grid-auto-rows: minmax(44px, auto);
  gap: 4px;
}

.sq-step {
  aspect-ratio: 1 / 1;
  /* Explicit inline size + start alignment. Without these, Safari resolves
     the square against the 44px grid-auto-rows floor instead of the column
     width (WebKit transfers the block size through aspect-ratio before
     applying inline-axis stretch) — cells render as small squares with big
     gaps and stop tracking the column. Chrome happened to stretch first. */
  width: 100%;
  align-self: start;
  background: var(--step);
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  position: relative;
  min-height: 28px;
  transition: background 60ms linear;
}

/* held cells span multiple columns; stop them from also doubling in height */
.sq-step.is-held { aspect-ratio: var(--hspan, 1) / 1; height: auto; align-self: stretch; }
.sq-steps { align-items: stretch; }

.sq-step.is-on {
  background: var(--note-color, var(--step-on));
  filter: brightness(calc(0.35 + var(--vel, 1) * 0.65));
}
.sq-step.is-beat { outline: 1px solid rgba(255,255,255,0.06); }
.sq-step.is-now { border-color: var(--step-now); box-shadow: 0 0 0 1px var(--step-now) inset; }
/* A nudged note's tick (stepGrid.js's markNudge, on the step grid; the piano
   roll draws a nudged note where it plays instead): where in the step it lands,
   50% being on the grid. Divided by the span so a held note's tick sits in its
   first step. */
.sq-steps .sq-step.is-on.is-nudged {
  --nudge-x: calc((0.5 + var(--nudge, 0)) * 100% / var(--hspan, 1));
  background-image: linear-gradient(to right,
    transparent calc(var(--nudge-x) - 1px), rgba(255,255,255,0.9) calc(var(--nudge-x) - 1px),
    rgba(255,255,255,0.9) calc(var(--nudge-x) + 1px), transparent calc(var(--nudge-x) + 1px));
}
/* Step input's cursor (keyboard.js): record armed, transport stopped. Marked on
   the active track's grid always, drawn only in that mode. */
body.kbd-recording:not(.sq-playing) .sq-track.is-kbd-active .sq-step.is-cursor {
  outline: 2px dashed #e5484d; outline-offset: -2px;
}

.sq-step.is-accent { background: #363b47; }
.sq-step.is-accent.is-on { background: var(--note-color, color-mix(in oklab, var(--step-on) 85%, #fff 15%)); }
.sq-step.is-accent::before {
  content: "";
  position: absolute;
  top: 0; left: 4px; right: 4px;
  height: 3px;
  background: var(--accent-2);
  opacity: 0.75;
  border-radius: 0 0 3px 3px;
}
.sq-step.is-accent.is-on::before { background: #0b0d10; opacity: 0.45; }

.sq-step.is-held {
  background: linear-gradient(
    90deg,
    var(--note-color, var(--step-on)) 0 calc(100% / var(--span, 1) - 2px),
    color-mix(in oklab, var(--note-color, var(--step-on)) 70%, transparent) calc(100% / var(--span, 1))
  );
  position: relative;
}
.sq-step.is-held::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: 3px;
  background: #0b0d10;
  opacity: 0.3;
  border-radius: 0 4px 4px 0;
}
.sq-step, .sq-step * { user-select: none; -webkit-user-select: none; }
.sq-steps { touch-action: none; }

.sq-step-editor {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  display: grid;
  gap: 8px;
  min-width: 300px;
  max-width: min(94vw, 860px);
  box-shadow: 0 12px 32px rgba(0,0,0,0.55);
  z-index: 100;
}
.sq-step-editor .sq-se__title {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.sq-step-editor .sq-se__field {
  display: grid;
  grid-template-columns: 60px 1fr 84px;
  gap: 10px;
  align-items: center;
}
.sq-step-editor .sq-se__field[hidden] { display: none; }
.sq-step-editor .sq-se__field > label:first-child { white-space: nowrap; }
.sq-step-editor .sq-se__field.sq-se__chord-field {
  grid-template-columns: 60px auto auto 1fr;
}
.sq-step-editor .sq-se__cpx-row { grid-template-columns: 60px 1fr; }
.sq-step-editor .sq-se__cpx-row select { width: 100%; }
.sq-step-editor .sq-se__arp-selects {
  display: flex;
  gap: 8px;
  grid-column: 2 / -1;
}
.sq-step-editor .sq-se__arp-selects select { flex: 1; min-width: 0; }
.sq-step-editor .sq-se__arp-row { grid-template-columns: 60px 1fr; }

.sq-step-editor .sq-se__note-field {
  grid-template-columns: 60px minmax(0, 1fr);
  grid-template-rows: auto auto;
  row-gap: 10px;
}
.sq-step-editor .sq-se__note-field > label:first-child { align-self: start; padding-top: 4px; }
.sq-step-editor .sq-se__note-header {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.sq-step-editor .sq-se__note-header .sq-se__note-label {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.04em;
}
.sq-step-editor .sq-se__oct-pager {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.04em;
}
.sq-step-editor .sq-se__oct-pager button {
  padding: 2px 8px;
  font-size: 10px;
}
.sq-step-editor .sq-se__oct-pager button[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}
.sq-step-editor .sq-se__oct-range { min-width: 60px; text-align: center; }
.sq-step-editor .sq-se__keyboard {
  --se-pad-size: 48px;
  grid-column: 1 / -1;
  grid-row: 2;
  display: grid;
  grid-template-columns: repeat(12, var(--se-pad-size));
  gap: 4px;
  padding: 4px;
  min-width: 0;
  max-width: 100%;
  justify-content: start;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 0;
  overflow-x: auto;
}
.sq-step-editor .sq-se__keyboard .sq-se__pad {
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-3));
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 0;
  font-size: 11px;
  font-family: ui-monospace, monospace;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  line-height: 1;
  min-width: 0;
  min-height: 0;
  aspect-ratio: 1 / 1;
  text-transform: lowercase;
}
.sq-step-editor .sq-se__keyboard .sq-se__pad.is-root {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
}
.sq-step-editor .sq-se__keyboard .sq-se__pad.is-selected {
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
}
.sq-step-editor .sq-se__keyboard .sq-se__pad:hover { filter: brightness(1.15); }

.sq-step-editor .sq-se__sample-row { grid-template-columns: 60px 1fr; }
.sq-step-editor .sq-se__sample-ctl {
  display: grid;
  gap: 6px;
  grid-column: 2 / -1;
  font-size: 11px;
  color: var(--fg-dim);
}
.sq-step-editor .sq-se__waveform {
  width: 100%;
  height: 72px;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: ew-resize;
  display: block;
  touch-action: none;
  background: #0e0f12;
}
.sq-step-editor .sq-se__sample-fade {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  font-size: 11px;
  color: var(--fg-dim);
}
.sq-step-editor .sq-se__sample-fade label {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.sq-step-editor .sq-se__sample-fade .sq-knob { --knob-size: 40px; }

.sq-step-editor .sq-se__sample-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.sq-step-editor .sq-se__smp-info {
  color: var(--fg-dim);
  font-size: 11px;
  flex: 1 1 100%;
}
.sq-step-editor .sq-se__smp-snap select,
.sq-step-editor .sq-se__smp-fit  select { padding: 2px 6px; font-size: 11px; }
.sq-step-editor .sq-se__preview {
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
  font-weight: 600;
  padding: 3px 10px;
}
.sq-step-editor .sq-se__chord-field .sq-se__chord-label {
  text-align: right;
  color: var(--fg-dim);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sq-step-editor .sq-se__field label { color: var(--fg-dim); }
.sq-step-editor .sq-se__field .sq-knob { --knob-size: 40px; }
.sq-step-editor .sq-se__field .sq-se__note-label,
.sq-step-editor .sq-se__field .sq-se__vel-label { color: var(--fg); font-size: 12px; text-align: right; }
.sq-step-editor .sq-se__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 2px;
}
.sq-step-editor .sq-se__arp-wrap,
.sq-step-editor .sq-se__cpx-wrap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--fg-dim);
  font-size: 11px;
}
.sq-step-editor .sq-se__cpx-wrap input { width: 44px; }
.sq-step-editor .sq-se__arp-wrap input:disabled + * { opacity: 0.4; }

.sq-step-editor .sq-se__close {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 600;
}

.sq-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  display: grid;
  place-items: center;
  z-index: 200;
}
.sq-modal {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px 22px;
  min-width: 360px;
  max-width: 520px;
  display: grid;
  gap: 12px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.6);
}
.sq-modal__title {
  color: var(--fg);
  font-size: 13px;
}
/* The message under a confirm dialog's title — the title asks, this says what
   it costs, so it reads as body text rather than a second question. */
.sq-modal__body {
  color: var(--fg-dim);
  font-size: 12px;
  line-height: 1.5;
}
.sq-modal__input {
  width: 100%;
  font: inherit;
}
.sq-modal__input--multiline {
  min-height: 110px;
  resize: vertical;
  line-height: 1.45;
}
.sq-modal__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* The euclid LFO shape's own controls — a second line inside the mod row,
   only there when the shape is euclid. The written-out ring stands in for the
   dial the track panel gets: the mod panel has no room for a circle, and
   "x..x..x." says the same thing on one line. */
.sq-lfo__euc {
  /* The row is a fixed seven-column grid; this is a second line under all of
     it, not an eighth cell. */
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  width: 100%;
  padding: 4px 2px 0;
  border-top: 1px solid var(--border);
}
.sq-lfo__euc[hidden] { display: none; }
.sq-lfo__euc-bits {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--accent);
  word-break: break-all;
  flex: 1 1 8ch;
  min-width: 8ch;
}
.sq-lfo__euc-f {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: 10px;
  line-height: 1.1;
  color: var(--fg-dim);
}
.sq-lfo__euc-f .sq-knob { --knob-size: 34px; }
.sq-lfo__euc-val { color: var(--fg); font-size: 11px; font-variant-numeric: tabular-nums; }

/* Euclidean rhythm panel. The ring is the point: evenness is visible on a
   circle and invisible on a line, so the panel leads with the circle and puts
   the tiled-out track strip underneath it. Lives on the track like the filter
   and the rack — its three counts are parameters, so they have to be in the
   DOM whether or not anyone has opened it. */
.sq-track__euclid-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 8px;
  max-width: 460px;
}
.sq-track__euclid-panel[hidden] { display: none; }
.track-euclid[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
/* Live: the track's rhythm is coming from here, not from the grid. Worth
   saying on the button, which is all you can see with the panel closed. */
.track-euclid.is-live { box-shadow: 0 0 0 1px var(--accent-2) inset; color: var(--accent-2); }

.sq-euclid__title { color: var(--fg); font-size: 12px; }
.sq-euclid__viz { display: flex; align-items: center; gap: 14px; }
.sq-euclid__ring { flex: 0 0 auto; }
.sq-euclid__orbit { fill: none; stroke: var(--border); stroke-width: 1.5; }
.sq-euclid__poly {
  fill: color-mix(in oklab, var(--accent) 16%, transparent);
  stroke: color-mix(in oklab, var(--accent) 60%, transparent);
  stroke-width: 1.5;
  stroke-linejoin: round;
}
.sq-euclid__node { fill: var(--fg-dim); opacity: 0.5; }
.sq-euclid__node.is-on { fill: var(--accent); opacity: 1; }
.sq-euclid__readout { display: grid; gap: 6px; min-width: 0; font-size: 12px; }
.sq-euclid__formula { color: var(--fg); font-size: 13px; }
.sq-euclid__bits {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.4;
  color: var(--accent);
  word-break: break-all;
}
.sq-euclid__hint { color: var(--fg-dim); font-size: 11px; line-height: 1.4; }
.sq-euclid__strip { display: flex; gap: 2px; }
.sq-euclid__cell {
  flex: 1 1 0;
  min-width: 0;
  height: 16px;
  border-radius: 2px;
  background: var(--step);
  border: 1px solid transparent;
}
.sq-euclid__cell.is-beat { border-color: var(--border); }
.sq-euclid__cell.is-on { background: var(--step-on); }
/* Where the cycle starts over — the one thing the ring cannot show. */
.sq-euclid__cell.is-cycle { box-shadow: -2px 0 0 var(--accent-2); }

/* Three knobs, like every other played control in the app. Counts, so the
   value is written under the dial rather than left to the drag bubble. */
.sq-euclid__ctls { display: flex; justify-content: center; gap: 22px; padding: 2px 0; }
.sq-euclid__f {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  line-height: 1.1;
  color: var(--fg-dim);
}
.sq-euclid__f .sq-knob { --knob-size: 46px; }
.sq-euclid__val {
  color: var(--fg);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
/* Something else is driving this count right now — an LFO, a lane or a pad.
   The knob deliberately stays where you left it (it is the base the modulation
   swings around), so without this the still knob beside a turning ring reads
   as a bug. */
.sq-euclid__f.is-driven > span { color: var(--accent-2); }
.sq-euclid__f.is-driven .sq-euclid__val { color: var(--accent-2); }

.sq-euclid__opts { display: flex; gap: 16px; align-items: center; font-size: 12px; color: var(--fg-dim); }
.sq-euclid__opts label { display: inline-flex; align-items: center; gap: 6px; }
.sq-euclid__actions { display: flex; gap: 12px; align-items: center; justify-content: space-between; }
.sq-euclid__live { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-dim); }
.sq-euclid__live:has(:checked) { color: var(--accent-2); }
.sq-euclid__write { font-size: 12px; }

/* A generator running live: the grid is showing a part nobody wrote, so it
   cannot also be the thing you edit — a click would land on a step the
   generator is about to overwrite on its next turn. Read-only and visibly so,
   until live goes off and the written pattern (untouched all along) comes back. */
.sq-track.is-euclid .sq-steps,
.sq-track.is-chance .sq-steps { pointer-events: none; opacity: 0.85; }
.sq-track.is-euclid .sq-step.is-on,
.sq-track.is-chance .sq-step.is-on {
  background-image: repeating-linear-gradient(
    135deg, transparent 0 5px, color-mix(in oklab, var(--accent-2) 30%, transparent) 5px 7px);
}

/* Chance panel. Two sections stacked, each with its own dice, and the generated
   part drawn above them as pitch against time — which is the only picture in
   which a rest, a tie and a leap all read at a glance (the ring's polygon says
   nothing here: there is no evenness to see). Lives on the track like the ring
   and the rack, because its six modulatable controls are parameters and have to
   be in the DOM whether or not anyone has opened it. */
.sq-track__chance-panel {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  display: grid;
  gap: 8px;
  max-width: 520px;
}
.sq-track__chance-panel[hidden] { display: none; }
.track-chance[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
/* Live: the track's part is coming from here, not from the grid. Worth saying on
   the button, which is all you can see with the panel closed. */
.track-chance.is-live { box-shadow: 0 0 0 1px var(--accent-2) inset; color: var(--accent-2); }

.sq-chance__title { color: var(--fg); font-size: 12px; }
/* The title shares its line with live and write: the two switches that decide
   what the panel is FOR, up where they are seen before anything is scrolled. */
.sq-chance__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 12px;
}
.sq-chance__readout { display: grid; gap: 4px; font-size: 11px; }
.sq-chance__summary { color: var(--fg); font-size: 12px; font-variant-numeric: tabular-nums; }
.sq-chance__hint { color: var(--fg-dim); font-size: 11px; line-height: 1.4; }

/* The throw. One column per step of the window; a note is a bar whose height is
   its pitch within the range and whose width is how long it is held. */
.sq-chance__roll {
  display: grid;
  /* The column count is written from JS (drawChanceViz) — it is the window's
     length, and `repeat()` will not take a custom property as its count. */
  grid-template-columns: repeat(16, minmax(0, 1fr));
  align-items: end;
  gap: 1px;
  height: 74px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--step);
}
.sq-chance__cell { min-width: 0; height: 100%; border-radius: 2px; }
/* A bare cell is a rest — faintly floored so the row still reads as steps. A
   step covered by a note already sounding gets no cell at all: that note's bar
   spans it (see drawChanceViz). */
.sq-chance__cell { background: linear-gradient(to top, var(--border) 0 1px, transparent 1px); }
.sq-chance__cell[data-beat="1"] { box-shadow: -1px 0 0 var(--border); }
/* A note. `--h` places the band in the range and `--span` is how many columns it
   holds; the transparent blocks above and below are what lift the band off the
   floor, so height and position are one gradient rather than a wrapper each. */
.sq-chance__cell.is-on {
  grid-column: span var(--span, 1);
  background:
    linear-gradient(to top,
      transparent 0 calc(var(--h, 50%) - 3px),
      var(--accent) calc(var(--h, 50%) - 3px) calc(var(--h, 50%) + 3px),
      transparent calc(var(--h, 50%) + 3px));
}
/* A ratcheted note is struck more than once inside its span — the triplets and
   the 1/32s, which a sixteenth grid can only say this way. The band is split
   into `--hits` segments, and the splitting layer is sized and placed to the
   band itself: full height it would rule the whole column and the row would
   read as texture rather than as notes. */
.sq-chance__cell.is-on[data-hits="2"],
.sq-chance__cell.is-on[data-hits="3"] {
  background-image:
    repeating-linear-gradient(90deg,
      transparent 0 calc(100% / var(--hits, 1) - 2px),
      var(--bg-3) calc(100% / var(--hits, 1) - 2px) calc(100% / var(--hits, 1))),
    linear-gradient(to top,
      transparent 0 calc(var(--h, 50%) - 3px),
      var(--accent-2) calc(var(--h, 50%) - 3px) calc(var(--h, 50%) + 3px),
      transparent calc(var(--h, 50%) + 3px));
  background-size: 100% 6px, 100% 100%;
  background-position: 0 calc(100% - var(--h, 50%) - 3px), 0 0;
  background-repeat: no-repeat, no-repeat;
}

/* A section head: which half of the machine, its dice, and how long a throw is held. */
.sq-chance__sec {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 2px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
  row-gap: 4px;
  font-size: 11px;
  color: var(--fg-dim);
}
.sq-chance__sec-t { color: var(--fg); letter-spacing: 0.06em; text-transform: uppercase; font-size: 10px; }
.sq-chance__sec button { font-size: 11px; }
/* The section's options sit beside its name; the dice and the hold go right. */
.sq-chance__sec > button { margin-left: auto; }
.sq-chance__throws { margin-left: auto; display: inline-flex; align-items: center; gap: 2px; }
.sq-chance__throws > button:disabled { opacity: 0.35; }
.sq-chance__pos { min-width: 3ch; color: var(--fg-dim); font-size: 10px; font-variant-numeric: tabular-nums; }
/* A dice with nothing to decide. Not `disabled`: the button is how you find out
   why, and on a touch screen its title is out of reach — pressing it puts the
   reason in the status bar. Same strike-through the unreachable semitones get. */
.sq-chance__sec button.is-dead { color: var(--fg-dim); text-decoration: line-through; }
.sq-chance__free { display: inline-flex; align-items: center; gap: 5px; }
.sq-chance__free select { font-size: 11px; }
/* A throw that moves on its own is marked, as the realtime switch it replaced was. */
.sq-chance__free:has(option:checked:not([value="0"])) { color: var(--accent-2); }
.sq-chance__free:has(option:checked:not([value="0"])) select { color: var(--accent-2); }

/* Knobs, like every other played control in the app, with the value written
   under the dial rather than left to the drag bubble — these read as words
   ("1/8T", "shorter 40%") and a bubble you have to hold the knob to see would
   hide exactly the thing you are setting. */
.sq-chance__ctls { display: flex; justify-content: center; gap: 18px; flex-wrap: wrap; padding: 2px 0; }
/* The melody's range and the window share a row, the window marked off by a
   rule and its name set sideways, so neither costs a line of its own. */
.sq-chance__grp { display: flex; gap: 18px; align-items: flex-start; }
.sq-chance__grp--window { padding-left: 14px; border-left: 1px solid var(--border); gap: 14px; }
.sq-chance__grp--window > .sq-chance__sec-t {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  align-self: center;
  color: var(--fg-dim);
}
.sq-chance__f {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  line-height: 1.1;
  color: var(--fg-dim);
}
.sq-chance__f .sq-knob { --knob-size: 42px; }
.sq-chance__val { color: var(--fg); font-size: 11px; font-variant-numeric: tabular-nums; }
/* Something else is driving this control right now — an LFO, a lane or a pad.
   The knob deliberately stays where you left it (it is the base the modulation
   swings around), so without this the still knob beside a changing part reads as
   a bug. */
.sq-chance__f.is-driven > span,
.sq-chance__f.is-driven .sq-chance__val { color: var(--accent-2); }

/* The twelve semitone probabilities. Sliders and not knobs (see KNOB_EXCLUDE in
   knob.js): side by side they are the pitch profile, and a row of dials would
   say nothing about which notes this part is made of. */
.sq-chance__keys {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 3px;
  padding: 4px 2px 0;
}
.sq-chance__key {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  flex: 1 1 0;
  min-width: 0;
  font-size: 9px;
  color: var(--fg-dim);
}
.sq-chance__key > input {
  -webkit-appearance: slider-vertical;
  writing-mode: vertical-lr;
  direction: rtl;
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
  height: 58px;
}
/* The black keys dimmer, so the row reads as a keyboard and you can find F♯
   without counting. */
.sq-chance__key.is-black { color: color-mix(in oklab, var(--fg-dim) 70%, transparent); }
/* Raised and reachable, versus raised and out of range — a fader asking for a
   note the range cannot play does nothing, and should look like it. */
.sq-chance__key.is-live > span { color: var(--accent); }
.sq-chance__key.is-dead > span { color: var(--danger); text-decoration: line-through; }

.sq-chance__opts {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  font-size: 11px;
  color: var(--fg-dim);
}
.sq-chance__opts label { display: inline-flex; align-items: center; gap: 5px; }
.sq-chance__opts button { font-size: 11px; }
.sq-chance__actions { display: flex; gap: 12px; align-items: center; margin-left: auto; }
.sq-chance__live { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-dim); }
.sq-chance__live:has(:checked) { color: var(--accent-2); }
.sq-chance__write { font-size: 12px; }

/* Wavetable editor modal. */
.sq-wt__modal { width: min(94vw, 720px); gap: 10px; }
.sq-wt__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--fg-dim); }
.sq-wt__title { color: var(--fg); }
.sq-wt__frames { display: flex; gap: 6px; flex-wrap: wrap; }
.sq-wt__thumb { border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.sq-wt__thumb.is-sel { border-color: var(--accent); }
.sq-wt__frames-ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--fg-dim); }
.sq-wt__frames-ctl select { font-size: 11px; padding: 2px 4px; }
.sq-wt__canvas { width: 100%; height: auto; border: 1px solid var(--border); border-radius: 6px; background: #0e0f12; touch-action: none; cursor: crosshair; }
.sq-wt__harm { display: flex; align-items: flex-end; justify-content: space-between; gap: 4px; height: 60px; width: 100%; }
.sq-wt__harm-bar { -webkit-appearance: slider-vertical; writing-mode: vertical-lr; flex: 1 1 0; min-width: 0; width: auto; height: 56px; }
.sq-wt__uni { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; font-size: 12px; color: var(--fg); }
.sq-wt__uni-wrap { display: flex; align-items: center; gap: 6px; }
.sq-wt__uni select { font-size: 11px; padding: 2px 4px; }
.sq-wt__uni-hint { font-size: 11px; color: var(--fg-dim); }
.sq-wt__scan { display: flex; flex-direction: column; gap: 8px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; }
.sq-wt__scan-en-wrap { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg); cursor: pointer; }
.sq-wt__scan-ctl { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--fg-dim); }
.sq-wt__scan-ctl[hidden] { display: none !important; }
.sq-wt__scan-ctl label { display: flex; align-items: center; gap: 4px; }
.sq-wt__scan-ctl select { font-size: 11px; padding: 2px 4px; }
.sq-wt__scan-ctl .sq-knob { --knob-size: 38px; }
.sq-wt__scan-rate-wrap[hidden], .sq-wt__scan-div-wrap[hidden] { display: none !important; }
.sq-wt__hint { font-size: 10px; color: var(--fg-dim); opacity: 0.8; }
.sq-wt__credit { font-size: 10px; color: var(--fg-dim); opacity: 0.65; }
.sq-wt__credit a { color: inherit; text-decoration: underline; }

/* Sample-editor + slicer modals. */
.sq-samp__modal { width: min(94vw, 720px); gap: 10px; }
.sq-samp__head, .sq-slice__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--fg-dim); }
.sq-samp__file { font: inherit; color: inherit; background: var(--bg-3); border: 1px solid var(--border); border-radius: 4px; padding: 1px 4px; max-width: 220px; }
.sq-samp__title { color: var(--fg); }
.sq-samp__canvas, .sq-slice__canvas { width: 100%; height: auto; border: 1px solid var(--border); border-radius: 6px; background: #0e0f12; touch-action: none; }
.sq-samp__row, .sq-slice__row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--fg-dim); }
.sq-samp__slice-row { border-top: 1px solid var(--border); padding-top: 8px; margin-top: 2px; }
.sq-samp__row select, .sq-slice__row select, .sq-slice__row input[type="number"] { font-size: 11px; padding: 2px 4px; }
.sq-samp__row .sq-knob { --knob-size: 38px; }
.sq-slice__pads { display: flex; flex-wrap: wrap; gap: 4px; max-height: 96px; overflow-y: auto; }
.sq-slice__pad { font-size: 10px; padding: 4px 6px; min-width: 34px; }
.sq-slice__hint { font-size: 10px; color: var(--fg-dim); opacity: 0.8; }

/* Sampler source picker modal: load-file button + bundled sample list. */
.sq-sampler__src-modal { width: min(92vw, 420px); }
/* Attribution line in a source picker (see the wavetable editor's credit). */
.sq-modal__credit { font-size: 10px; color: var(--fg-dim); text-align: center; margin-top: 8px; line-height: 1.5; }
.sq-modal__credit a { color: var(--fg-dim); }
.sq-sampler__src-sep { font-size: 11px; color: var(--fg-dim); text-align: center; }
.sq-sampler__src-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; max-height: 44vh; overflow-y: auto; }
.sq-sampler__src-item { font-size: 11px; padding: 6px 4px; }
.sq-sampler__src-kit { grid-column: 1 / -1; font-size: 10px; color: var(--fg-dim); margin-top: 4px; }
.sq-sampler__src-kit:first-child { margin-top: 0; }

/* Granular WAV viewer modal: waveform canvas + live grain bars. */
.sq-gwav__modal {
  width: min(94vw, 720px);
  max-width: 94vw;
  gap: 10px;
}
.sq-gwav__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 12px;
  color: var(--fg-dim);
}
.sq-gwav__file { font: inherit; color: inherit; background: var(--bg-3); border: 1px solid var(--border); border-radius: 4px; padding: 1px 4px; max-width: 220px; }
.sq-gwav__title { color: var(--fg); }
.sq-gwav__file { color: var(--fg-dim); }
.sq-gwav__anim { display: inline-flex; align-items: center; gap: 6px; }
.sq-gwav__canvas {
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #0e0f12;
}
.sq-gwav__info {
  font: 11px ui-monospace, monospace;
  color: var(--fg-dim);
}
.sq-gwav__hint { font-size: 10px; color: var(--fg-dim); opacity: 0.8; }
.sq-gwav__ctl { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--border); padding-top: 8px; }
.sq-gwav__ctl-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--fg-dim); }
.sq-gwav__ctl-row label { display: inline-flex; align-items: center; gap: 4px; }
.sq-gwav__ctl-row select { font-size: 11px; padding: 2px 4px; }
.sq-gwav__ctl-row .sq-knob { --knob-size: 38px; }

/* Audio gate dialog: shown on page load until the user taps to unlock
   audio. Sits above the rest of the UI so it must be the first interaction. */
.sq-audio__gate-overlay { z-index: 300; }
.sq-audio__gate-modal {
  text-align: center;
  padding: 24px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 380px;
}
.sq-audio__gate-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: 0.02em;
}
.sq-audio__gate-hint {
  font-size: 12px;
  color: var(--fg-dim);
  line-height: 1.5;
  min-height: 3.2em;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 180ms ease;
}
.sq-audio__gate-hint strong { color: var(--fg); }
.sq-audio__gate-btn {
  padding: 14px 18px;
  font-size: 14px;
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 700;
  min-height: 48px;
}
.sq-audio__gate-btn:disabled { opacity: 0.6; cursor: progress; }
/* The phone caveat: up top and in a box of its own, so it is read before the
   button rather than found under it. */
.sq-audio__gate-note {
  font-size: 14px;
  color: var(--fg);
  line-height: 1.4;
  padding: 10px 12px;
  border: 1px solid var(--accent-2);
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent-2) 10%, transparent);
}
.sq-audio__gate-note strong { color: var(--accent-2); }
.sq-audio__gate-status {
  font-size: 11px;
  color: var(--fg-dim);
  min-height: 14px;
}

/* Panel modal: hosts a reparented track panel (.sq-track__roll-panel,
   .sq-track__mod-panel, .sq-track__aut-panel) as a centered overlay. The panel keeps
   its existing render + event wiring; only its parent changes. */
.sq-panel__modal {
  width: min(96vw, 1100px);
  max-width: 96vw;
  min-width: 0;
  max-height: 92vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
}
.sq-panel__modal > .sq-track__roll-panel,
.sq-panel__modal > .sq-track__mod-panel,
.sq-panel__modal > .sq-track__aut-panel,
.sq-panel__modal > .sq-track__filter-panel,
.sq-panel__modal > .sq-track__env-panel,
.sq-panel__modal > .sq-track__fx-panel,
.sq-panel__modal > .sq-track__eq-panel,
.sq-panel__modal > .sq-track__comp-panel,
.sq-panel__modal > .sq-track__euclid-panel,
.sq-panel__modal > .sq-track__chance-panel {
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0;
  overflow-y: auto;
}
.sq-panel__modal .sq-panel__modal-close {
  align-self: stretch;
  padding: 10px;
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 600;
  min-height: 40px;
}
/* A heading over a panel that carries no name of its own (openPanelAsModal's
   `title`): the instrument modal says which track and engine it is. */
.sq-panel__modal-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Chord settings as a modal (the mobile route in — see openChordMenu). The
   panel is the transport's own element moved here, so everything below is a
   restyle of the same controls: stacked, named, and at a finger's size. */
.sq-chord__menu-modal {
  width: min(94vw, 420px);
  min-width: 0;
  max-height: 88vh;
  overflow-y: auto;
  gap: 12px;
}
.sq-chord__menu-modal .sq-kbd-chord,
.sq-chord__menu-modal .sq-kbd-arp,
.sq-chord__menu-modal .sq-kbd-arp__opts {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  width: 100%;
  font-size: 13px;
}
.sq-chord__menu-modal .sq-kbd-arp[hidden],
.sq-chord__menu-modal .sq-kbd-arp__opts[hidden] { display: none !important; }
/* The scale row, moved in beside the chord cluster: one line of the switch,
   root, mode and the note-colour toggle, each at a finger's size. */
.sq-chord__menu-modal .sq-scale__field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
  font-size: 13px;
}
.sq-chord__menu-modal .sq-scale__toggle { min-height: 40px; gap: 8px; padding-right: 4px; }
.sq-chord__menu-modal .sq-scale__toggle input { width: 20px; height: 20px; }
.sq-chord__menu-modal .sq-scale__field .sq-icon-btn { min-width: 44px; height: 40px; }
/* The chord cluster's own label heads its half of the sheet. */
.sq-chord__menu-modal .sq-kbd-chord__lbl {
  border-top: 1px solid var(--border);
  padding-top: 12px;
  color: var(--fg);
}
.sq-chord__menu-modal .sq-kbd-chord__f {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* One width for every label so the selects line up down the sheet — six
   controls each starting at a different x reads as six unrelated rows. */
.sq-chord__menu-modal .sq-kbd-chord__sub { display: inline; color: var(--fg-dim); flex: 0 0 auto; min-width: 66px; }
.sq-chord__menu-modal select {
  flex: 1 1 0;
  min-width: 0;
  font-size: 14px;
  padding: 8px 10px;
  min-height: 40px;
}
.sq-chord__menu-modal .sq-kbd-arp {
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
.sq-chord__menu-modal .sq-kbd-arp__toggle { min-height: 40px; gap: 8px; }
.sq-chord__menu-modal .sq-kbd-arp__toggle input { width: 20px; height: 20px; }
.sq-chord__menu-close {
  align-self: stretch;
  margin-top: 4px;
  padding: 10px;
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 600;
  min-height: 44px;
}

.euclid-modal { width: min(94vw, 460px); }
.chance-modal { width: min(94vw, 540px); }
/* the roll modal hosts the piano editor (pianoEditor.js) on one track: the
   scroller takes what the window leaves after the toolbar, the velocity lane
   and the close button */
.sq-roll__modal { height: 92vh; }
.sq-roll__modal > .sq-track__roll-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 0; overflow: hidden; }
.sq-roll__modal .sq-pr__bar, .sq-roll__modal .sq-pr__vel { flex: 0 0 auto; }
.sq-roll__modal .sq-pr__scroll { flex: 1 1 auto; min-height: 120px; }
.sq-mod__modal,
.sq-aut__modal {
  width: min(96vw, 820px);
}
.sq-filter__modal,
.sq-env__modal,
.sq-eq__modal,
.sq-comp__modal {
  width: min(96vw, 680px);
}
/* The fx button's picker (openFxAsModal): every stage by name, the ones on
   the track lit in their own colour. No controls here; a stage's knobs are
   on the track. */
.sq-fx__modal {
  width: min(96vw, 560px);
}
/* The instrument modal (openInstrumentAsModal): the track's synth row, moved
   here from the head. A phone's way to the instrument's controls, since the
   row is not inline on the track there (see the mobile block). Stacked, one
   group a line, scrolling inside; the hexop's operator grid scrolls sideways
   in its own panel as it does in the head. */
.sq-inst__modal { width: min(96vw, 900px); }
.sq-inst__modal > .sq-track__synth-row {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 6px;
  overflow-y: auto;
  max-height: calc(92vh - 110px);
  min-height: 0;
}
.sq-inst__modal > .sq-track__synth-row.is-empty::after {
  content: "nothing to set on this instrument";
  color: var(--fg-dim);
  font-size: 12px;
  padding: 12px 2px;
}
/* The phone's fx modal (openFxAsModal's phone branch): the rack panel moved
   here, filtered to the stages on the track exactly as the inline view
   filters it (same `is-live` rows, same × and ‹ ›), with the picker behind
   `add`. With no stage on the track the panel is hidden (refreshPanelBadges)
   and the line after it says so. */
.sq-fxrack__modal { width: min(96vw, 900px); }
.sq-fxrack__modal > .sq-track__fx-panel { max-height: calc(92vh - 150px); }
.sq-fxrack__modal > .sq-track__fx-panel > .sq-fx__row:not(.is-live) { display: none; }
.sq-fxrack__empty { display: none; color: var(--fg-dim); font-size: 12px; padding: 10px 2px; }
.sq-fxrack__modal > .sq-track__fx-panel[hidden] + .sq-fxrack__empty { display: block; }
.sq-fxrack__add {
  align-self: stretch;
  min-height: 44px;
  padding: 10px;
  font-weight: 600;
  border-style: dashed;
}
.sq-fxpick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  overflow-y: auto;
}
.sq-fxpick__btn {
  --fx-c: var(--fg-dim);
  min-height: 44px;
  padding: 8px 10px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg-dim);
  text-align: left;
  cursor: pointer;
}
.sq-fxpick__btn { display: flex; justify-content: space-between; gap: 6px; align-items: center; }
.sq-fxpick__n { font-size: 11px; opacity: 0.8; }
.sq-fxpick__btn:hover { border-color: var(--fx-c); color: var(--fx-c); }
.sq-fxpick__btn[aria-pressed="true"] {
  color: #0b0d10;
  background: var(--fx-c);
  border-color: var(--fx-c);
  font-weight: 600;
}
.sq-mod__modal > .sq-track__mod-panel,
.sq-aut__modal > .sq-track__aut-panel,
.sq-filter__modal > .sq-track__filter-panel,
.sq-env__modal > .sq-track__env-panel,
.sq-eq__modal > .sq-track__eq-panel,
.sq-comp__modal > .sq-track__comp-panel {
  max-height: calc(92vh - 80px);
}
.sq-bounce__opts { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.sq-bounce__opts label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-dim); }
.sq-bounce__opts input[type="number"] { width: 64px; }
.sq-bounce__opts input[type="text"] { width: 220px; }
.sq-bounce__note { color: var(--fg-dim); font-size: 11px; }
.sq-bounce__progress-label { font-size: 11px; color: var(--fg-dim); }
.sq-bounce__progress-bar { position: relative; height: 10px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.sq-bounce__progress-fill { position: absolute; inset: 0 auto 0 0; width: 0%; background: var(--accent); transition: width 80ms linear; }
.sq-bounce__progress-status { font-size: 11px; color: var(--fg-dim); }

.sq-patch__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; max-height: 40vh; overflow-y: auto; }
.sq-patch__list .sq-patch__row { display: flex; gap: 6px; align-items: stretch; }
.sq-patch__list .sq-patch__load { flex: 1; text-align: left; }
.sq-patch__eng { color: var(--fg-dim); opacity: 0.75; }
.sq-patch__list .sq-patch__del { padding: 4px 10px; }
.sq-patch__empty { color: var(--fg-dim); font-size: 12px; padding: 8px 0; }

.sq-track__mod-panel .sq-mod__ctl-row { display: flex; gap: 16px; padding-bottom: 4px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.sq-track__mod-panel .sq-mod__ctl { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--fg-dim); }
.sq-track__mod-panel .sq-mod__ctl .sq-knob { --knob-size: 40px; }
.sq-track__mod-panel .sq-mod__rows { display: grid; gap: 4px; }
.sq-track__mod-panel .sq-mod__add-row { display: flex; gap: 6px; align-items: center; margin-top: 4px; padding-top: 6px; border-top: 1px solid var(--border); }
.sq-track__mod-panel .sq-mod__add-btn { font-size: 11px; padding: 4px 10px; }
.sq-track__mod-panel .sq-mod__add-select { font-size: 11px; padding: 3px 6px; }
.sq-track__mod-panel .sq-lfo__row .sq-lfo__remove {
  padding: 0;
  font-size: 13px;
  line-height: 1;
  color: var(--fg-dim);
  width: 24px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sq-track__mod-panel .sq-lfo__row .sq-lfo__remove:hover { color: var(--danger); }
.sq-mod__hint { font-size: 11px; color: var(--fg-dim); opacity: 0.8; }

/* A dot beside a parameter's label when something is moving it: green for an
   lfo, blue for a live automation lane, grey for a lane that's switched off.
   Painted by refreshParamIndicators (paramTargets.js). */
[data-motion="mod"] { --motion-dot: var(--accent); }
[data-motion="aut"] { --motion-dot: var(--accent-2); }
[data-motion="off"] { --motion-dot: var(--fg-dim); }
/* A macro pad is the third thing that can own a parameter, alongside an lfo and
   an automation lane — so it gets its own dot for the same reason they do. */
[data-motion="macro"] { --motion-dot: #ff9ad5; }
/* `:not(.sq-knob)` because the knob's own wrapper is a span too, and a direct
   child of these labels — without it the dot is drawn twice, once beside the
   word and once floating on the dial. */
.sq-field[data-motion] > label::after,
.sq-fx__ctl[data-motion] > span:not(.sq-knob)::after,
.sq-contagion__f[data-motion] > span:not(.sq-knob)::after,
.sq-guitar__f[data-motion] > span:not(.sq-knob)::after,
.sq-bass__f[data-motion] > span:not(.sq-knob)::after,
.sq-sub__f[data-motion] > span:not(.sq-knob)::after,
.sq-drone__f[data-motion] > span:not(.sq-knob)::after,
.sq-vox__f[data-motion] > span:not(.sq-knob)::after,
.sq-lancet__f[data-motion] > span:not(.sq-knob)::after,
.sq-halo__f[data-motion] > span:not(.sq-knob)::after,
.sq-siege__f[data-motion] > span:not(.sq-knob)::after,
.sq-oracle__f[data-motion] > span:not(.sq-knob)::after,
.sq-hexop__f[data-motion]::after,
label[data-motion]:not(.sq-fx__ctl):not(.sq-contagion__f):not(.sq-guitar__f):not(.sq-bass__f):not(.sq-sub__f):not(.sq-drone__f):not(.sq-vox__f):not(.sq-lancet__f):not(.sq-halo__f):not(.sq-siege__f):not(.sq-oracle__f):not(.sq-hexop__f)::before {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--motion-dot);
  vertical-align: middle;
  margin: 0 0 1px 5px;
}
label[data-motion]:not(.sq-fx__ctl):not(.sq-contagion__f):not(.sq-guitar__f):not(.sq-bass__f):not(.sq-sub__f):not(.sq-drone__f):not(.sq-vox__f):not(.sq-lancet__f):not(.sq-halo__f):not(.sq-siege__f):not(.sq-oracle__f):not(.sq-hexop__f)::before { margin: 0 5px 1px 0; }
/* The hexop's operator cells have no label text of their own (the column heading
   names them), so the dot goes after the slider rather than beside a word. */
.sq-hexop__f[data-motion]::after { flex: 0 0 auto; margin: 0 0 0 3px; }
.sq-field[data-motion="off"] > label,
.sq-fx__ctl[data-motion="off"] > span:not(.sq-knob),
.sq-contagion__f[data-motion="off"] > span:not(.sq-knob),
.sq-guitar__f[data-motion="off"] > span:not(.sq-knob),
.sq-bass__f[data-motion="off"] > span:not(.sq-knob),
.sq-sub__f[data-motion="off"] > span:not(.sq-knob),
.sq-drone__f[data-motion="off"] > span:not(.sq-knob),
.sq-vox__f[data-motion="off"] > span:not(.sq-knob),
.sq-siege__f[data-motion="off"] > span:not(.sq-knob),
.sq-oracle__f[data-motion="off"] > span:not(.sq-knob),
.sq-lancet__f[data-motion="off"] > span:not(.sq-knob),
.sq-halo__f[data-motion="off"] > span:not(.sq-knob) { opacity: 0.8; }

/* Right-click a parameter → what is modulating / automating that one control. */
.sq-pmenu__modal { width: min(96vw, 900px); max-width: 96vw; gap: 10px; }
.sq-pmenu__sub { color: var(--fg-dim); }
.sq-pmenu__desc { font-size: 11px; color: var(--fg-dim); line-height: 1.5; max-width: 68ch; }
.sq-pmenu__code { font-size: 11px; color: var(--fg-dim); }
.sq-pmenu__code code { color: var(--accent); user-select: all; overflow-wrap: anywhere; }
.sq-pmenu__sec { display: grid; gap: 6px; padding-top: 8px; border-top: 1px solid var(--border); }
.sq-pmenu__sec-head { font-size: 11px; color: var(--fg-dim); text-transform: lowercase; }
.sq-pmenu__key { color: var(--accent); }
.sq-pmenu__body { display: grid; gap: 6px; }
.sq-pmenu__none { font-size: 11px; color: var(--fg-dim); }
.sq-pmenu__blocked { color: var(--fg-dim); opacity: 0.85; font-style: italic; }
.sq-pmenu__add { font-size: 11px; padding: 4px 10px; justify-self: start; }
/* Reset sits at the far end of the action row, away from `done`: it is the one
   button in this modal that throws something away. */
.sq-pmenu__reset { font-size: 11px; margin-right: auto; }
.sq-pmenu__macro-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; }
.sq-pmenu__macro-on { color: #ff9ad5; }
.sq-pmenu__macro-row input[type="number"] { width: 60px; padding: 3px 6px; font-size: 11px; }

/* ---------- MACRO PADS -----------------------------------------------------
   An XY surface per pad, each axis driving a list of parameters that may span
   tracks. The pad wants to be big — it is played, not adjusted — so it takes
   the left half on a desktop and the full width on a phone. */
.sq-macro__modal { width: min(96vw, 860px); max-width: 96vw; gap: 10px; }
/* Learn mode: the backdrop stops eating pointers so the control you are trying
   to assign is actually reachable, and the panel gets out of the middle of the
   screen. Only the modal keeps its own pointer events. */
.sq-modal-overlay.is-learning {
  pointer-events: none;
  background: transparent;
  align-content: end;
}
.sq-modal-overlay.is-learning .sq-macro__modal {
  pointer-events: auto;
  margin-bottom: 12px;
  box-shadow: 0 0 0 1px var(--accent), 0 10px 40px rgba(0, 0, 0, 0.7);
}
.sq-macro__head { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
.sq-macro__tabs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.sq-macro__tab { font-size: 11px; padding: 4px 10px; }
.sq-macro__tab.is-on { border-color: var(--accent); color: var(--accent); }
.sq-macro__add { font-size: 13px; padding: 2px 10px; line-height: 1; }
.sq-macro__body { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: 14px; align-items: start; }
.sq-macro__stage { display: grid; gap: 6px; }

.sq-macro__pad {
  position: relative;
  aspect-ratio: 1;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 8px;
  background:
    linear-gradient(to right, rgba(255,255,255,0.05) 1px, transparent 1px) 0 0 / 25% 100%,
    linear-gradient(to top, rgba(255,255,255,0.05) 1px, transparent 1px) 0 100% / 100% 25%,
    radial-gradient(circle at 50% 50%, rgba(194,240,74,0.06), transparent 70%),
    var(--bg-3);
  cursor: crosshair;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  overflow: hidden;
}
.sq-macro__pad.is-held { border-color: var(--accent); }
.sq-macro__cursor {
  position: absolute;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: rgba(194, 240, 74, 0.18);
  pointer-events: none;
}
/* Crosshair, so the position still reads when a finger covers the dot. */
.sq-macro__cursor::before,
.sq-macro__cursor::after {
  content: "";
  position: absolute;
  background: rgba(194, 240, 74, 0.35);
}
.sq-macro__cursor::before { left: 50%; top: -100vh; width: 1px; height: 200vh; }
.sq-macro__cursor::after { top: 50%; left: -100vw; height: 1px; width: 200vw; }
/* Dimmed unless held: in momentary mode the parameters have already sprung back
   by the time you see this, so a fully lit cursor would claim the pad is still
   doing something. A latched pad stays lit, because it is. */
.sq-macro__cursor { opacity: 0.4; }
.sq-macro__pad.is-held .sq-macro__cursor,
.sq-macro__pad.is-latched .sq-macro__cursor { opacity: 1; }
.sq-macro__pad.is-held .sq-macro__cursor { background: rgba(194, 240, 74, 0.32); }

.sq-macro__padfoot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; }
.sq-macro__latch { display: inline-flex; align-items: center; gap: 5px; color: var(--fg); }
.sq-macro__hint { color: var(--fg-dim); flex: 1 1 140px; min-width: 0; }
.sq-macro__dice { font-size: 11px; padding: 3px 8px; display: inline-flex; align-items: center; gap: 5px; }
.sq-macro__del { font-size: 11px; padding: 3px 8px; color: var(--danger); }

.sq-macro__axes { display: grid; gap: 12px; }
.sq-macro__axis { display: grid; gap: 5px; padding: 8px; border: 1px solid var(--border); border-radius: 6px; }
.sq-macro__axis-head { display: flex; align-items: center; gap: 8px; }
.sq-macro__axis-name { font-size: 12px; color: var(--accent-2); text-transform: uppercase; letter-spacing: 0.08em; }
.sq-macro__learn { font-size: 11px; padding: 3px 10px; margin-left: auto; }
.sq-macro__learn.is-on { border-color: var(--accent); color: var(--accent); }
.sq-macro__assign { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; }
.sq-macro__assign-name { flex: 1 1 140px; min-width: 0; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-macro__range { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-macro__range input[type="number"] { width: 58px; padding: 3px 6px; font-size: 11px; }
.sq-macro__inv { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); }
.sq-macro__rm { padding: 2px 8px; font-size: 13px; line-height: 1; color: var(--fg-dim); }
.sq-macro__empty { font-size: 11px; color: var(--fg-dim); font-style: italic; }
.sq-macro__note { font-size: 11px; color: var(--danger); }

@media (max-width: 768px) {
  /* The pad goes full width and the assignment lists sit under it — playing is
     the point, editing the assignments is the rare part. */
  .sq-macro__body { grid-template-columns: minmax(0, 1fr); }
  .sq-macro__pad { max-width: min(80vw, 420px); justify-self: center; }
  .sq-macro__assign-name { flex: 1 1 100%; }
}
.sq-pmenu__modal .sq-lfo__row { grid-template-columns: 52px 52px 88px 58px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 28px; }
.sq-pmenu__modal .sq-lfo__row .sq-lfo__rate-label { min-width: 92px; }
.sq-pmenu__modal .sq-lfo__row .sq-lfo__remove {
  padding: 0;
  font-size: 13px;
  line-height: 1;
  color: var(--fg-dim);
  width: 24px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sq-pmenu__modal .sq-lfo__row .sq-lfo__remove:hover { color: var(--danger); }
.sq-pmenu__modal .sq-aut__grid { height: 120px; }
.sq-modal__ok {
  background: var(--accent);
  color: #0b0d10;
  border-color: var(--accent);
  font-weight: 600;
}
.sq-modal__ok:disabled,
.sq-sound__preview:disabled,
.sq-sound__regen:disabled { opacity: 0.45; cursor: not-allowed; }
.sq-sound__preview {
  background: var(--accent-2);
  color: #0b0d10;
  border-color: var(--accent-2);
  font-weight: 600;
}
.sq-sound__dialog { min-width: 460px; max-width: 640px; }
.sq-sound__dialog-status {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 2px 2px;
}

.sq-step__note {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 10px;
  color: #0b0d10;
  pointer-events: none;
  font-weight: 700;
  opacity: 0.75;
}
.sq-step:not(.is-on) .sq-step__note { display: none; }

/* ============================================================ */
/* mobile / portrait-phone adaptations                          */
/* ------------------------------------------------------------ */
/* Everything below is mobile-only. No horizontal scroll: every */
/* row wraps within the viewport width.                          */
/* ============================================================ */

/* track-extras-toggle: hidden on desktop, visible on mobile */
.sq-mobile-only { display: none !important; }

@media (max-width: 768px) {
  html, body { font-size: 12px; }

  /* respect notch / home indicator on iOS */
  body { padding-bottom: env(safe-area-inset-bottom); }
  .sq-transport { padding-top: max(6px, env(safe-area-inset-top)); }

  /* show mobile-only elements (track "more" toggle etc.) */
  .sq-mobile-only { display: inline-flex !important; }

  /* ---------- TRANSPORT BAR ---------- */
  .sq-transport {
    padding: 6px 8px;
    gap: 6px;
    flex-wrap: wrap;
    align-items: center;
    row-gap: 4px;
  }
  /* Dissolve the desktop wrappers so the order-based mobile layout below applies
     to the controls directly, as it did before the wrappers existed. */
  .sq-transport__main, .sq-transport__right, .sq-transport__kbd-row { display: contents; }
  /* No physical keyboard on mobile — hide the computer-keyboard controls.
     The chord cluster is scoped to the transport rather than hidden outright:
     chord mode also decides what a TAPPED step writes, so #chord-menu-btn below
     hosts the very same panel in a modal, where these rules must not reach it.
     #kbd-octave needs no such escape and is hidden outright: it reads the base
     octave that z and x shift, and with the keys themselves gone it was a bare
     "C4" in the middle of the transport that nothing on a phone could change or
     act on. It is a sibling of #kbd-chord rather than inside it, so the modal
     does not carry it along. */
  #kbd-label, #kbd-record, #kbd-capture, #kbd-icon, #vim-toggle,
  #kbd-octave { display: none !important; }
  .sq-transport #kbd-chord, .sq-transport #kbd-arp { display: none !important; }
  .sq-transport .sq-logo { padding-right: 4px; gap: 6px; }
  .sq-transport .sq-logo img { width: 26px; height: 26px; }
  .sq-transport .sq-logo span { display: none; }

  .sq-transport button.sq-play {
    min-width: 76px;
    min-height: 44px;
    padding: 9px 14px;
    font-weight: 700;
    font-size: 13px;
  }

  .sq-transport .sq-field { gap: 4px; }
  .sq-transport .sq-field label { font-size: 10px; }
  .sq-transport input[type="number"] { width: 56px; min-height: 40px; padding: 5px 6px; }
  .sq-transport .sq-knob { --knob-size: 42px; }
  /* The transport's own icon buttons (metronome, note colours) to the same
     floor everything else in reach of a thumb gets. */
  .sq-transport .sq-icon-btn { min-width: 44px; height: 40px; }

  /* The scale row moves into the scale & chord modal on a phone (see
     #chord-menu-btn below and openChordMenu). Scoped to the transport so the
     same element, once moved into the modal, is shown again. */
  .sq-transport .sq-scale__field { display: none !important; }
  .sq-transport #add-track { order: 99; padding: 6px 10px; }
  /* Undo, redo, the scale & chord button, the metronome, the session menu and
     the macro pads are the transport's icon buttons on a phone (the keyboard cluster is hidden), and
     they are drawn like the track's dice / euclid / chance: icon over caption,
     50px square, so a thumb has a target and each says what it is without a
     hover. */
  .sq-transport .sq-history { gap: 4px; }
  /* Those six read as one group, so they break onto a line together rather
     than leaving undo on the play row and redo under it. They live in two
     different clusters on desktop (undo/redo in the main one, the metronome in
     the right one), so no wrapper can hold them without moving the metronome
     there too. Instead the transport's own ::before is a line break, the same
     trick as .sq-track__head::before: a zero-height flex item with a 100%
     basis, ordered in front of the group, which (with the clock and the meter
     after it) then always starts a line of its own. */
  .sq-transport::before { content: ""; flex: 0 0 100%; height: 0; order: 1; }
  .sq-transport .sq-history,
  .sq-transport #chord-menu-btn,
  .sq-transport__right > * { order: 1; }
  /* Macro sits in the main cluster, ahead of the others in the markup, so it
     is ordered after them explicitly: last in the group, after session. */
  .sq-transport #macro-pads { order: 2; }
  .sq-transport .sq-history button,
  .sq-transport #chord-menu-btn,
  .sq-transport #metronome,
  .sq-transport #pattern-menu-btn,
  .sq-transport #macro-pads {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    /* One line of six whatever the phone: 50px where it fits, narrower where
       it does not (a 320px screen gets ~46px). 44px is the transport's side
       padding (16) plus the five gaps between the six (28). A flex item
       wraps at its full size rather than shrinking first, so the width has
       to be worked out up front like this, not left to flex-shrink. */
    width: clamp(40px, calc((100vw - 44px) / 6), 50px);
    min-width: 0;
    height: auto;
    min-height: 50px;
    padding: 5px 2px;
    line-height: 1.1;
    text-transform: none;
  }
  .sq-transport .sq-history .sq-btn-icon,
  .sq-transport #chord-menu-btn .sq-btn-icon,
  .sq-transport #metronome .sq-btn-icon,
  .sq-transport #pattern-menu-btn .sq-btn-icon,
  .sq-transport #macro-pads .sq-btn-icon { width: 18px; height: 18px; }
  .sq-transport #macro-pads { display: inline-flex; }   /* not an .sq-icon-btn */
  .sq-transport #macro-pads .sq-btn-icon { display: block; }
  /* Its caption is the label span it carries for its desktop text, the way
     clear and roll do it. */
  .sq-transport #macro-pads .sq-btn__label { font-size: 10px; letter-spacing: 0; text-transform: lowercase; }
  .sq-transport .sq-history [data-label]::after,
  .sq-transport #chord-menu-btn[data-label]::after,
  .sq-transport #metronome[data-label]::after,
  .sq-transport #pattern-menu-btn[data-label]::after {
    content: attr(data-label);
    font-size: 10px;
    letter-spacing: 0;
    text-transform: lowercase;
    white-space: nowrap;
  }
  /* Above the metronome's volume fill, as its icon already is. */
  .sq-transport #metronome[data-label]::after { position: relative; }
  /* Its caption carries note names (C#, not c#). */
  .sq-transport #chord-menu-btn[data-label]::after { text-transform: none; }
  .sq-transport #chord-menu-btn[aria-pressed="true"] {
    background: var(--accent-2);
    color: #0b0d10;
    border-color: var(--accent-2);
  }
  .sq-transport .sq-spacer { display: none; }

  .sq-transport .sq-status {
    flex: 1 1 100%;
    order: 200;
    min-width: 0;
    text-align: left;
    font-size: 11px;
  }
  .sq-meter--master { width: 50px; height: 8px; }
  .sq-beat-indicator { width: 30px; height: 30px; }

  /* ---------- PATTERN BAR ----------
     Hidden on a phone: everything in it lives in the session menu modal
     (openPatternMenu), opened from #pattern-menu-btn in the transport's icon
     group. The modal moves the bar's children out and back, so the bar
     itself only has to exist, not show. */
  .sq-pattern-bar { display: none; }

  /* When the pattern menu modal is open, its children appear inside it.
     Layout matches what the bar used to look like, vertically stacked. */
  .sq-pattern__menu-modal {
    width: 96vw;
    max-width: 96vw;
    min-width: 0;
    max-height: 92vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px;
  }
  .sq-pattern__menu-modal #set-save,
  .sq-pattern__menu-modal #set-load,
  .sq-pattern__menu-modal #set-export,
  .sq-pattern__menu-modal #set-import,
  .sq-pattern__menu-modal #bounce-audio,
  .sq-pattern__menu-modal #bounce-track,
  .sq-pattern__menu-modal #pattern-dup {
    padding: 8px 10px;
    font-size: 12px;
    min-width: 0;
    min-height: 36px;
  }
  .sq-pattern__menu-modal .sq-set__stack,
  .sq-pattern__menu-modal .sq-mode__stack { display: flex; flex-direction: row; gap: 6px; }
  .sq-pattern__menu-modal .sq-set__stack > *,
  .sq-pattern__menu-modal .sq-mode__stack > * { flex: 1; }
  .sq-pattern__menu-modal .sq-mode__row { display: flex; gap: 6px; flex: 1; }
  .sq-pattern__menu-modal .sq-mode__row > * { flex: 1; }
  .sq-pattern__menu-modal .sq-repeat__wrap {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    font-size: 12px;
  }
  .sq-pattern__menu-modal .sq-repeat__wrap input,
  .sq-pattern__menu-modal .sq-repeat__wrap select {
    width: auto;
    flex: 1;
    padding: 6px 8px;
    text-align: center;
  }
  /* The mode and switch buttons are a glyph each — a curling arrow and a
     lightning bolt — and in here that is all a phone gets, since their titles
     are hovers. They name themselves the same way the track's icon buttons do;
     main.js keeps the attribute in step with the state as it flips. */
  .sq-pattern__menu-modal .sq-mode__row > button {
    flex-direction: column;
    gap: 2px;
    height: auto;
    min-height: 44px;
    padding: 6px 4px;
  }
  .sq-pattern__menu-modal .sq-mode__row > button[data-label]::after {
    content: attr(data-label);
    font-size: 10px;
    text-transform: lowercase;
    letter-spacing: 0;
  }

  /* mini-row that groups sig + rep + dup */
  .sq-pattern__menu-modal .sq-pattern__menu-row {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
  }
  .sq-pattern__menu-modal .sq-pattern__menu-row > * { flex: 1 1 auto; }

  /* pattern grid inside the modal: wide cells, 8 per row */
  .sq-pattern__menu-modal .sq-pattern__grid {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    grid-auto-rows: 34px;
    gap: 4px;
    width: 100%;
  }
  .sq-pattern__cell { min-height: 28px; font-size: 11px; }
  .sq-pattern__menu-modal .sq-pattern__cell { min-height: 34px; font-size: 12px; }

  .sq-pattern__menu-modal .sq-pattern__menu-close {
    align-self: stretch;
    margin-top: 4px;
    padding: 10px;
    background: var(--accent);
    color: #0b0d10;
    border-color: var(--accent);
    font-weight: 600;
    min-height: 40px;
  }
  .sq-pattern__menu-modal .sq-dl__btn { gap: 6px; }
  .sq-pattern__menu-modal .sq-dl__btn .sq-dl__label { display: inline; }

  /* ---------- MAIN AREA ---------- */
  main { padding: 8px; gap: 8px; }
  /* The two buttons under the track list are the only way to grow a session,
     and they were 30px tall in a layout where everything else had been raised
     to a finger's height. */
  .sq-add-track-row button { min-height: 44px; padding: 8px 14px; }
  .sq-track { padding: 8px; gap: 8px; border-radius: 6px; }
  .sq-track::before { border-radius: 6px 0 0 6px; }

  /* ---------- TRACK HEAD ---------- */
  .sq-track__head { gap: 4px; }
  .sq-track__head .sq-field .sq-knob { --knob-size: 44px; }
  .sq-track__head .sq-field label { font-size: 10px; }

  .sq-track__head .sq-track__name {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
    font-size: 13px;
    min-height: 40px;
    padding: 6px 8px;
  }
  .sq-track__head .sq-track__engine,
  .sq-track__head .sq-track__engine-btn {
    flex: 2 1 0;
    min-width: 0;
    max-width: none;
    min-height: 40px;
    padding: 6px 6px;
  }
  /* The instrument picker fills the phone: two cards across, a tall scroll,
     and chips that scroll sideways rather than eating half the sheet. */
  .sq-modal.sq-engine-picker { width: calc(100vw - 16px); max-width: none; max-height: calc(100dvh - 24px); padding: 14px; }
  .sq-engine-picker__top { flex-wrap: wrap; }
  .sq-engine-picker__search { min-height: 40px; font-size: 16px; }
  .sq-engine-picker__chips { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; }
  .sq-engine-picker__chip { flex: none; min-height: 34px; padding: 4px 12px; }
  .sq-engine-picker__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sq-engine-picker__card { min-height: 44px; }
  /* The line break between name / engine / len and the vol row. A flex
     container's own ::before IS a flex item, so a zero-height one with a
     full-width basis is a line break — which is the only way to put a break at
     a chosen point in a wrapping row without adding a wrapper element to the
     markup, and the track head's children are queried from a dozen places by
     their position in it. `order` is what places it: 9, between the two groups
     below. */
  .sq-track__head::before {
    content: "";
    order: 9;
    flex: 0 0 100%;
    height: 0;
    margin: 0;
  }
  /* And the one between the vol row and the generator buttons. The synth row
     (order 20, full width) used to be that break by being there; with it
     hidden on a phone, the dice and its neighbours flowed up beside vol and
     squeezed solo / mute / p-lock to nothing. The head's other pseudo. */
  .sq-track__head::after {
    content: "";
    order: 20;
    flex: 0 0 100%;
    height: 0;
    margin: 0;
  }
  /* Vol used to take a line of its own — one 44px control centred in 374px of
     nothing, with the three buttons that follow it pushed onto a line of their
     own underneath. Given a fixed column it keeps its size and solo / mute /
     p-lock come up beside it, which is one row per track less to scroll past.
     The meter narrows to the knob's column; it reads a level, not a number. */
  .sq-track__head .sq-vol__field { flex: 0 0 auto; order: 10; }
  .sq-track__head .sq-vol-combo { width: 52px; }
  .sq-track__head .sq-track__solo,
  .sq-track__head .sq-track__mute,
  .sq-track__head .sq-track__plock { order: 10; flex: 1 1 0; min-width: 0; }
  /* The synth row and the generator buttons keep their places after it. Order
     is stated rather than left to DOM order because the vol field sits BEFORE
     the synth row in the markup and the buttons after it. */
  .sq-track__head .sq-track__synth-row { order: 20; }
  .sq-track__head .sq-track__clear,
  .sq-track__head .track-dice,
  .sq-track__head .track-euclid,
  .sq-track__head .track-chance,
  .sq-track__head .sq-panel__btn-group { order: 30; }

  /* The "more" toggle (opens the track menu) reads as this row's own menu
     button, so it sits last rather than ahead of roll. */
  .sq-track__head .sq-track__more { order: 31; }

  /* len + spd group: keep inline but compact */
  .sq-track__head .sq-field:has(.sq-track__len) { gap: 3px; }
  .sq-track__head .sq-track__len { width: 48px; min-height: 40px; padding: 4px 4px; text-align: center; }
  .sq-track__head .sq-track__speed { padding: 4px 4px; }
  .sq-track__len-extend { gap: 2px; flex-wrap: nowrap; }
  .sq-track__len-extend button {
    padding: 4px 6px;
    min-width: 32px;
    font-size: 10px;
    min-height: 28px;
  }

  .sq-track__oct { gap: 2px; flex-wrap: nowrap; }
  .sq-track__oct button {
    padding: 4px 6px;
    min-width: 36px;
    font-size: 10px;
    min-height: 28px;
  }

  /* Hide-by-default extras inside the track-head; the "more" button opens
     these in a centered modal instead (.sq-track__menu-modal). The selectors
     are scoped to .sq-track__head so when nodes are reparented into the modal
     they become visible again automatically. The sound-shaping panel buttons
     (env/comp) move to that modal; fx, filter, eq, mod and aut go to the
     quick row under the head (render.js's applyQuickRow). roll keeps its inline
     spot. */
  .sq-track__head .sq-track__save,
  .sq-track__head .sq-track__load-patch,
  .sq-track__head .sq-track__copy,
  .sq-track__head .sq-track__paste,
  .sq-track__head .sq-track__dup,
  .sq-track__head .sq-track__remove,
  .sq-track__head .sq-track__oct,
  .sq-track__head .sq-track__len-extend,
  .sq-track__head .sq-panel__btn-group > button:not(.sq-track__roll) {
    display: none;
  }
  /* Speed sits on the vol line after p-lock, at a fixed width: solo / mute /
     p-lock are `flex: 1 1 0`, so they take what is left and the line never
     wraps. The select is the buttons' height. */
  .sq-track__head .sq-field:has(.sq-track__speed) { order: 11; flex: 0 0 auto; }
  .sq-track__head .sq-track__speed { min-height: 44px; width: 58px; padding: 4px 2px; font-size: 12px; }

  /* The synth row (harm/timb/morph/decay + the engine's groups) is not inline
     on a phone: it is the instrument modal's (openInstrumentAsModal, from the
     quick row's `instrument` button), where this rule, scoped to the head,
     lets go of it. Inline it was most of a track's height — a contagion or a
     hexop panel alone is a screen — so the next track's steps were a long
     scroll away, and the controls you reach for are the step grid's. */
  .sq-track__head .sq-track__synth-row { display: none; }
  /* Four knobs fit across a phone where four sliders never did — 4x44px plus
     gaps is under 200px — so the timbre macros sit on one row of the modal. */
  .sq-param-group .sq-field {
    flex: 1 1 20%;
    min-width: 0;
  }
  /* The live panels — filter, env, eq, comp, the rack's engaged stages and the
     mod matrix's enabled rows — are not inline either (each panel's button
     carries a dot and opens it as a modal: filter, eq, mod, aut and the
     rack's stages from the quick row, env / comp from the "more" menu). The two
     generators stay: a live ring or a chance part is a picture of what the
     track is playing, and the grid under it is read-only while it is. The
     wrapper hides itself when a generator is not what is live in it, or an
     empty grid would still cost the track's gap. */
  .sq-track .sq-track__live > .sq-track__filter-panel,
  .sq-track .sq-track__live > .sq-track__env-panel,
  .sq-track .sq-track__live > .sq-track__eq-panel,
  .sq-track .sq-track__live > .sq-track__comp-panel,
  .sq-track .sq-track__live > .sq-track__fx-panel,
  .sq-track .sq-track__live > .sq-track__mod-panel { display: none !important; }
  .sq-track__live:not(:has(> .sq-track__euclid-panel.is-live, > .sq-track__chance-panel.is-live)) { display: none; }

  /* ---------- ICON BUTTONS NAME THEMSELVES ON A PHONE ----------------------
     A title attribute is a hover, and a finger cannot hover — so the dice, the
     ring, the die, the sample editor, clear, roll and the "more" toggle were
     seven unlabelled glyphs in a row with no way to find out what any of them
     did. Each one now carries its name under its icon.

     Under, not beside: a caption beside the glyph doubles the button's width
     and six of those do not fit across a phone, where a column of icon-over-
     caption is 44px wide — which is the target size these buttons needed
     anyway. The two shapes are the same rule because they are the same button:
     `data-label` for the ones drawn from an icon alone (studioMarkup.ts, and
     render.js for the ones it builds), and the `.sq-btn__label` span that
     clear and roll carry for their desktop text. The same square as the
     quick row's under the head (56px tall, 2px side padding), so the three
     lines of buttons on a track read as one grid. */
  .sq-track__head .sq-icon-btn,
  .sq-track__head .sq-track__roll,
  .sq-track__head .sq-track__clear {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 44px;
    width: auto;
    height: auto;
    min-height: 56px;
    padding: 5px 2px;
    line-height: 1.1;
  }
  /* Icons are drawn at a fixed width/height attribute (icons.js); a CSS
     dimension beats a presentation attribute, so this scales them up to match
     the bigger button rather than leaving them adrift in the middle of it. */
  .sq-track__head .sq-icon-btn .sq-btn-icon,
  .sq-track__head .sq-track__roll .sq-btn-icon,
  .sq-track__head .sq-track__clear .sq-btn-icon { width: 18px; height: 18px; }
  /* display only on these two: `.sq-icon-btn` is already inline-flex, and
     setting it here as well would resurrect save / load / dup / remove, which
     the block above hides so the track menu can host them. */
  .sq-track__head .sq-track__roll,
  .sq-track__head .sq-track__clear { display: inline-flex; }
  .sq-track__head .sq-track__roll .sq-btn-icon,
  .sq-track__head .sq-track__clear .sq-btn-icon { display: block; }
  .sq-track__head .sq-track__roll .sq-btn__label,
  .sq-track__head .sq-track__clear .sq-btn__label,
  .sq-track__head [data-label]::after { font-size: 10px; letter-spacing: 0; text-transform: lowercase; }
  .sq-track__head .sq-track__roll .sq-btn__label,
  .sq-track__head .sq-track__clear .sq-btn__label { display: block; }
  .sq-track__head [data-label]::after { content: attr(data-label); }
  .sq-track__head .sq-track__more[aria-pressed="true"] {
    background: var(--accent);
    color: #0b0d10;
    border-color: var(--accent);
  }

  /* Track menu modal: hosts the reparented track-head extras. Layout
     vertically stacks the groups for thumb-friendly tapping. */
  .sq-track__menu-modal {
    width: 96vw;
    max-width: 96vw;
    min-width: 0;
    max-height: 92vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px;
  }
  .sq-track__menu-modal .sq-track__menu-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--fg);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
  }
  .sq-track__menu-modal .sq-track__menu-engine {
    font-size: 11px;
    font-weight: 400;
    color: var(--fg-dim);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* dim lowercase section labels */
  .sq-track__menu-modal .sq-track__menu-label {
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--fg-dim);
    margin-top: 6px;
  }
  .sq-track__menu-modal .sq-track__menu-row {
    display: flex;
    gap: 6px;
  }
  .sq-track__menu-modal .sq-track__menu-row > * {
    flex: 1 1 0;
    min-width: 0;
  }
  .sq-track__menu-modal .sq-track__save,
  .sq-track__menu-modal .sq-track__load-patch,
  .sq-track__menu-modal .sq-track__copy,
  .sq-track__menu-modal .sq-track__paste,
  .sq-track__menu-modal .sq-track__dup,
  .sq-track__menu-modal .sq-track__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 8px 10px;
    height: auto;
    font-size: 12px;
    min-width: 0;
    min-height: 40px;
  }
  .sq-track__menu-modal .sq-menu-btn-label {
    font-size: 12px;
    text-transform: lowercase;
  }
  .sq-track__menu-modal .sq-track__len-extend,
  .sq-track__menu-modal .sq-track__oct {
    display: flex;
    gap: 6px;
    flex-wrap: nowrap;
  }
  .sq-track__menu-modal .sq-track__len-extend > button,
  .sq-track__menu-modal .sq-track__oct > button {
    flex: 1 1 0;
    min-width: 0;
    min-height: 40px;
    padding: 6px 4px;
    font-size: 12px;
  }
  /* sound-shaping panel buttons: tap-friendly grid at the top of the menu */
  .sq-track__menu-modal .sq-track__menu-panels {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
  }
  .sq-track__menu-modal .sq-track__menu-panels > button {
    min-width: 0;
    min-height: 40px;
    padding: 8px 4px;
    font-size: 12px;
    border: 1px solid var(--border);
    border-radius: 6px;
  }
  .sq-track__menu-modal .sq-track__menu-panels > button[aria-pressed="true"] {
    background: var(--accent);
    color: #0b0d10;
    border-color: var(--accent);
  }
  .sq-track__menu-modal .sq-track__menu-close {
    align-self: stretch;
    margin-top: 4px;
    padding: 10px;
    background: var(--accent);
    color: #0b0d10;
    border-color: var(--accent);
    font-weight: 600;
    min-height: 40px;
  }

  /* solo / mute / p-lock: the same 44px floor the knobs get from --knob-hit.
     They sit beside the vol knob now, so they are also what sets that row's
     height. */
  .sq-track__head .sq-track__solo,
  .sq-track__head .sq-track__mute,
  .sq-track__head .sq-track__plock {
    padding: 6px 8px;
    min-height: 44px;
    font-size: 12px;
  }

  /* synth-row: stack groups */
  /* nowrap matters: a column that WRAPS sizes its line to the widest item's
     content — with a hexop in the row that is the 500px operator grid — and
     then stretches every panel to that line, off the edge of the phone. Unwrapped,
     the panels take the row's width and the grid scrolls inside its own panel. */
  .sq-track__synth-row { gap: 4px; flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
  .sq-param-group { padding: 4px 6px; gap: 6px; flex-wrap: wrap; }
  .sq-param-group .sq-field .sq-knob { --knob-size: 44px; }
  /* The selects and the odd button sitting among the knobs (an engine's wave
     or mode picker, `rand`) to the same floor as the knobs beside them — a
     21px-tall select in a row of 44px targets is the one anybody misses. */
  .sq-param-group select,
  .sq-param-group button { min-height: 40px; padding: 4px 8px; }
  .sq-param-group--ladder .sq-ladder__osc-row { flex-wrap: wrap; }
  .sq-param-group--ladder .sq-ladder__freq .sq-knob { --knob-size: 42px; }

  /* panel-btn-group: only roll stays inline (rest live in the "more" menu) —
     let it flow beside solo/mute/clear instead of claiming a full row */
  .sq-track__head .sq-panel__btn-group {
    flex: 0 1 auto;
    border-radius: 6px;
  }
  .sq-track__head .sq-panel__btn-group > button {
    padding: 5px 4px;
    font-size: 12px;
    min-width: 56px;
    min-height: 56px;
    border-right: 1px solid var(--border);
  }
  .sq-track__head .sq-panel__btn-group > .sq-track__roll { border-right: none; }
  /* The six on that line (clear / dice / euclid / chance / roll / more) share
     it exactly as the quick row's six do (`flex: 1 1 44px`, the same 4px
     gap), so the three lines of buttons on a track are one grid of equal
     squares. */
  .sq-track__head .sq-track__clear,
  .sq-track__head .track-dice,
  .sq-track__head .track-euclid,
  .sq-track__head .track-chance,
  .sq-track__head .sq-panel__btn-group,
  .sq-track__head .sq-track__more { flex: 1 1 44px; min-width: 44px; }
  .sq-track__head .sq-panel__btn-group > .sq-track__roll { width: 100%; min-width: 0; min-height: 54px; padding: 5px 2px; }

  /* ---------- FX / FILTER / EQ / COMP / ENV panels ---------- */
  .sq-track__fx-panel,
  .sq-track__filter-panel,
  .sq-track__env-panel,
  .sq-track__eq-panel,
  .sq-track__comp-panel,
  .sq-track__mod-panel,
  .sq-track__aut-panel,
  .sq-track__roll-panel {
    padding: 6px 8px;
  }
  /* The fx rack is fifteen stages deep, and on a phone each one was a name on
     a line of its own with two or three controls tucked under its left end —
     about 120px of height to carry 140px of controls across a 374px row. Read
     end to end that was a 1900px scroll to reach the reverb, most of it empty.

     The name goes back on the stage's own line, in a fixed column the eye can
     run down, with the controls beside it in the space they were leaving
     empty. A stage is one row again and the rack is half as tall. Stages with
     more controls than fit (the delay, the reverb) wrap under that column,
     which is the only thing the full-width title was buying. */
  .sq-fx__row {
    gap: 6px 8px;
    padding: 5px 0;
    row-gap: 4px;
    flex-wrap: wrap;
    align-items: center;
    border-bottom: 1px solid var(--border);
  }
  .sq-fx__row .sq-fx__title {
    flex: 0 0 78px;
    min-width: 0;
    font-size: 10px;
    /* The names are what you scan for, so they wrap rather than ellipse —
       "fuzz (dba-style)" says nothing cut off at "fuzz (db". */
    overflow-wrap: anywhere;
    line-height: 1.2;
  }
  .sq-fx__ctl { gap: 2px; }
  .sq-fx__ctl span { min-width: 0; font-size: 10px; }
  /* Shorter bars in the rack than elsewhere: fifteen of them stacked is the one
     place the extra travel costs more than it buys, and the drag is relative,
     so a shorter bar is a coarser sweep rather than a smaller range. */
  .sq-fx__ctl .sq-knob { --knob-size: 40px; --knob-len: 48px; }
  /* The rule separates stages, so the last one has nothing to separate from —
     and these rows are the filter, env, eq and comp panels too, where there are
     only one or two of them. */
  .sq-fx__row:last-child { border-bottom: none; }

  /* Dense engine panels on a phone. The 44px target is non-negotiable, so
     rather than let neighbouring targets overlap in a tight grid, the knobs
     themselves grow and the grid scrolls sideways — which .sq-hexop__ops was
     already set up to do. */
  .sq-hexop__ops .sq-hexop__f .sq-knob { --knob-size: 40px; --knob-thickness: 5px; }
  /* The synth row is a flex item, and a flex item's automatic minimum width is
     its content's — which with a hexop in it is the 500px grid below. Without
     this the row grows to fit the grid, the track grows to fit the row, and the
     whole page scrolls sideways. (`overflow: auto` on the grid changes its own
     minimum, not what it contributes to its ancestors' — hence the rule sits up
     here, beside the nowrap on the row itself.) */
  .sq-track__head .sq-track__synth-row { min-width: 0; }
  /* Same again one level down: the hexop panel is a wrapping column too (every
     .sq-param-group wraps on a phone), so its line would size to the grid and
     stretch the global rows to 500px with it. */
  .sq-param-group--hexop { flex-wrap: nowrap; }
  .sq-hexop__ophead, .sq-hexop__oprow {
    grid-template-columns: 34px repeat(8, minmax(44px, 1fr)) 60px;
    min-width: 500px;
  }
  .sq-param-group--contagion .sq-contagion__f .sq-knob,
  .sq-param-group--guitar .sq-guitar__f .sq-knob,
  .sq-param-group--bass .sq-bass__f .sq-knob,
  .sq-param-group--hexop .sq-hexop__row .sq-hexop__f .sq-knob { --knob-size: 42px; }
  .sq-fx__ctl select { padding: 3px 4px; font-size: 11px; }

  /* lfo row: the on/shape/sync controls stack, but the three sliders (length,
     amount, phase) sit in one row of three columns rather than one below the
     other — three short vertical bars side by side read at a glance and cost
     a third the scroll of stacking them. */
  .sq-lfo__row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: auto auto auto;
    gap: 6px;
    row-gap: 4px;
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
  }
  .sq-lfo__row .sq-lfo__target {
    grid-column: 1 / -1;
    font-size: 11px;
  }
  .sq-lfo__row .sq-lfo__enable { grid-column: 1; }
  .sq-lfo__row .sq-lfo__shape { grid-column: 2 / -1; }
  .sq-lfo__row .sq-lfo__sync-wrap { grid-column: 1; }
  .sq-lfo__row .sq-lfo__rate-field {
    grid-column: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
  }
  .sq-lfo__row .sq-lfo__amt-field { grid-column: 2; }
  .sq-lfo__row .sq-lfo__phase-field { grid-column: 3; }
  .sq-lfo__row .sq-lfo__remove {
    grid-row: 1;
    grid-column: 3 / -1;
    justify-self: end;
    align-self: start;
  }

  /* automation lane: stack label/buttons above grid */
  .sq-aut__lane {
    grid-template-columns: 1fr auto auto auto;
    grid-template-rows: auto auto;
    gap: 4px;
    row-gap: 4px;
  }
  .sq-aut__lane .sq-aut__grid {
    grid-column: 1 / -1;
    grid-row: 2;
    height: 64px;
  }

  /* keep wide piano-roll contents from pushing the page width.
     Tracks (and main grid items) default to min-width: auto, which lets
     intrinsic content grow them — explicitly clamp to 0 so the page stays
     bounded and the overflow lives inside the roll-grid. */
  main, .sq-track { min-width: 0; }
  .sq-track > * { min-width: 0; }
  .sq-track__roll-panel { min-width: 0; overflow: hidden; }

  /* ---------- STEP GRID (no horizontal scroll) ---------- */
  /* 16 cols stay; cells are tall to compensate for narrow width. */
  .sq-steps {
    grid-auto-rows: 52px;
    gap: 2px;
    touch-action: pan-y;
  }
  .sq-step {
    aspect-ratio: auto;
    min-height: 52px;
    height: 52px;
    border-radius: 3px;
  }
  .sq-step.is-held {
    height: auto;
    min-height: 52px;
    align-self: stretch;
  }
  /* note label larger so a finger can read it before tapping */
  .sq-step__note { font-size: 11px; }
  .sq-step.is-beat { outline: 1px solid rgba(255,255,255,0.10); }

  /* ---------- STEP EDITOR MODAL ---------- */
  .sq-step-editor {
    padding: 10px;
    max-width: 96vw;
    width: 96vw;
    max-height: 92vh;
    overflow-y: auto;
    gap: 6px;
    min-width: 0;
  }
  .sq-step-editor .sq-se__title { font-size: 10px; }
  .sq-step-editor .sq-se__field {
    grid-template-columns: 50px minmax(0, 1fr) 60px;
    gap: 6px;
  }
  .sq-step-editor .sq-se__chord-field {
    grid-template-columns: 50px 1fr auto;
    row-gap: 4px;
  }
  .sq-step-editor .sq-se__chord-field .sq-se__chord-label {
    grid-column: 1 / -1;
    text-align: left;
  }
  .sq-step-editor .sq-se__note-field {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    gap: 6px;
  }
  .sq-step-editor .sq-se__note-field > label:first-child { display: none; }
  .sq-step-editor .sq-se__note-header {
    grid-column: 1;
    grid-row: 1;
  }
  .sq-step-editor .sq-se__keyboard {
    --se-pad-size: 30px;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 3px;
    grid-column: 1;
    grid-row: 2;
  }
  .sq-step-editor .sq-se__keyboard .sq-se__pad {
    font-size: 10px;
    aspect-ratio: 1 / 1;
  }
  .sq-step-editor .sq-se__arp-selects { flex-wrap: wrap; }
  .sq-step-editor .sq-se__arp-selects select { flex: 1 1 30%; }
  .sq-step-editor .sq-se__sample-row { grid-template-columns: 1fr; }
  .sq-step-editor .sq-se__sample-row > label:first-child { display: none; }
  .sq-step-editor .sq-se__sample-ctl { grid-column: 1; }
  .sq-step-editor .sq-se__sample-meta { gap: 6px; }
  .sq-step-editor .sq-se__sample-fade { grid-template-columns: 1fr; gap: 6px; }
  .sq-step-editor .sq-se__waveform { height: 60px; }

  /* ---------- GENERIC MODALS ---------- */
  .sq-modal {
    min-width: 0;
    width: 94vw;
    max-width: 94vw;
    padding: 14px 16px;
    gap: 8px;
  }
  .sq-modal__actions button {
    padding: 8px 12px;
    min-height: 36px;
  }
  .sq-bounce__opts input[type="text"] { width: 100%; }
  .sq-bounce__opts label { flex: 1 1 100%; }

  .sq-patch__list .sq-patch__row button { padding: 8px 10px; min-height: 36px; }
}

/* ---------- A TOUCH DEVICE TOO WIDE FOR THE PHONE LAYOUT -------------------
   A phone turned sideways is 844px, past the 768px the layout rules key on, so
   it gets the desktop arrangement — which is the right call: every pattern slot
   and every track control is on screen at once. What it does not get is any of
   the sizing, so it was the desktop's 24-30px buttons under a finger, while the
   knobs beside them already had their 44px from --knob-hit.

   `pointer: coarse` is the same test the knobs-as-sliders block uses: the
   PRIMARY pointer is a finger, so a phone and a tablet match and a touchscreen
   laptop does not. A floor only, and a modest one — the layout is dense by
   design here and 44px everywhere would wrap the transport onto four lines. */
@media (pointer: coarse) and (min-width: 769px) {
  .sq-transport button,
  .sq-transport select,
  .sq-transport input[type="number"],
  .sq-pattern-bar button,
  .sq-pattern-bar select,
  .sq-pattern-bar input[type="number"],
  .sq-track__head button,
  .sq-track__head select,
  .sq-track__head input[type="text"],
  .sq-track__head input[type="number"],
  .sq-add-track-row button { min-height: 34px; }
  .sq-pattern__cell { min-height: 34px; }
  /* Icon buttons are square-ish, so the floor has to reach their width too or
     they stay a 32px column however tall the row gets. */
  .sq-transport .sq-icon-btn,
  .sq-pattern-bar .sq-icon-btn,
  .sq-track__head .sq-icon-btn { min-width: 36px; }
}

/* very small phones: a touch tighter */
@media (max-width: 380px) {
  main { padding: 6px; }
  .sq-track { padding: 6px; }
  .sq-transport { padding: 5px 6px; }
  .sq-pattern-bar { padding: 5px 6px; }
  .sq-steps { grid-auto-rows: 48px; }
  .sq-step { min-height: 48px; height: 48px; }
}


/* ── Preloader ──────────────────────────────────────────────────────────────
   Covers the server-rendered studio skeleton until the engine has booted (see
   app/preloader.ts). Ships hidden and is revealed by its inline driver, so a
   page whose scripts never run shows the bare app rather than a screen that
   never leaves. */
.sq-preload { display: none; }
.sq-preload.is-on {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg);
  /* Fade only. A scale/slide-away would move the wordmark, which sits in very
     nearly the same place in the header underneath. */
  transition: opacity 300ms ease;
}
.sq-preload.is-out { opacity: 0; pointer-events: none; }
/* No scrolling the skeleton around behind the overlay. */
html.sq-preloading, html.sq-preloading body { overflow: hidden; }

.sq-preload__box {
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: min(420px, 100%);
}
.sq-preload__logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--fg);
}
.sq-preload__logo img { display: block; }

/* The progress bar is a step row, because that is what this app is. Cells fill
   left to right; the leading cell is the playhead. */
.sq-preload__steps {
  display: grid;
  grid-template-columns: repeat(16, 1fr);
  gap: 4px;
}
.sq-preload__step {
  height: 26px;
  border-radius: 3px;
  background: var(--step);
  border: 1px solid var(--border);
  transition: background-color 140ms linear, border-color 140ms linear;
}
.sq-preload__step.is-on {
  background: var(--step-on);
  border-color: var(--step-on);
}
.sq-preload__step.is-head {
  background: var(--accent-2);
  border-color: var(--accent-2);
  animation: sq-preload-head 900ms ease-in-out infinite;
}
@keyframes sq-preload-head {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

.sq-preload__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--fg-dim);
}
.sq-preload__pct { font-variant-numeric: tabular-nums; }
.sq-preload.is-error .sq-preload__phase { color: var(--danger); }
.sq-preload.is-error .sq-preload__step.is-head {
  background: var(--danger);
  border-color: var(--danger);
  animation: none;
}

/* The hint line. min-height holds two lines whatever the tip's length, so the
   box never resizes under a rotation — the progress row above it would twitch
   every four seconds otherwise. */
.sq-preload__tip {
  margin: 0;
  min-height: 2.8em;
  font-size: 11px;
  line-height: 1.4;
  color: var(--fg-dim);
  transition: opacity 200ms ease;
}
.sq-preload__tip.is-fading { opacity: 0; }

/* The escape hatch: appears once the wait stops looking normal. */
.sq-preload__slow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--fg-dim);
  line-height: 1.5;
}
/* Explicit, because `display: flex` beats the UA's [hidden] rule (see the
   Hidden-attribute gotcha in CLAUDE.md). */
.sq-preload__slow[hidden] { display: none !important; }
.sq-preload__slow-msg { flex: 1 1 180px; }
.sq-preload__slow-btn {
  padding: 8px 12px;
  font-size: 11px;
  min-height: 36px;
}

@media (prefers-reduced-motion: reduce) {
  .sq-preload.is-on { transition: none; }
  .sq-preload__step { transition: none; }
  .sq-preload__step.is-head { animation: none; opacity: 1; }
  .sq-preload__tip { transition: none; }
}

/* ---- the code drawer (codePanel.js) --------------------------------------
   Strudel live coding, docked at the bottom so the tracks it writes
   stay in view above it. The editor is a transparent textarea over a <pre>
   that carries the colours and the marks on the sounding atoms; both share
   one font and one padding, or the caret drifts off the text. */
.sq-code {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: min(46vh, 420px);
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  border-top: 1px solid var(--accent);
  z-index: 60;
  backdrop-filter: blur(6px);
}
body.has-code-drawer { padding-bottom: min(46vh, 420px); }
#code-btn[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
.sq-code__bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}
.sq-code__title { color: var(--accent); font-weight: 600; margin-right: 4px; }
.sq-code__spacer { flex: 1; }
.sq-code__bar select { max-width: 130px; }
.sq-code__out { text-decoration: none; padding: 4px 8px; border: 1px solid var(--border); border-radius: 4px; color: var(--fg); font-size: 12px; }
.sq-code__out[hidden] { display: none !important; }
.sq-code__editor { position: relative; flex: 1; min-height: 0; }
.sq-code__hl, .sq-code__input {
  position: absolute; inset: 0;
  margin: 0; padding: 10px 14px;
  font: 13px/1.55 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: 0;
  white-space: pre-wrap; overflow-wrap: anywhere;
  overflow: auto;
  tab-size: 2;
  border: 0;
}
.sq-code__hl { color: var(--fg); pointer-events: none; }
.sq-code__input {
  background: transparent; color: transparent; caret-color: var(--accent);
  resize: none; outline: none;
}
.sq-code__input::selection { background: color-mix(in srgb, var(--accent-2) 30%, transparent); color: transparent; }
.sq-code__hl .t-c { color: var(--fg-dim); font-style: italic; }
.sq-code__hl .t-s { color: var(--accent); }
.sq-code__hl .t-n { color: #ffb86b; }
.sq-code__hl .t-l { color: var(--accent-2); font-weight: 600; }
.sq-code__hl .t-k { color: var(--accent-2); }
.sq-code__hl .t-m { color: #d7a6ff; }
.sq-code__hl .t-f { color: #d7a6ff; }
.sq-code__hl mark {
  background: transparent; color: inherit;
  outline: 1px solid var(--step-now);
  border-radius: 2px;
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
}
.sq-code__msg {
  max-height: 30%; overflow: auto;
  padding: 6px 12px;
  border-top: 1px solid var(--border);
  color: var(--fg-dim); font-size: 12px;
}
.sq-code__msg:empty { display: none; }
.sq-code__msg.is-ok { color: var(--accent); }
.sq-code__msg.is-warn span { color: var(--accent); }
.sq-code__msg.is-warn ul { margin: 4px 0 0; padding-left: 18px; color: var(--fg-dim); }
.sq-code__msg.is-error { color: var(--danger); }
@media (max-width: 768px) {
  .sq-code { height: 60vh; }
  body.has-code-drawer { padding-bottom: 60vh; }
  .sq-code__hl, .sq-code__input { font-size: 14px; }
}
.sq-code__pin { font-size: 11px; color: var(--fg-dim); border: 1px solid var(--border); border-radius: 4px; padding: 2px 6px; }
.sq-code__pin.is-off { color: var(--danger); border-color: var(--danger); }
/* the song has moved under code that was typed in: a dot on `from song` */
.sq-code__from.is-stale { color: var(--accent); border-color: var(--accent); }
.sq-code__from.is-stale::after { content: ""; display: inline-block; width: 6px; height: 6px; margin-left: 6px; border-radius: 50%; background: var(--accent); vertical-align: middle; }

/* The keyboard shortcuts list (`?`, shortcuts.js). */
.sq-shortcuts { max-width: 560px; }
.sq-shortcuts__list {
  display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px;
  margin: 0 0 12px; font-size: 12px; line-height: 1.4;
}
.sq-shortcuts__list dt { margin: 0; white-space: nowrap; }
.sq-shortcuts__list dd { margin: 0; color: var(--fg-dim); }
.sq-shortcuts__list kbd {
  font: inherit; padding: 1px 5px; border: 1px solid rgba(255,255,255,0.18);
  border-radius: 3px; background: var(--bg-3, rgba(255,255,255,0.05)); color: var(--fg);
}

/* The control the keyboard's panel navigator has picked (shortcuts.js). */
.is-kbd-nav { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* Vim mode (vim.js). The cursor is step input's, shown in every vim mode;
   insert while stopped keeps step input's red dashed one (the more specific
   rule above). */
body.vim-on .sq-track.is-kbd-active .sq-step.is-cursor { outline: 2px solid var(--accent); outline-offset: -2px; }
body.vim-visual .sq-step.is-vsel { background-image: linear-gradient(rgba(110,231,255,0.28), rgba(110,231,255,0.28)); outline: 2px solid var(--accent-2); outline-offset: -2px; }
#vim-toggle[aria-pressed="true"] { color: var(--bg); background: var(--accent); border-color: var(--accent); }
.sq-vim {
  position: fixed; left: 12px; bottom: 12px; z-index: 400;
  display: flex; align-items: center; gap: 10px;
  padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg-2); color: var(--fg); font-size: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
}
.sq-vim[hidden] { display: none; }
body.vim-insert .sq-vim__mode { color: #e5484d; }
body.vim-visual .sq-vim__mode { color: var(--accent-2); }
.sq-vim__keys { color: var(--fg-dim); min-width: 2ch; }
.sq-vim__cmd { width: 340px; font: inherit; color: var(--fg); background: transparent; border: 0; outline: none; padding: 0; }
.sq-vim__cmd[hidden] { display: none; }
/* The command line's completions (vim.js), stacked above the bar. */
.sq-vim__menu {
  position: absolute; left: -1px; bottom: calc(100% + 6px);
  min-width: 360px; max-width: min(560px, calc(100vw - 24px));
  margin: 0; padding: 4px; list-style: none;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg-2); box-shadow: 0 8px 24px rgba(0,0,0,0.45);
}
.sq-vim__menu[hidden] { display: none; }
.sq-vim__opt { display: flex; gap: 14px; padding: 3px 8px; border-radius: 4px; cursor: pointer; white-space: nowrap; }
.sq-vim__opt.is-on { background: var(--accent); color: var(--bg); }
.sq-vim__opt-name { min-width: 11ch; }
.sq-vim__opt-hint { color: var(--fg-dim); overflow: hidden; text-overflow: ellipsis; }
.sq-vim__opt.is-on .sq-vim__opt-hint { color: inherit; opacity: 0.75; }
.sq-vim__opt--more { color: var(--fg-dim); cursor: default; }


/* ---- THE VIEWS: groove / clips / arrange / piano (views.js) -------------
   One song, four pictures. `body.view-<name>` says which is open; the groove
   view is `#tracks` as it always was, the others are sections after it,
   drawn into only while open (clipView.js, arrangeView.js, pianoView.js). Desktop only:
   below 768px the switcher goes and the groove view stands. */
.sq-view-switch { display: inline-flex; gap: 0; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.sq-view-switch button { border: 0; border-radius: 0; padding: 4px 10px; font-size: 12px; background: transparent; color: var(--fg-dim); cursor: pointer; }
.sq-view-switch button + button { border-left: 1px solid var(--border); }
.sq-view-switch button[aria-pressed="true"] { background: var(--accent); color: #0b0d10; }
body.view-clips #tracks, body.view-arrange #tracks, body.view-piano #tracks { display: none; }
/* the pattern bar is the groove view's: the clip view's scene row and the
   arrangement's own toggle do its jobs there */
body.view-clips .sq-pattern-bar, body.view-arrange .sq-pattern-bar, body.view-piano .sq-pattern-bar { display: none; }
.sq-clips, .sq-arrange { max-width: 1400px; margin: 0 auto; padding: 12px 16px; }

/* The three views as two panes (viewSplit.js): the view on top, the selected
   track's card under it, a handle between them dragged to share the height.
   The section is what the pinned bar and transport leave of the window, so
   each pane scrolls on its own. --split-top is the top pane's share. */
.sq-split:not([hidden]) {
  display: flex; flex-direction: column;
  height: calc(100vh - var(--sq-topbar-h, 0px) - var(--sq-transport-h, 0px) - 8px);
  height: calc(100dvh - var(--sq-topbar-h, 0px) - var(--sq-transport-h, 0px) - 8px);
  min-height: 360px;
}
.sq-split__top { flex: 0 0 var(--split-top, 50%); min-height: 0; overflow: auto; overscroll-behavior: contain; }
.sq-split__bottom { flex: 1 1 0; min-height: 0; overflow: auto; overscroll-behavior: contain; }
.sq-split__handle {
  flex: 0 0 12px; position: relative; cursor: row-resize; touch-action: none;
  margin: 2px 0; border-radius: 4px;
}
.sq-split__handle::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--border);
}
.sq-split__handle::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 4px; margin: -2px 0 0 -22px;
  border-radius: 2px; background: var(--fg-dim); opacity: 0.5;
}
.sq-split__handle:hover::after, .sq-split__handle:focus-visible::after, .sq-split.is-resizing .sq-split__handle::after { background: var(--accent); opacity: 1; }
.sq-split__handle:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }
.sq-split.is-resizing { user-select: none; -webkit-user-select: none; cursor: row-resize; }
/* the toolbars stay in sight as their pane scrolls */
.sq-split__top .sq-clips__bar, .sq-split__top .sq-arr__bar-row { position: sticky; top: 0; z-index: 7; background: var(--bg); padding: 2px 0; }

/* The eight track hues, as #tracks has them, for the two views' rows (buses
   are never rows here and sit last in state.tracks, so the counts line up). */
.sq-clips__rows > :nth-child(8n+1), .sq-arr__rows > :nth-child(8n+1) { --track-hue: 35;  --track-color: hsl(35 85% 58%); }
.sq-clips__rows > :nth-child(8n+2), .sq-arr__rows > :nth-child(8n+2) { --track-hue: 232; --track-color: hsl(232 70% 68%); }
.sq-clips__rows > :nth-child(8n+3), .sq-arr__rows > :nth-child(8n+3) { --track-hue: 297; --track-color: hsl(297 55% 68%); }
.sq-clips__rows > :nth-child(8n+4), .sq-arr__rows > :nth-child(8n+4) { --track-hue: 120; --track-color: hsl(120 45% 52%); }
.sq-clips__rows > :nth-child(8n+5), .sq-arr__rows > :nth-child(8n+5) { --track-hue: 253; --track-color: hsl(253 70% 72%); }
.sq-clips__rows > :nth-child(8n+6), .sq-arr__rows > :nth-child(8n+6) { --track-hue: 318; --track-color: hsl(318 65% 68%); }
.sq-clips__rows > :nth-child(8n+7), .sq-arr__rows > :nth-child(8n+7) { --track-hue: 155; --track-color: hsl(155 45% 50%); }
.sq-clips__rows > :nth-child(8n+8), .sq-arr__rows > :nth-child(8n+8) { --track-hue: 275; --track-color: hsl(275 55% 70%); }

/* the strip at the left of a row: name, mute, solo, and the row's own button */
.sq-clips__strip, .sq-arr__strip {
  width: 150px; flex: 0 0 150px; display: flex; align-items: center; gap: 4px; padding: 0 6px 0 10px;
  border-left: 3px solid var(--track-color, var(--border)); font-size: 12px; min-width: 0;
}
.sq-clips__name, .sq-arr__name { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg); }
.sq-clips__strip button, .sq-arr__strip button { padding: 2px 6px; font-size: 11px; min-width: 22px; }
.sq-clips__mute[aria-pressed="true"], .sq-arr__mute[aria-pressed="true"] { background: var(--danger); color: #0b0d10; border-color: var(--danger); }
.sq-clips__solo[aria-pressed="true"], .sq-arr__solo[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
/* the track's own rule: a muted track can't be soloed nor a soloed one muted */
.sq-clips__strip button:disabled, .sq-arr__strip button:disabled { opacity: 0.35; cursor: not-allowed; }
.sq-clips__row.is-muted .sq-clips__cells, .sq-arr__row.is-muted .sq-arr__lane { opacity: 0.45; }

/* clips: a toolbar, then a row of 32 cells per track, scenes above */
.sq-clips__bar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: 12px; }
.sq-clips__mode[aria-pressed="true"], .sq-clips__switch[aria-pressed="true"] { background: var(--accent-2); color: #0b0d10; border-color: var(--accent-2); }
.sq-clips__head, .sq-clips__row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.sq-clips__head { margin-bottom: 8px; }
.sq-clips__cells { --clip-gap: 3px; flex: 1 1 0; display: grid; grid-template-columns: repeat(32, minmax(0, 1fr)); gap: var(--clip-gap); }
/* zoom (clipView.js): fit is the 32 columns across the width; zoomed, each
   column is --clip-w wide and the grid scrolls sideways under a sticky strip */
.sq-clips__zoom { display: inline-flex; gap: 2px; margin-left: auto; }
/* only zoomed: an overflow box would clip the scene +'s and a growing cell in fit */
.sq-clips__grid.is-zoomed .sq-clips__scroll { overflow-x: auto; padding: 2px 10px 6px 0; }
.sq-clips__grid.is-zoomed .sq-clips__table { width: max-content; }
.sq-clips__grid.is-zoomed .sq-clips__cells { flex: 0 0 auto; grid-template-columns: repeat(32, var(--clip-w)); }
.sq-clips__grid.is-zoomed .sq-clips__strip { position: sticky; left: 0; z-index: 3; background: var(--bg); align-self: stretch; }
.sq-clips__grid.is-zoomed .sq-clips__row.is-selected .sq-clips__strip { background: color-mix(in oklab, var(--track-color, var(--accent)) 18%, var(--bg)); }
.sq-clips__grid.is-zoomed .sq-clip.has-notes .sq-clip__n { font-size: 10px; }
.sq-clips__grid.is-zoomed .sq-clip__notes { top: 14px; bottom: 6px; left: 4px; right: 4px; }
/* the fill handle: a cell's right edge, dragged across the cells beside it
   to copy the clip into those scenes (shift: its length instead, the cell
   growing over its neighbours, an explicit width on a grid item overflowing
   its column); the targets light up while it lasts, and a multi-bar pattern
   wears its bar count */
.sq-clip__fill { position: absolute; right: -2px; top: 0; bottom: 0; width: 7px; cursor: ew-resize; border-radius: 0 3px 3px 0; }
.sq-clip:hover .sq-clip__fill { background: color-mix(in oklab, var(--fg) 18%, transparent); }
.sq-clip__bars { position: absolute; right: 3px; bottom: 1px; font-style: normal; font-size: 8px; line-height: 1; color: var(--fg); opacity: 0.8; }
.sq-clip.is-long { border-right-color: color-mix(in oklab, var(--fg) 30%, transparent); }
.sq-clip.is-filling { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.sq-clip.is-growing { z-index: 2; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.sq-clip.is-fill-target { border-color: var(--accent); border-style: dashed; background: color-mix(in oklab, var(--accent) 25%, var(--step)); }
.sq-clip, .sq-clips__scene {
  position: relative; height: 28px; padding: 0; border-radius: 3px; border: 1px solid transparent;
  background: var(--step); color: var(--fg-dim); font-size: 10px; cursor: pointer; display: grid; place-items: center;
}
.sq-clips__scene { height: 22px; background: color-mix(in oklab, var(--bg-3) 70%, var(--step)); }
.sq-clips__scene.is-filled { color: var(--fg); }
/* the repeat row: a column's bars in chain mode (state.patternRepeats) */
.sq-clips__reps { margin-top: -4px; }
.sq-clips__reps .sq-clips__strip { justify-content: flex-end; color: var(--fg-dim); font-size: 10px; }
.sq-clips__rep { min-width: 0; width: 100%; height: 18px; padding: 0 2px; font-size: 9px; text-align: center; text-align-last: center;
  appearance: none; -webkit-appearance: none; background: transparent; border: 1px solid transparent; border-radius: 3px; color: var(--fg-dim); cursor: pointer; }
.sq-clips__rep:hover, .sq-clips__rep:focus { border-color: var(--border); background: var(--bg-2); }
.sq-clips__rep.is-set { color: var(--accent-2); }
/* the + at a scene number's edges: a new empty column there (insertEmptyPattern) */
.sq-clips__ins { position: absolute; top: -1px; bottom: -1px; width: 11px; display: none; place-items: center; z-index: 2;
  font-size: 10px; line-height: 1; border-radius: 3px; background: var(--accent); color: #0b0d10; cursor: copy; }
.sq-clips__ins.is-left { left: -7px; }
.sq-clips__ins.is-right { right: -7px; }
.sq-clips__scene:hover .sq-clips__ins { display: grid; }
.sq-clips__scene.is-active { border-color: var(--step-now); box-shadow: 0 0 0 1px var(--step-now) inset; }
.sq-clips__scene.is-queued, .sq-clip.is-queued { border-color: var(--accent-2); }
.sq-clips__scene.is-looped { box-shadow: inset 0 -3px 0 var(--accent-2); }
/* --clip-c is the shade this distinct clip wears (clipView.js), the track colour by default */
.sq-clip.is-filled { background: color-mix(in oklab, var(--clip-c, var(--track-color, var(--step-on))) 45%, var(--step)); color: var(--fg); }
.sq-clip.is-scene { box-shadow: 0 0 0 1px color-mix(in oklab, var(--step-now) 35%, transparent) inset; }
.sq-clip.is-now { border-color: var(--step-now); color: var(--fg); background: color-mix(in oklab, var(--clip-c, var(--track-color, var(--step-on))) 75%, var(--step)); }
body.sq-playing .sq-clip.is-now::after { content: ""; position: absolute; left: 4px; bottom: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--step-now); animation: sq-clip-pulse 0.5s ease-in-out infinite alternate; }
@keyframes sq-clip-pulse { from { opacity: 0.35; } to { opacity: 1; } }
/* a clip's notes, one pass squeezed into the cell (patternPicture.js); the
   number moves to the corner so the picture has the middle */
.sq-clips__rows .sq-clip { height: var(--clip-h, 34px); }
.sq-clip.has-notes { place-items: start; }
.sq-clip__n { position: relative; z-index: 1; line-height: 1; }
.sq-clip.has-notes .sq-clip__n { padding: 2px 0 0 3px; font-size: 8px; opacity: 0.75; }
.sq-clip__notes { position: absolute; left: 2px; right: 2px; top: 10px; bottom: 4px; pointer-events: none;
  background: color-mix(in oklab, var(--fg) 85%, transparent);
  -webkit-mask-image: var(--notes); mask-image: var(--notes);
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.sq-clip.is-locked::before { content: ""; position: absolute; right: 3px; top: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
.sq-clip.is-drag-over { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 25%, var(--step)); }
.sq-clips__stop { font-size: 10px; }
.sq-clips__stop.is-on { color: var(--danger); border-color: var(--danger); }
.sq-clips__rec[aria-pressed="true"], .sq-arr__rec[aria-pressed="true"] { background: var(--danger); color: #0b0d10; border-color: var(--danger); }
.sq-clips__head .sq-clips__strip { justify-content: flex-end; border-left-color: transparent; }
.sq-clips__row.is-selected .sq-clips__strip, .sq-arr__row.is-selected .sq-arr__strip { background: color-mix(in oklab, var(--track-color, var(--accent)) 18%, transparent); border-radius: 0 4px 4px 0; }
.sq-clips__name, .sq-arr__name { text-align: left; background: transparent; border: 0; padding: 2px 4px; cursor: pointer; }
.sq-clips__name:hover, .sq-arr__name:hover { color: var(--accent); }
/* the selected track's own card, moved in from #tracks (trackDetail.js) */
.sq-track-detail { margin-top: 6px; display: grid; gap: 8px; }
/* the roll is the piano view's step editor: its card shows the sound alone */
.sq-piano .sq-track-detail .sq-steps { display: none; }
.sq-track-detail__title { font-size: 12px; color: var(--fg-dim); padding-left: 4px; }
.sq-track-detail > .sq-track::before { background: var(--track-color, var(--accent)); }

/* arrange: a ruler, a lane per track, blocks on it */
.sq-arr__bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; }
.sq-arr__bar-row .sq-arr__len { color: var(--fg-dim); margin-left: auto; }
.sq-arr__meter { display: inline-flex; align-items: center; gap: 4px; color: var(--fg-dim); font-size: 11px; }
.sq-arr__meter select { height: 22px; padding: 0 4px; font-size: 11px; }
.sq-arr__zoom { display: inline-flex; gap: 2px; }
.sq-arr__scroll { overflow-x: auto; padding-bottom: 6px; }
.sq-arr__grid { position: relative; min-width: max-content; --arr-bar-w: 48px; }
.sq-arr__ruler, .sq-arr__row { display: flex; align-items: stretch; }
.sq-arr__ruler { height: 22px; margin-bottom: 4px; }
.sq-arr__ruler .sq-arr__strip { border-left-color: transparent; }
.sq-arr__row { height: 46px; margin-bottom: 4px; }
/* the strip stays put while the lanes scroll under it, so a row keeps its name */
.sq-arr__ruler .sq-arr__strip, .sq-arr__row .sq-arr__strip { position: sticky; left: 0; z-index: 3; background: var(--bg); align-self: stretch; }
.sq-arr__row.is-selected .sq-arr__strip { background: color-mix(in oklab, var(--track-color, var(--accent)) 18%, var(--bg)); }
.sq-arr__lane { position: relative; flex: 0 0 auto; height: 100%; background: var(--step); border-radius: 3px;
  background-image: linear-gradient(to right, var(--border) 1px, transparent 1px); background-size: var(--arr-bar-w) 100%; }
.sq-arr__lane--ruler { background: transparent; }
.sq-arr__bar { position: absolute; top: 0; height: 100%; font-size: 10px; color: var(--fg-dim); border-left: 1px solid var(--border); }
.sq-arr__bar.is-four { border-left-color: var(--fg-dim); }
.sq-arr__bar span { position: absolute; left: 4px; top: 3px; }
.sq-arr__bar i { position: absolute; bottom: 0; height: 5px; border-left: 1px solid var(--border); }
.sq-arr__block {
  position: absolute; top: 3px; bottom: 3px; border-radius: 4px; box-sizing: border-box; cursor: grab; user-select: none;
  background: color-mix(in oklab, var(--clip-c, var(--track-color, var(--step-on))) 40%, var(--bg-3));
  border: 1px solid color-mix(in oklab, var(--clip-c, var(--track-color, var(--step-on))) 80%, transparent);
  display: flex; align-items: flex-start; gap: 4px; padding: 3px 6px 0; overflow: hidden; font-size: 11px; color: var(--fg);
  isolation: isolate;
}
/* the block's pattern: one pass of its notes (arrangeView.js patternPicture),
   a mask repeated every pass as the block loops it */
.sq-arr__notes {
  position: absolute; left: 0; right: 0; top: 4px; bottom: 4px; pointer-events: none; z-index: -1;
  background: color-mix(in oklab, var(--fg) 80%, transparent);
  -webkit-mask-image: var(--notes); mask-image: var(--notes);
  -webkit-mask-size: var(--rep) 100%; mask-size: var(--rep) 100%;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
  -webkit-mask-position: 0 0; mask-position: 0 0;
}
.sq-arr__block.is-empty { background: repeating-linear-gradient(135deg, color-mix(in oklab, var(--track-color, var(--step-on)) 30%, var(--bg-3)) 0 6px, var(--bg-3) 6px 12px); color: var(--fg-dim); }
.sq-arr__block.is-selected { outline: 2px solid var(--accent); outline-offset: -1px; }
.sq-arr__block.is-now { border-color: var(--step-now); box-shadow: 0 0 0 1px var(--step-now) inset; }
.sq-arr__block.is-ghost { opacity: 0.6; pointer-events: none; }
/* a recording in progress, not yet in the arrangement (clips.js finishTake) */
.sq-arr__block.is-take { pointer-events: none; opacity: 0.75; border-style: dashed; border-color: var(--danger); background: color-mix(in oklab, var(--danger) 25%, var(--bg-3)); }
.sq-arr__slot { padding: 0 4px; font-size: 10px; line-height: 14px; border-radius: 3px; background: color-mix(in oklab, var(--bg) 70%, transparent); border: 1px solid transparent; color: inherit; cursor: pointer; }
.sq-arr__block.is-selected .sq-arr__slot { border-color: var(--accent); }
.sq-arr__lock { color: var(--accent); font-size: 8px; }
.sq-arr__handle { position: absolute; right: 0; top: 0; bottom: 0; width: 8px; cursor: ew-resize; }
.sq-arr__handle::after { content: ""; position: absolute; right: 2px; top: 30%; bottom: 30%; border-right: 2px solid color-mix(in oklab, var(--fg) 50%, transparent); }
.sq-arr__on { color: var(--fg-dim); font-size: 10px; }
.sq-arr__head { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid var(--step-now); pointer-events: none; z-index: 2; }
.sq-arr__end { position: absolute; top: 0; bottom: 0; width: 0; border-left: 1px dashed var(--accent-2); pointer-events: none; opacity: 0.7; }
.sq-arr__lane--ruler { cursor: pointer; }
/* the start marker: a flag on the ruler and a faint line down the lanes */
.sq-arr__start { position: absolute; top: 0; bottom: 0; width: 0; border-left: 1px solid color-mix(in oklab, var(--accent) 55%, transparent); pointer-events: none; z-index: 1; }
.sq-arr__start::before { content: ""; position: absolute; top: 0; left: -5px; border: 5px solid transparent; border-top: 8px solid var(--accent); }
/* the loop section: a band on the ruler (dim while the loop is off) and a tint down the lanes */
.sq-arr__loop { position: absolute; top: 0; height: 100%; pointer-events: none; opacity: 0.45;
  background: color-mix(in oklab, var(--accent-2) 30%, transparent); border-left: 2px solid var(--accent-2); border-right: 2px solid var(--accent-2); box-sizing: border-box; }
.sq-arr__loop.is-on { opacity: 1; }
.sq-arr__loop-shade { position: absolute; top: 26px; bottom: 6px; pointer-events: none; z-index: 1;
  background: color-mix(in oklab, var(--accent-2) 7%, transparent); border-left: 1px dashed var(--accent-2); border-right: 1px dashed var(--accent-2); }
.sq-arr__empty { padding: 18px 12px 6px 162px; color: var(--fg-dim); font-size: 12px; }
.sq-arr__empty button { margin: 0 4px; border-color: var(--accent); color: var(--accent); }
.sq-arr__follow[aria-pressed="true"], .sq-arr__loopbtn[aria-pressed="true"] { background: var(--accent-2); color: #0b0d10; border-color: var(--accent-2); }
.sq-arr__picker { position: fixed; z-index: 210; display: grid; grid-template-columns: repeat(8, 32px); gap: 3px; padding: 6px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 8px 24px rgb(0 0 0 / 0.4); }
.sq-arr__picker .sq-pattern__cell { min-height: 24px; }

/* piano (pianoView.js): one track's pattern as a full piano roll. A sticky
   keyboard down the left and bar ruler across the top inside one scroller,
   the notes absolutely placed on the field, the velocity stalks under it
   scrolled sideways with it. */
.sq-piano { max-width: 1600px; margin: 0 auto; padding: 12px 16px; }
/* the editor's root, in the view or in a track's roll modal (pianoEditor.js adds the class) */
.sq-pr { --pr-keys: 66px; --pr-ruler: 40px; --pr-sech: 20px; }
.sq-pr__empty { color: var(--fg-dim); font-size: 12px; }
.sq-pr__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; }
.sq-pr__bar select { font-size: 12px; }
.sq-pr__track { max-width: 180px; }
.sq-pr__tools { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.sq-pr__tools button { border: 0; border-radius: 0; padding: 4px 9px; font-size: 12px; background: transparent; color: var(--fg-dim); cursor: pointer; }
.sq-pr__tools button + button { border-left: 1px solid var(--border); }
.sq-pr__tools button[aria-pressed="true"] { background: var(--accent); color: #0b0d10; }
.sq-pr__snap, .sq-pr__nudge, .sq-pr__len, .sq-pr__zoom { display: inline-flex; align-items: center; gap: 3px; color: var(--fg-dim); }
.sq-pr__nudge button, .sq-pr__len button, .sq-pr__zoom button { padding: 2px 6px; font-size: 11px; min-width: 22px; }
.sq-pr__loopbtn { display: inline-flex; align-items: center; gap: 4px; }
.sq-pr__loopbtn svg { width: 14px; height: 14px; }
.sq-pr__loopbtn[aria-pressed="true"], .sq-pr__followbtn[aria-pressed="true"], .sq-pr__ghostbtn[aria-pressed="true"], .sq-pr__hearbtn[aria-pressed="true"], .sq-pr__foldbtn[aria-pressed="true"] { background: var(--accent-2); color: #0b0d10; border-color: var(--accent-2); }
.sq-pr__delbtn:disabled { opacity: 0.4; cursor: default; }
/* the scroll buttons: a phone's, where there is no scrollbar and one finger draws */
.sq-pr__nav { display: none; gap: 3px; }
.sq-pr__nav button { padding: 2px 8px; font-size: 13px; min-width: 32px; }
/* the bar is two groups, inline on desktop: what a phone shows (the tools,
   the scroll buttons, delete) and what it keeps behind the ⋯ button */
.sq-pr__bar-main, .sq-pr__bar-more { display: contents; }
.sq-pr__morebtn { display: none; }
.sq-pr__warn { color: var(--danger); }

/* the track's card in a window over the roll (openSoundModal) */
.sq-pr__soundmodal { width: min(1100px, 96vw); max-width: none; max-height: 90vh; overflow-y: auto; padding: 14px; }
.sq-pr__soundmodal > .sq-track { margin: 0; border-left: 3px solid var(--track-color); }
.sq-pr__soundmodal .sq-steps { display: none; }
.sq-pr__soundmodal .sq-panel__modal-close { position: sticky; bottom: 0; padding: 10px; background: var(--accent); color: #0b0d10; border-color: var(--accent); font-weight: 600; min-height: 40px; }

.sq-pr__scroll { overflow: auto; border: 1px solid var(--border); border-radius: 6px 6px 0 0; background: var(--bg); overscroll-behavior: contain; }
/* the roll fills its pane: the toolbar and the velocity stalks keep their
   height, the scroller takes the rest */
.sq-piano .sq-split__top { display: flex; flex-direction: column; overflow: hidden; }
.sq-piano .sq-split__top > .sq-pr__bar, .sq-piano .sq-split__top > .sq-pr__vel { flex: 0 0 auto; }
.sq-piano .sq-pr__scroll { flex: 1 1 0; min-height: 60px; }
.sq-pr__canvas { display: grid; position: relative; width: max-content; user-select: none; -webkit-user-select: none; }
.sq-pr__corner { position: sticky; top: 0; left: 0; z-index: 6; background: var(--bg-2); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
  font-size: 10px; color: var(--track-color); padding: 4px 6px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; box-sizing: border-box; }
.sq-pr__corner i { display: block; font-style: normal; color: var(--fg-dim); margin-top: 2px; }
/* the computer keyboard's reach (octBand) */
.sq-pr__oct { position: absolute; right: 0; width: 5px; z-index: 2; background: var(--accent); opacity: 0.85; pointer-events: auto; border-radius: 2px 0 0 2px; }
.sq-pr__oct span { position: absolute; bottom: 0; right: 7px; font-size: 9px; line-height: 1; padding: 2px 3px; border-radius: 3px; background: var(--accent); color: #0b0d10; white-space: nowrap; pointer-events: none; }
.sq-pr__ruler { position: sticky; top: 0; z-index: 5; background: var(--bg-2); border-bottom: 1px solid var(--border); overflow: hidden; touch-action: none; }
/* a pattern in the ruler: its header on top, its bars under it */
.sq-pr__sec { position: absolute; top: 0; bottom: 0; border-left: 2px solid var(--fg-dim); box-sizing: border-box; overflow: hidden; }
.sq-pr__sec.is-now { border-left-color: var(--track-color); background: color-mix(in oklab, var(--track-color) 10%, transparent); }
.sq-pr__sec-h { position: absolute; top: 0; left: 0; right: 0; height: var(--pr-sech); display: flex; align-items: center; gap: 3px; padding: 0 4px; font-size: 10px; color: var(--fg-dim); white-space: nowrap; border-bottom: 1px solid var(--border); }
.sq-pr__sec-h button { padding: 0 5px; font-size: 10px; min-width: 0; height: 16px; line-height: 14px; }
.sq-pr__sec-play { color: var(--fg); font-weight: 600; }
.sq-pr__sec.is-now .sq-pr__sec-play { background: var(--track-color); color: #0b0d10; border-color: var(--track-color); }
.sq-pr__sec-len { margin-right: 2px; }
.sq-pr__sec-meter { height: 16px; padding: 0 2px; font-size: 10px; min-width: 0; width: auto; background: transparent; color: var(--fg-dim); border: 1px solid var(--border); border-radius: 3px; cursor: pointer; }
.sq-pr__sec-meter:hover, .sq-pr__sec-meter:focus { color: var(--fg); background: var(--bg-2); }
.sq-pr__rbar { position: absolute; top: var(--pr-sech); bottom: 0; border-left: 1px solid var(--fg-dim); box-sizing: border-box; padding: 3px 4px; font-size: 10px; color: var(--fg);
  background-image: linear-gradient(to right, var(--border) 1px, transparent 1px); background-size: calc(var(--sw) * var(--beat)) 100%; background-position: calc(var(--sw) * var(--beat) - 1px) 0; }

/* the keyboard: white keys, shorter black ones over them, C named */
.sq-pr__keys { position: sticky; left: 0; z-index: 4; background: #d9d6cc; border-right: 1px solid var(--border); touch-action: none; }
.sq-pr__key { position: absolute; left: 0; right: 0; box-sizing: border-box; background: #ecebe4; border-bottom: 1px solid #b9b6ac;
  font-size: 9px; line-height: 1; color: #555; display: flex; align-items: flex-end; justify-content: flex-end; padding: 0 4px 1px 0; cursor: pointer; }
.sq-pr__key.is-black { right: 38%; background: #24262c; border-bottom-color: #111; border-radius: 0 3px 3px 0; z-index: 1; }
.sq-pr__key.is-c { border-bottom-color: #8d8a80; color: #222; }
.sq-pr__key:hover { background: #fff; }
.sq-pr__key.is-black:hover { background: #3a3d45; }
.sq-pr__key.is-lit, .sq-pr__key.is-down { background: var(--track-color, var(--accent)) !important; color: #0b0d10; }

/* the field: a row per pitch, the lines over them, then ghosts, chord tones, notes */
.sq-pr__field { position: relative; overflow: hidden; touch-action: none; cursor: crosshair; }
/* the pan tool: a finger scrolls the roll natively, a pointer drags it */
.sq-pr__field.is-pan { touch-action: pan-x pan-y; cursor: grab; }
.sq-pr__field.is-pan .sq-pr__note { cursor: grab; }
.sq-pr__field.is-pan .sq-pr__edge { cursor: grab; }
.sq-pr__row { position: absolute; left: 0; right: 0; background: #1c1f26; border-bottom: 1px solid #15171c; box-sizing: border-box; }
.sq-pr__row.is-black { background: #16181e; }
.sq-pr__row.is-c { border-bottom-color: #2c3039; }
.sq-pr__row.is-out { background: #121318; }
.sq-pr__row.is-root { background: color-mix(in oklab, var(--accent) 9%, #1c1f26); }
.sq-pr__lines { position: absolute; top: 0; bottom: 0; pointer-events: none;
  background-image:
    linear-gradient(to right, #4a4f5c 1px, transparent 1px),
    linear-gradient(to right, #2f333d 1px, transparent 1px),
    linear-gradient(to right, #22252d 1px, transparent 1px);
  background-size: calc(var(--sw) * var(--barn)) 100%, calc(var(--sw) * var(--beat)) 100%, var(--sw) 100%; }
.sq-pr__lines.is-alt { background-color: rgb(255 255 255 / 0.025); }
/* the loop of patterns: a band in the ruler's header row, a shade over the field */
.sq-pr__sec-h { cursor: col-resize; }
.sq-pr__loop { position: absolute; top: 0; height: var(--pr-sech); pointer-events: none; z-index: 1; box-sizing: border-box; opacity: 0.45;
  background: color-mix(in oklab, var(--accent-2) 30%, transparent); border-left: 2px solid var(--accent-2); border-right: 2px solid var(--accent-2); }
.sq-pr__loop.is-on { opacity: 1; }
.sq-pr__loopshade { position: absolute; top: 0; bottom: 0; pointer-events: none; opacity: 0.4;
  background: color-mix(in oklab, var(--accent-2) 6%, transparent); border-left: 1px dashed var(--accent-2); border-right: 1px dashed var(--accent-2); }
.sq-pr__loopshade.is-on { opacity: 1; }
.sq-pr__secline { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid #5a6070; pointer-events: none; }
.sq-pr__ghost { position: absolute; box-sizing: border-box; border-radius: 2px; pointer-events: none;
  background: color-mix(in oklab, var(--g-c) 22%, transparent); border: 1px solid color-mix(in oklab, var(--g-c) 35%, transparent); }
.sq-pr__tone { position: absolute; box-sizing: border-box; border-radius: 3px; pointer-events: none;
  border: 1px dashed color-mix(in oklab, var(--track-color) 70%, transparent); background: color-mix(in oklab, var(--track-color) 18%, transparent); }
.sq-pr__note {
  position: absolute; box-sizing: border-box; border-radius: 3px; cursor: grab; overflow: hidden; z-index: 1;
  background: color-mix(in oklab, var(--track-color) calc(55% + var(--vel) * 45%), #0b0d10);
  border: 1px solid color-mix(in oklab, var(--track-color) 60%, #000);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.25);
}
.sq-pr__note.is-extra { background: color-mix(in oklab, var(--track-color) calc(40% + var(--vel) * 40%), #0b0d10); }
.sq-pr__note.is-sel { background: color-mix(in oklab, var(--track-color) 35%, #fff); border-color: #fff; z-index: 2; }
/* the scale row's "scale only" box is the piano view's (pianoView.js) */
#scale-fold-wrap { display: none; }
body.view-piano #scale-fold-wrap { display: inline-flex; }
#scale-fold-wrap:has(input:disabled) { opacity: 0.4; cursor: not-allowed; }
.sq-pr__note.is-lifted { opacity: 0.55; border-style: dashed; }
.sq-pr__note.is-off { outline: 1px dashed var(--danger); outline-offset: -1px; }
.sq-pr__note.is-new { opacity: 0.85; z-index: 3; }
.sq-pr__note span { display: block; font-size: 9px; line-height: var(--rh); padding: 0 3px; color: #0b0d10; white-space: nowrap; pointer-events: none; }
.sq-pr__edge { position: absolute; top: 0; bottom: 0; right: 0; width: max(4px, min(8px, 30%)); cursor: ew-resize; }
.sq-pr__marquee { position: absolute; z-index: 4; border: 1px solid var(--accent-2); background: color-mix(in oklab, var(--accent-2) 15%, transparent); pointer-events: none; }
.sq-pr__head { position: absolute; top: 0; bottom: 0; left: 0; width: 0; border-left: 2px solid var(--step-now); pointer-events: none; z-index: 5; }

/* the velocity lane */
.sq-pr__vel { display: flex; height: 72px; border: 1px solid var(--border); border-top: 0; border-radius: 0 0 6px 6px; background: var(--bg-2); overflow: hidden; }
.sq-pr__vel-label { flex: 0 0 var(--pr-keys); box-sizing: border-box; border-right: 1px solid var(--border); font-size: 10px; color: var(--fg-dim); padding: 4px 6px; }
.sq-pr__vel-clip { flex: 1 1 0; overflow: hidden; position: relative; }
.sq-pr__vel-inner { position: relative; height: 100%; touch-action: none; cursor: ns-resize; }
.sq-pr__vel-lines { position: absolute; top: 0; bottom: 0; pointer-events: none; border-left: 2px solid #4a4f5c; box-sizing: border-box;
  background-image: linear-gradient(to right, #3a3e49 1px, transparent 1px), linear-gradient(to right, #262933 1px, transparent 1px);
  background-size: calc(var(--sw) * var(--barn)) 100%, calc(var(--sw) * var(--beat)) 100%; }
.sq-pr__stalk { position: absolute; bottom: 0; width: 0; border-left: 2px solid var(--track-color); margin-left: 2px; pointer-events: none; }
.sq-pr__stalk::before { content: ""; position: absolute; top: -4px; left: -5px; width: 6px; height: 6px; border-radius: 50%;
  border: 2px solid var(--track-color); background: var(--bg-2); }

/* the piano editor under a finger: a narrower keyboard, a header row and
   toolbar at a finger's size, a wider tail to grab a note's end by. The roll
   modal is the piano roll on a phone, and a touchscreen laptop in the piano
   view wants the same room */
@media (max-width: 768px), (pointer: coarse) {
  .sq-pr { --pr-keys: 44px; --pr-ruler: 54px; --pr-sech: 30px; }
  .sq-pr__bar { gap: 6px; }
  .sq-pr__bar > button, .sq-pr__bar select, .sq-pr__tools button, .sq-pr__nudge button, .sq-pr__zoom button, .sq-pr__nav button { min-height: 36px; }
  .sq-pr__nav { display: inline-flex; }
  .sq-pr__nav button { min-width: 40px; font-size: 16px; }
  /* two rows, every button the same height: the tools and ⋯, then scroll
     and delete; the rest in a panel ⋯ opens under them */
  .sq-pr__bar { gap: 6px 0; }
  .sq-pr__bar-main { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; }
  .sq-pr__bar-main .sq-pr__track { flex: 1 1 100%; }
  .sq-pr__morebtn { display: inline-flex; align-items: center; justify-content: center; min-width: 40px; font-size: 18px; line-height: 1; margin-left: auto; }
  .sq-pr__morebtn[aria-pressed="true"] { background: var(--accent); color: #0b0d10; border-color: var(--accent); }
  .sq-pr__delbtn { margin-left: auto; }
  .sq-pr__bar-main .sq-pr__warn { flex: 1 1 100%; font-size: 11px; }
  .sq-pr__bar-more { display: none; }
  .sq-pr__bar-more.is-open { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; box-sizing: border-box; padding: 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-3); }
  .sq-pr__tools button { padding: 6px 8px; }
  .sq-pr__nudge button, .sq-pr__zoom button { min-width: 32px; }
  .sq-pr__sec-h { gap: 5px; }
  .sq-pr__sec-h button, .sq-pr__sec-meter { height: 26px; line-height: 24px; font-size: 11px; padding: 0 8px; }
  .sq-pr__edge { width: max(12px, min(22px, 40%)); }
  .sq-pr__vel { height: 84px; }
}
@media (max-width: 768px) {
  /* a phone's toolbar: the tools by their glyphs, the pattern's length readout
     folded into its buttons, so the roll keeps the height */
  .sq-pr__tools button span { display: none; }
  .sq-pr__tools button { padding: 6px 10px; font-size: 15px; }
  .sq-pr__sec-len { display: none; }
  /* desktop only in this cut: the groove view stands whatever was remembered */
  #view-switch, .sq-clips, .sq-arrange, .sq-piano { display: none !important; }
  body.view-clips #tracks, body.view-arrange #tracks, body.view-piano #tracks { display: grid; }
}
