/* triads — extends styles.css, same tokens, same restraint.

   The keyboard's own colours and proportions are page-level custom properties,
   so the renderer is CSS and the JS never draws a colour. */

/* A full-height view rather than a document: the nav and footer hold their
   place, and the two columns between them scroll independently. The sidebar
   then keeps its full-height rule whether or not its content reaches the
   bottom, which a document-flow sidebar cannot do. */
html:has(body.triads) { overflow: hidden; }
body.triads {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}
body.triads .doc-nav,
body.triads footer { flex: none; }

/* Two panes: what you operate on the left, what you read on the right.

   The sidebar only has to fit a one-octave piano, which spends its width on twelve
   keys rather than twenty-four — so it is narrower than the 24-key board needed and
   the chart keeps the difference. The board is still by far the largest keyboard on
   the page.

   `body.doc main { display: block }` in styles.css outranks a bare class, so the
   grid has to be claimed at the same specificity or it silently never applies. */
body.triads .triads-body {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0 var(--pad-x);
  font-weight: 200;
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  column-gap: clamp(24px, 3vw, 48px);
}
.triads-body h1 {
  font-size: clamp(28px, 5vw, 44px);
  font-weight: 200;
  line-height: 1.21;
  margin: 0 0 12px;
}
.triads-body .lede {
  color: var(--muted);
  max-width: 60ch;
  /* one step down from body copy — the same step the nav wordmark uses. This is
     orientation, read once; the tool below it is the thing. */
  font-size: clamp(14px, 1.4vw, 16px);
  line-height: 1.6;
  margin: 0 0 clamp(28px, 4vw, 44px);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* — the keyboard's own colours —

   Sampled from the shipped OP-XY sheet, not eyeballed. The keyboard is 629 x 89
   (Figma node 288:4023): both rows 44 tall on a 45 pitch.

   These were custom properties so an OP-1 field theme could swap them on <body>.
   That toggle is gone, so they are plain page-level properties again — the OP-1
   variant is in git history (node 311:12133, paper #f0efe9, outline #787875) if
   it is ever wanted back. */
body.triads {
  --panel-bg: #000;
  --panel-edge: rgba(178, 178, 178, 0.26);
  --cell-bg: #000;
  --cell-edge: var(--hair);   /* the site's hairline, on every cell */
  --cell-lit: #fff;
  --glyph-edge: #b2b2b2;      /* and unchanged when lit — checked against the artwork */
  --radius: 2px;
  --kbd-ar: 629 / 89;
  --fr-black: 44;             /* the Figma row proportions, kept as the record of */
  --fr-white: 44;             /* where the geometry came from */
}

/* — controls — */

.controls { margin-bottom: clamp(18px, 2.4vw, 28px); }
.controls .ctl-row:last-child { margin-bottom: 0; }

.ctl-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  margin-bottom: 16px;
}
/* display:flex would otherwise beat the hidden attribute */
.ctl-row[hidden] { display: none; }
.ctl-label {
  font-size: 13px;
  color: var(--muted);
  min-width: 62px;
}

.seg { display: flex; flex-wrap: wrap; gap: 6px; }
.seg-btn {
  font: inherit;
  font-size: 14px;
  font-weight: 200;
  padding: 7px 14px;
  background: none;
  color: var(--fg);
  border: 1px solid var(--hair);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.seg-btn:hover { border-color: var(--fg); }
.seg-btn[aria-pressed="true"] {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
.seg-btn:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; }

.triads-body select {
  font: inherit;
  font-size: 14px;
  font-weight: 200;
  background-color: var(--bg);
  color: var(--fg);
  border: 1px solid var(--hair);
  cursor: pointer;
/* The native arrow sits hard against the right edge with no way to inset it, so
   the select draws its own: appearance:none, a chevron as a background image, and
   the gap set where a gap belongs. padding-right clears the arrow, not the border. */
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%23b2b2b2' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding: 7px 34px 7px 10px;
}
.triads-body select:hover { border-color: var(--fg); }

/* Four things on one line in a 331px sidebar, so this row runs a tighter gap than
   the others and the family select takes the slack — it is the one that can
   truncate an option and still be read. Everything here exists to keep `clear`
   on the same line: wrapped, it left a gap the selects grew into, so they
   resized every time a scale was chosen or cleared. */
#scale-row { column-gap: 10px; }
#scale-root { flex: 0 0 auto; }
#scale-family { flex: 1 1 80px; min-width: 0; }

/* Everything you operate lives in the left column; everything you read lives in
   the right. Two columns of panels rather than three so each keyboard is large
   enough to read a chord off without leaning in.

   Only the nav and the footer draw horizontal hairlines. The panes are bounded
   by that chrome rather than each drawing an edge of their own, so nothing
   doubles up where they meet. */
/* One pixel of separation between scrolling content and the footer's hairline.
   Overflow clips to the padding box, so a transparent bottom border holds a band
   the content cannot paint into.

   A scrolling pane always cuts its content mid-way — that is what scrolling is —
   so the band is not there to hide the cut, only to stop the cut edge touching the
   hairline and reading as if it crossed it. 10px was tried and was worse: it turned
   a tight cut into a wide dead strip. One is enough to separate two lines. */
.grid-wrap {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  border-bottom: 1px solid transparent;
  box-sizing: border-box;
  padding: clamp(24px, 3vw, 40px) 0 clamp(24px, 3vw, 40px);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  scrollbar-width: thin;
}
.grid-col { width: 100%; max-width: 1180px; min-width: 0; }
/* the pane, not the viewport, decides how many columns fit — the sidebar takes
   its width out of this pane, so a viewport breakpoint would guess wrong */
.grid-wrap { container-type: inline-size; }

.side {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  border-bottom: 1px solid transparent;   /* see .grid-wrap */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;   /* so .promo can take margin-top:auto */
  /* bottom repeats the right gutter rather than carrying its own number, so the
     card at the foot sits the same distance off the footer as it does off the
     divider, at every width */
  padding: clamp(24px, 3vw, 40px) clamp(20px, 2vw, 32px) clamp(20px, 2vw, 32px) 0;
  /* The divider is a shadow, not a border. Borders miter: a border-right and the
     transparent bottom border above would share the corner at 45 degrees, so the
     bottom of the divider was drawn in the transparent half and vanished, leaving
     a break above the footer's hairline. A shadow spans the whole border box and
     has no corner to share. */
  box-shadow: 0.5px 0 0 var(--hair);
  scrollbar-width: thin;
}
.side .lede { margin-bottom: clamp(20px, 2.4vw, 28px); }
.side .readout { margin-top: clamp(20px, 2.4vw, 28px); }

/* Vertical inset only. The piano is the width of its surface — horizontal padding
   buys nothing here and costs key size, which is the one thing the board is for. */
.board-surface {
  background: var(--panel-bg);
  padding: clamp(10px, 1.4vw, 16px) 0;
}
#board { display: block; }

.pins {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  min-height: 32px;
  align-items: center;
}
.pins-empty { font-size: 13px; color: var(--muted); }
.chip {
  font: inherit;
  font-size: 14px;
  font-weight: 200;
  padding: 5px 10px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  color: var(--fg);
  border: 1px solid var(--fg);
  cursor: pointer;
}
.chip span { color: var(--muted); }
.chip:hover { background: var(--fg); color: var(--bg); }
.chip:hover span { color: var(--bg); }
/* the same plain control the scale row uses — a box around "clear" made it read
   as one more note chip */
.chip-clear {
  border: none;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
  padding: 5px 2px;
  font-size: 13px;
  color: var(--muted);
}
.chip-clear:hover { background: none; color: var(--fg); border-bottom-color: var(--fg); }
.chip:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; }

.readout h2 {
  font-size: 13px;
  font-weight: 200;
  color: var(--muted);
  margin: 0 0 10px;
}
.hint { font-size: 13px; color: var(--muted); margin: 0; line-height: 1.5; }

.scale-list { list-style: none; margin: 0 0 8px; padding: 0; }
.scale-list li { margin: 0; }
.scale-pick {
  font: inherit;
  font-weight: 200;
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  background: none;
  color: var(--fg);
  border: none;
  border-bottom: 0.5px solid var(--hair);
  text-align: left;
  cursor: pointer;
}
.scale-pick:hover .scale-name { border-bottom: 1px solid var(--fg); }
.scale-name { font-size: 15px; }
.scale-fit {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.scale-pick:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; }

#scale-clear {
  font: inherit;
  font-size: 13px;
  font-weight: 200;
  background: none;
  border: none;
  border-bottom: 1px solid var(--hair);
  color: var(--muted);
  padding: 0;
  cursor: pointer;
}
#scale-clear:hover { color: var(--fg); border-bottom-color: var(--fg); }
/* Reserved rather than removed: dropping it out of the flow made the selects
   beside it resize the moment a scale was chosen. */
#scale-clear.is-idle { visibility: hidden; }

/* — the grid of twelve — */

/* One column is the floor; two are an enhancement the pane opts into once each
   panel can still draw a legible keyboard. The threshold is set so the board in
   the sidebar stays the largest keyboard on the page — a chart row that outgrows
   the instrument reads as the thing you are meant to touch. */
.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(14px, 1.6vw, 22px);
  min-width: 0;
}

@container (min-width: 800px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the twelve flow down each column, so a column is a continuous chromatic
     climb. The seven degree panels must not — I ii iii would land above IV V vi
     in the wrong order — so they stay in row order. */
  .grid:not(.is-flow-row) {
    grid-template-rows: repeat(6, auto);
    grid-auto-flow: column;
  }
}


.panel {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.2vw, 16px);
  background: var(--panel-bg);
  border: 1px solid var(--panel-edge);
  padding: clamp(10px, 1.4vw, 16px);
  transition: opacity 140ms ease;
}
.panel.is-out { opacity: 0.4; }
/* the degree numeral beside the chord name — the part that explains rather than
   locates, so it sits with the name but reads as an annotation to it */
.panel-degree {
  display: block;
  font-size: 0.5em;
  color: var(--muted);
  margin-top: 0.15em;
  font-variant-numeric: tabular-nums;
}

/* The empty state: what the pane says when the key implies no panels to draw.
   Centred in the pane rather than hung off its top left — with nothing else in
   there, the corner reads as a page that failed to load. */
/* An empty .grid is not zero-height — six template rows still reserve five gaps
   between them, which pushed the message off centre by half of that. */
.grid-wrap.is-empty { align-items: center; }
.grid-wrap.is-empty .grid { display: none; }
.grid-note {
  margin: 0 auto;
  max-width: 44ch;
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: center;
  color: var(--muted);
}
.grid-note-lead { font-size: clamp(18px, 2.4vw, 24px); line-height: 1.3; }
.grid-note-sub { font-size: 14px; line-height: 1.6; }
.grid-note[hidden] { display: none; }

.panel-name {
  font-size: clamp(20px, 2.4vw, 30px);
  font-weight: 200;
  color: var(--fg);
  margin: 0;
  flex: none;
  min-width: 1.8em;
  text-align: center;
}

.voicings {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.9vw, 12px);
}

.voicing {
  display: flex;
  align-items: center;
  gap: 8px;
  transition: opacity 140ms ease;
}
.voicing.is-out { opacity: 0.32; }

/* wide enough for the longest label a triad produces — `b5-1-b3` — with the
   octave badge allowed to sit outside it, since only one row in four carries one.
   Diminished rows are routine now that keys show the degree they play. */
.v-label {
  flex: none;
  width: 4.3em;
  white-space: nowrap;
  text-align: right;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  position: relative;
}
/* On its own line under the degrees, right-aligned with them. Inline it pushed
   the label to its widest case, so every panel paid for the one row in four that
   carries a badge. */
.v-oct {
  display: block;
  width: fit-content;
  margin: 3px 0 0 auto;
  padding: 0.08em 0.34em;
  border-radius: 0.4em;
  font-size: 0.85em;
  line-height: 1.25;
  background: #fff;
  color: #000;
}

/* — the keyboard itself —

   14 white keys in a row; each black key sits on the boundary after the white
   key it follows, which is what --x counts. */

/* Both rows are absolutely positioned from the same 629-unit scale the design
   uses, so a black key's glyph lands exactly on the white-key seam below it.
   The row background is what shows through every gap. */
.kbd {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  aspect-ratio: var(--kbd-ar);
}
.kbd-row {
  position: relative;
  display: block;
}
/* One design unit of space between the rows — 44 + 1 + 44 = 89, the keyboard's
   own height. A percentage margin resolves against width, which is the right
   basis here: the aspect ratio is fixed, so a unit of height and a unit of
   width scale by exactly the same factor. */
.kbd-black {
  flex: var(--fr-black) 1 0;
  margin-bottom: calc(100% / 629);
}
.kbd-white { flex: var(--fr-white) 1 0; }

.k {
  position: absolute;
  top: 0;
  height: 100%;
  display: block;
  padding: 0;
  background: var(--cell-bg);
  border: 0.5px solid var(--cell-edge);
  border-radius: var(--radius);
  box-sizing: border-box;
}
/* the glyph: an outlined circle, sized off the row height so both rows carry the
   same one. --seam moves it off centre for black keys, onto the white-key seam
   its circle sits on in the artwork. */
.k::after {
  content: "";
  position: absolute;
  top: 50%;
  left: var(--seam, 50%);
  transform: translate(-50%, -50%);
  height: 64%;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1px solid var(--glyph-edge);
  box-sizing: border-box;
}


/* A panel cell says one thing: whether its triad uses that note.

   It used to also carry pins, greyed, and the two fought — a key of pinned chord
   tones read as one wash, and telling "in this triad" from "you played this"
   needed a second mark on the glyph. Pins moved to the board alone, and the cell
   went back to one job.

     plain          black cell
     off scale      black cell, 30% opacity
     in the triad   white cell                                                */
.k.is-lit { background: var(--cell-lit); }

.k.is-off-scale { opacity: 0.3; }

/* — the piano board —

   One octave, because a pin is a pitch class. The 24-key keyboard in the panels
   answers a different question — where on the hardware this note lives — so it
   keeps its own drawing and this one is free to match the app's scale picker.

   Traced from that picker: grey keys divided by black, five black keys centred
   on the seams at 63% of a white key's width and 64% of its height, and a filled
   dot low on the key for a note you have chosen. Colours are literal rather than
   token-derived, because matching the app is the point. */
.piano {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 733 / 328;
  background: #000;
  border: 2px solid #000;
  box-sizing: border-box;
}
.pk {
  position: absolute;
  top: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  box-sizing: border-box;
  border: 0;
}
.pk-w {
  height: 100%;
  background: #c8c8c8;
  border-left: 1px solid #000;   /* the divisions are gaps in the grey, as drawn */
}
.pk-w:first-child { border-left: 0; }
.pk-b {
  height: 64%;
  background: #000;
  z-index: 1;                    /* over the white keys it straddles */
}
.pk:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; z-index: 2; }

/* the dot is the whole of the state: present means chosen */
.pk::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 10%;
  transform: translateX(-50%);
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: 0;
}
.pk-w::after { width: 25%; bottom: 10%; background: #000; }
.pk-b::after { width: 40%; bottom: 10%; background: #c8c8c8; }
.pk.is-pinned::after { opacity: 1; }

/* A chosen scale fills in the notes it needs that you did not play, in grey — the
   scale completing itself in front of you. Greying the *other* keys was the first
   attempt and it said the same thing backwards, by making eleven keys look
   disabled to point at seven. Declared before .is-pinned so yours stay solid. */
body.has-scale .pk:not(.is-off-scale)::after { opacity: 1; background: #808080; }
body.has-scale .pk.is-pinned.pk-w::after { background: #000; }
body.has-scale .pk.is-pinned.pk-b::after { background: #c8c8c8; }


/* The keys themselves never dim. An earlier version tinted the out-of-scale ones
   and it fought the instrument: a piano with eleven shades is not a piano. */


/* — the VJ-XY card —

   Pinned to the foot of the sidebar: .side is a flex column and this takes
   margin-top:auto, so it sits at the bottom while the sidebar has room and simply
   follows the content once there is enough to scroll. In chromatic that puts it in
   the empty space under two control rows; in key, once pins and a scale list fill
   the column, it goes below the fold — up while you orient, gone while you work.

   It is the only promotional block on any sub-page. Keep it this size: a thumb, a
   line, a link. No badge, no price, no second call to action. */
.promo {
  position: relative;
  margin-top: auto;          /* the bottom-anchoring */
  padding: 12px;
  border: 0.5px solid var(--hair);
  flex: none;
}
.promo[hidden] { display: none; }

.promo-link {
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: none;
  color: var(--muted);
}
.promo-link:hover { border-bottom: none; }
.promo-link img {
  display: block;
  width: 92px;
  height: auto;
  flex: none;
}
.promo-text { display: block; min-width: 0; }
.promo-copy {
  display: block;
  font-size: 13px;
  line-height: 1.4;
  /* room for the dismiss, so a second line never runs under it */
  padding-right: 14px;
}
.promo-more {
  display: inline-block;
  margin-top: 6px;
  font-size: 13px;
  color: var(--fg);
  border-bottom: 1px solid var(--hair);
  transition: border-color 120ms ease;
}
.promo-link:hover .promo-more { border-bottom-color: var(--fg); }
.promo-link:focus-visible { outline: 1px solid var(--fg); outline-offset: 3px; }

.promo-close {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 120ms ease;
}
.promo-close svg { width: 13px; height: 13px; display: block; }
.promo-close:hover { color: var(--fg); }
.promo-close:focus-visible { outline: 1px solid var(--fg); outline-offset: 1px; }

.triads-body .disclaimer {
  margin: clamp(48px, 6vw, 80px) 0 0;
  padding: 0;
  text-align: left;
}

/* — small screens — */

/* One column: the page goes back to being a document that scrolls, and the
   template re-spells itself so the board still sits between the settings and
   the chart rather than below all twelve panels. */
@media (max-width: 1080px) {
  html:has(body.triads) { overflow: visible; }
  body.triads { height: auto; overflow: visible; }
  body.triads .triads-body { grid-template-columns: minmax(0, 1fr); }
  .side {
    overflow: visible;
    box-shadow: none;
    /* nothing clips here, so the band that keeps scrolling content off the
       footer's hairline would just be dead space between the two blocks */
    border-bottom: 0;
    padding: clamp(20px, 3vw, 32px) 0 0;
  }
  .grid-wrap {
    overflow: visible;
    border-bottom: 0;
    padding-top: clamp(18px, 2.4vw, 28px);
  }
}

@media (max-width: 640px) {
  .ctl-row { gap: 8px 12px; }
  .ctl-label { min-width: 100%; }
  .v-label { width: 4.4em; font-size: 12px; }
  .panel-name { font-size: 22px; }

  .readout { margin-top: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .panel, .voicing { transition: none; }
}
