* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: #fafafa;
  color: #222;
}

main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

h1 {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.25rem;
  font-size: 1.75rem;
  letter-spacing: 0.02em;
}

/* Headstock mark. Inlined in the markup rather than pulled from favicon.svg,
   because both url() techniques fail where this site is meant to be used: a
   CSS mask is CORS-blocked at a file:// origin, and an <img> is a separate
   document that cannot inherit this page's color — so favicon.svg's own
   prefers-color-scheme rule would turn it white on this light-only page.
   Inline + currentColor is the only form that survives file://. */
h1 .logo {
  flex: none;
  width: 1.1em;
  height: 1.1em;
}

.tagline {
  margin: 0 0 2rem;
  color: #666;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1.5rem;
  padding: 0;
  border: 0;
}

.chips legend {
  padding: 0;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
  color: #888;
}

/* `.chip` is the opt-in form of this look, for chips that live outside a
   `.chips` fieldset — currently the neck's string toggles, which have to sit
   inside the fretboard grid. Adding the class beats duplicating these rules or
   re-tagging the ~50 labels already nested in `.chips`. */
.chips label,
.chip {
  padding: 0.3rem 0.7rem;
  border: 1px solid #b9b2a4;
  border-radius: 999px;
  font-size: 0.85rem;
  color: #555;
  cursor: pointer;
  user-select: none;
}

/* Visually hidden but focusable checkbox/radio */
.chips input,
.chords input,
.chip input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.chips label:has(input:checked),
.chip:has(input:checked) {
  background-color: var(--note-bg);
  border-color: var(--note-bg);
  color: var(--note-fg);
}

.chips label:has(input:focus-visible),
.chip:has(input:focus-visible) {
  outline: 2px solid #4a90d9;
  outline-offset: 2px;
}

/* ---------- Control panels.

   Two panels of equal width, both centred under the board: the key picker above
   the neck and the chord strip below it. Spec:
   docs/superpowers/specs/2026-07-26-control-layout-design.md */
.controls,
.chords-slot {
  --key-w: 3.2rem;
  --key-gutter: 2.3rem;
  --key-gap: 0.3rem;

  /* Each key block is one gutter track plus N chip tracks, with a gap between
     every pair — so the panel's width follows from the chip tokens rather than
     being a magic number, and the narrow-viewport overrides at the foot of this
     file resize the chord strip to match for free. */
  --block-sharp-w: calc(var(--key-gutter) + 7 * (var(--key-w) + var(--key-gap)));
  --block-flat-w: calc(var(--key-gutter) + 5 * (var(--key-w) + var(--key-gap)));
  --panel-w: calc(var(--block-sharp-w) + 1.6rem + var(--block-flat-w));

  /* Centring happens inside the content box, so this padding is what makes the
     panels centre on the BOARD rather than on the content column. Same technique
     .chart uses; --neck-inset is defined once in fretboard.css. */
  padding-left: var(--neck-inset);
}

.controls {
  margin: 0 0 1.5rem;
}

/* The equal-width rule. `min(…, 100%)` keeps a panel from outgrowing a narrow
   viewport; below that the key matrix hands its overflow to the scroll valve at
   the foot of this file. */
.key-picker,
.chords,
.chords-hint,
.voicing {
  width: min(var(--panel-w), 100%);
  margin-inline: auto;
}

/* The fieldset keeps its `chips` class only so its labels go on inheriting the
   shared pill look above; it is really a legend plus an inner grid now. The grid
   never goes on the fieldset itself — a <legend> inside a `display: grid`
   fieldset renders inconsistently across browsers. */
.controls .chips {
  display: block;
  /* margin-block, not margin: the shorthand would reset the `margin-inline: auto`
     set above — this rule is both later and more specific — and the panel would
     sit left-aligned while the chord strip centred. */
  margin-block: 0;
  /* A <fieldset> carries `min-inline-size: min-content` from the UA stylesheet,
     which outranks the `width` above and holds the panel at the matrix's
     intrinsic width on a narrow viewport — so the key panel stopped matching the
     chord strip below ~26rem. Zeroing it lets the width win and hands the
     overflow to the scroll valve instead. */
  min-width: 0;
}

/* ---------- Key matrix.

   Columns are key signatures: each holds a major and its relative minor, and the
   two blocks run C→F# by sharps and F→Db by flats. Ordering the 24 chips this
   way is a pure reorder — no id changes — so keys.css is untouched.

   One grid PER ROW, every row in a block sharing one fixed track list. A single
   grid for the whole block does not work and fails silently: a row holding fewer
   items than there are tracks does not end at its last item, so the five-column
   flat block spills its next row into the two leftover tracks. Fixed track
   widths give the Maj/min alignment without a shared grid or subgrid. */
.key-blocks {
  display: grid;
  grid-template-columns: auto auto;
  gap: 1.6rem;
  align-items: start;
}

.key-block {
  display: grid;
  gap: var(--key-gap);
}

.key-row {
  display: grid;
  gap: var(--key-gap);
  align-items: center;
}

.block-sharp .key-row {
  grid-template-columns: var(--key-gutter) repeat(7, var(--key-w));
}

.block-flat .key-row {
  grid-template-columns: var(--key-gutter) repeat(5, var(--key-w));
}

/* Scoped to .key-row, never bare `.chip`: the neck's string names share that
   class and depend on shrink-to-fit plus justify-self: center, which is what
   keeps "e" and "E" the same width. */
.key-row label {
  width: 100%;
  padding-left: 0.2rem;
  padding-right: 0.2rem;
  text-align: center;
}

.key-row .sig,
.key-row .mode {
  font-size: 0.7rem;
  color: var(--label-color);
}

.key-row .sig {
  text-align: center;
}

.key-row .mode {
  text-align: right;
  padding-right: 0.15rem;
}

/* ---------- Chord strips.

   The strip sits BELOW the neck now, between the board and the chord charts,
   so it needs no reserved height: nothing above the board can change size, which
   is what keeps the neck's position fixed in every state. Its margin therefore
   leads rather than trails, as .chart's does. */
.chords-slot {
  margin: 1.5rem 0 0;
}

/* Visible by default and hidden once a key is chosen. Still reset-by-absence —
   the all-default state matches no rule — but the test is now "is any key
   radio checked" rather than "is the None chip checked", since there is no
   longer a None chip to check. */
.chords-hint {
  font-size: 0.85rem;
  color: var(--label-color);
}

main:has(input[name="key"]:checked) .chords-hint {
  display: none;
}

/* The voicing switch, occupying the slot the hint vacates. Revealed by the exact
   inverse of the rule above, on the same test, because the choice says nothing
   with no chord strip on screen. Reset by absence still holds: with no key
   checked, neither this nor the hint's hide rule matches.

   `display` is set in both states rather than toggling `visibility`, so the row
   takes no height before a key is picked. That is safe here for the reason the
   whole control layout relies on: everything above the board is fixed-size, and
   this sits below it, so the neck cannot move. */
.voicing {
  display: none;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
}

main:has(input[name="key"]:checked) .voicing {
  display: flex;
}

.voicing-label {
  font-size: 0.85rem;
  color: var(--label-color);
}

/* Diatonic chord strips: one per key, revealed by css/keys.css.
   No flex-wrap: the eight chips share the panel width equally, so they always
   fit on one row and the strip's edges line up with the key picker's above. */
.chords {
  display: none;
  gap: 0.4rem;
  list-style: none;
  padding: 0;
}

.chords li {
  /* Equal share of the panel rather than shrink-to-fit, so the row reads as one
     control instead of eight differently sized pills. Done on the item because
     keys.css owns `display` on .chords and reveals it as `flex`. */
  flex: 1;
  min-width: 0;
  border: 1px solid #b9b2a4;
  border-radius: 0.5rem;
  text-align: center;
}

.chords .rn {
  display: block;
  font-size: 0.75rem;
  color: #888;
}

.chords .name,
.chords .q {
  font-size: 0.95rem;
}

/* Chord entries are labels wrapping a hidden radio, so the whole chip clicks */
.chords label {
  display: block;
  padding: 0.3rem 0.6rem;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}

.chords li:has(input:checked) {
  background-color: var(--note-bg);
  border-color: var(--note-bg);
  color: var(--note-fg);
}

/* The numeral row's grey would drop below contrast on the dark fill */
.chords li:has(input:checked) .rn {
  color: var(--note-fg);
  opacity: 0.7;
}

.chords li:has(input:focus-visible) {
  outline: 2px solid #4a90d9;
  outline-offset: 2px;
}

/* ---------- Panel breakpoints.

   Every threshold here is a CONTENT-box requirement — the media width minus
   main's 3rem of padding, and minus this panel's own --neck-inset. Sizing a step
   against the viewport instead is what put a real overflow at 390px on an earlier
   pass: the matrix measured 355px, which fits 390px of viewport but not the 342px
   of content inside it. They are measured, not estimated.

   The token overrides deliberately hit .chords-slot as well as .controls. Both
   derive --panel-w from the same chip tokens, so resizing the matrix resizes the
   chord strip by exactly as much and the two panels stay equal width. */

/* Side by side needs --panel-w (48.2rem) plus --neck-inset (4.2rem) of content,
   i.e. ~55.4rem of media width. Stack at 56rem. This is much lower than it used
   to be, because the tone toggles no longer sit beside the matrix. */
@media (max-width: 56rem) {
  .key-blocks {
    grid-template-columns: auto;
    gap: 0.85rem;
  }

  /* Stacked, the panel is only as wide as the sharps block — and the chord strip
     narrows with it. */
  .controls,
  .chords-slot {
    --panel-w: var(--block-sharp-w);
  }
}

/* 22.2rem of tracks — needs ~403px of media width. */
@media (max-width: 34rem) {
  .controls,
  .chords-slot {
    --key-w: 2.6rem;
    --key-gutter: 1.9rem;
  }

  .key-row label {
    font-size: 0.75rem;
    padding-left: 0.1rem;
    padding-right: 0.1rem;
  }
}

/* 19.35rem of tracks — needs ~358px of media width, so 360px and 375px phones
   fit. Below that the valve below takes over. */
@media (max-width: 26rem) {
  .controls,
  .chords-slot {
    --key-w: 2.3rem;
    --key-gutter: 1.5rem;
    --key-gap: 0.25rem;
  }
}

/* 44rem is where fretboard.css flips the neck vertical, and the vertical neck is
   centred in the content column rather than inset by --neck-inset. Drop the inset
   so the panels centre on the same axis the neck now uses; keeping it would push
   them 4.2rem right of the neck's centre. Last in the file so it wins for every
   width at or below the threshold. */
@media (max-width: 44rem) {
  .controls,
  .chords-slot {
    padding-left: 0;
  }
}

/* Final valve for anything narrower than ~358px, matching the escape
   .board-scroll and .chart already use. It goes on .key-blocks rather than the
   fieldset so the legend stays outside the scroller. */
.key-blocks {
  overflow-x: auto;
  /* Load-bearing, and the reason is subtle. The chips' inputs are visually
     hidden with `position: absolute`, and nothing else here establishes a
     containing block, so they would resolve against the initial containing
     block. An absolutely positioned element whose containing block lies OUTSIDE
     a scroll container is not clipped by that container: the inputs would strand
     themselves at their static position in the scrolled-away region and drag the
     root element's scrollable overflow out with them — a page that scrolls
     sideways by 15-35px with nothing visible there. `body.scrollWidth` does not
     show it; only the root's does. Same containing-block trap as `.dot.open` in
     chord-charts.css. */
  position: relative;
}
