/* Components.
 *
 * Only semantic tokens are used here -- never --ansi-* and never --paper-*.
 * That is what makes the palette swappable by editing sixteen values.
 *
 * BOX DRAWING IS CSS, NOT TEXT. There are no U+2500 characters in the markup.
 * A screen reader would read them out one by one, which is the accessibility
 * failure this repo already refuses in its PDFs: never declare structure you do
 * not deliver, and never bury the structure you do deliver under decoration.
 */

/* -- frame ----------------------------------------------------------------- */

.frame {
  position: relative;
  padding: calc(var(--lh) / 2) var(--pad-x);
  border: 1px solid var(--rule);
  background: var(--bg-raise);
}

.frame__label {
  display: block;
  margin-block-end: calc(var(--lh) / 2);
  color: var(--fg-dim);
  font-size: 0.875rem;
}

/* The shell prompt in front of a labelled block. Generated, with an empty alt
 * text so assistive technology that honours it omits the glyph entirely. */
.frame__label::before {
  content: "$ " / "";
  color: var(--axis-public);
}

/* -- search ---------------------------------------------------------------- */

.search {
  display: flex;
  align-items: baseline;
  gap: var(--cw);
  padding: calc(var(--lh) / 4) var(--cw);
  border: 1px solid var(--rule);
  background: var(--bg);
}

.search:focus-within { border-color: var(--focus); }

.search::before {
  content: "> ";
  color: var(--axis-public);
}

.search__input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--fg-strong);
  font: inherit;
}

.search__input:focus { outline: none; }        /* the box shows focus instead */
.search__input::placeholder { color: var(--fg-dim); }

.search__hint {
  flex: none;
  color: var(--fg-dim);
  font-size: 0.875rem;
}

.search__status {
  margin-block: calc(var(--lh) / 2) 0;
  color: var(--fg-dim);
  font-size: 0.875rem;
}

/* -- skills table ---------------------------------------------------------- */

.skills {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}

.skills th,
.skills td {
  padding: calc(var(--lh) / 6) var(--cw) calc(var(--lh) / 6) 0;
  vertical-align: baseline;
  font-weight: 400;
}

/* Column widths in character cells, so the count and its bar stay on one line.
 * Allowed to wrap, the bar drops below its number and stops reading as a
 * measurement of it. */
.skills__col-skill  { width: 18ch; }
.skills__col-public { width: 16ch; }
.skills__col-repos  { width: 7ch; }

.skills__count { white-space: nowrap; }

.skills thead th {
  color: var(--fg-dim);
  font-size: 0.875rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  border-block-end: 1px solid var(--rule);
}

.skills tbody th { color: var(--fg-strong); }
.skills tbody tr:hover { background: var(--bg-raise); }

/* Axis 1 is green because it is checkable; axis 2 is cyan because it is
 * attested. See tokens.css. */
.axis-public { color: var(--axis-public); }
.axis-paid   { color: var(--axis-paid); }

.skills__employers { color: var(--fg-dim); font-size: 0.875rem; }

/* A meter never appears without its number: the bar is decoration sized in
 * character cells, the count beside it is the actual content.
 *
 * The bar takes the same axis class as its number, so a green bar always means
 * "verifiable" and a cyan one always means "paid". A bar in the wrong colour
 * would quietly misreport which axis it belongs to. */
.meter {
  display: inline-block;
  letter-spacing: -0.05em;
}

/* -- axes legend ----------------------------------------------------------- */

.axes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr));
  gap: var(--lh) calc(var(--cw) * 2);
}

.axes__box { border: 1px solid var(--rule); padding: calc(var(--lh) / 2) var(--pad-x); }
.axes__box--public h3 { color: var(--axis-public); }
.axes__box--paid h3 { color: var(--axis-paid); }
.axes__box p { margin-block-end: 0; font-size: 0.9375rem; }

/* -- cast ------------------------------------------------------------------ */

.cast { margin: 0 0 var(--lh); }

.cast img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  /* A recording is a photograph of a terminal. It keeps the colours it was
   * recorded with, in both themes, rather than being repainted to match the
   * page -- see /colophon/. */
  background: #0b0f0d;
}

.cast figcaption { margin-block-start: calc(var(--lh) / 4); color: var(--fg-dim); font-size: 0.875rem; }
.cast details { margin-block-start: calc(var(--lh) / 4); }
.cast summary { color: var(--fg-dim); cursor: pointer; }

.cast pre {
  overflow-x: auto;
  padding: calc(var(--lh) / 2) var(--pad-x);
  border: 1px solid var(--rule);
  background: var(--bg-raise);
  font-size: 0.875rem;
}

/* -- curriculum vitae ------------------------------------------------------ */

.cv dt { margin-block-start: var(--lh); }
.cv dd { margin-inline-start: calc(var(--cw) * 2); }
.cv__skills { margin-block: calc(var(--lh) / 4) 0; font-size: 0.875rem; }

/* The commercial seam. --paid-seam appears on this page and nowhere else, so
 * money is visually quarantined and never colours the evidence. */
.seam { border-inline-start: 2px solid var(--paid-seam); padding-inline-start: var(--pad-x); }
.seam h2 { color: var(--paid-seam); }

/* -- toggles --------------------------------------------------------------- */

.toggle {
  border: 0;
  padding: 0;
  background: none;
  color: var(--fg-dim);
  font: inherit;
  cursor: pointer;
}

.toggle:hover { color: var(--fg-strong); }
.toggle__value { color: var(--fg-strong); }

/* The toggles are the only controls that require scripting, so they are not
 * rendered at all until it is available -- a control that does nothing is a
 * small lie. base.html adds .has-js to the root. */
.js-only { display: none; }
:root.has-js .js-only { display: inline-block; }

/* -- publications ---------------------------------------------------------- */

.pub { margin-block-end: var(--lh); }
.pub__title { margin-block: 0 calc(var(--lh) / 4); }
.pub__cite { margin-block: 0 calc(var(--lh) / 4); font-size: 0.875rem; }
.pub__refs { margin-block-start: calc(var(--lh) / 4); font-size: 0.875rem; }
.pub__refs summary { color: var(--fg-dim); cursor: pointer; }
.pub__refs ol { margin-block: calc(var(--lh) / 4) 0; padding-inline-start: calc(var(--cw) * 4); }
.pub__refs li { margin-block-end: calc(var(--lh) / 6); overflow-wrap: anywhere; }

/* The reference a marker points at, once it has been jumped to. */
.pub__refs li:target { color: var(--fg-strong); }

/* -- figures set beside the opening text ----------------------------------- */

/* Used on /writing/ and /about/: a drawing to the side, with the heading and
 * first paragraph wrapping around it. No frame and no panel -- it reads as part
 * of the page rather than as an inserted plate.
 *
 * Kept floated at every width; 30% of a narrow viewport is already small, so it
 * shrinks rather than stacking. */
.aside-figure {
  /* `right` first: `inline-end` is newer, and a browser that ignores it would
     drop the float entirely. */
  float: right;
  float: inline-end;
  width: 30%;
  max-width: 200px;
  min-width: 84px;
  margin: 0 0 calc(var(--lh) / 2) calc(var(--cw) * 2);
}

.aside-figure svg {
  display: block;
  width: 100%;
  height: auto;
}

/* The writer sits a little narrower than the shared default: at 30% it crowded
 * the four lines of text beside it. Only this drawing -- the lute on /about/
 * keeps the full width. */
.aside-figure--writer { width: 26%; max-width: 170px; }

/* Much taller than it is wide, so the shared 200px cap would leave it hanging
 * far below the paragraph it sits beside and open a gap before the next
 * heading. Width is set from the height it may occupy, not the other way.
 *
 * The redrawn juggler is taller again -- 75x191 where the old one was 73x131 --
 * so the cap stays below the shared 200px. It draws about 265 tall here; the
 * old drawing at its old cap was 222, and 124px under the new ratio would be
 * 316, which is the overhang this rule exists to prevent. */
.aside-figure--juggle { width: 18%; max-width: 104px; }

/* Safari derives no height from a viewBox alone, so `height: auto` resolves to
 * zero and the drawing is laid out but never painted. One ratio per drawing,
 * matching its own viewBox. */
.aside-figure--writer svg { aspect-ratio: 43 / 42; }
.aside-figure--erik svg   { aspect-ratio: 70 / 65; }
.aside-figure--lute svg   { aspect-ratio: 74 / 81; }
.aside-figure--skate svg  { aspect-ratio: 109 / 116; }
.aside-figure--juggle svg { aspect-ratio: 75 / 191; }

/* -- music ----------------------------------------------------------------- */

/* .score and .score__paper are further down, under "lilypond scores" -- they
 * are the contract with music-of-the-stars' theme-scores.py and were already
 * here. What follows is only the page furniture around them. */

/* The engraving as one figure with one caption, however many systems it took.
 * The systems are aria-hidden; this caption is the accessible name.
 *
 * Cleared, because a floated drawing sits beside the heading and the player
 * above it: a block box with a border does not wrap around a float, it slides
 * underneath one, so the lute would end up drawn on top of the staves. Same
 * for the list on the index. */
.score-set {
  clear: both;
  margin: 0 0 var(--lh);
  padding: calc(var(--lh) / 2) var(--pad-x);
  border: 1px solid var(--rule);
  /* Paper, not the raised surface: a score is a picture of a printed page, and
   * .score__paper fills to --bg, so the frame has to agree with it. */
  background: var(--bg);
}

.score-set figcaption {
  margin-block-start: calc(var(--lh) / 2);
  color: var(--fg-dim);
  font-size: 0.875rem;
}

/* Left to the browser's own player. A custom one would need its own focus
 * handling, its own keyboard contract and its own reduced-motion behaviour --
 * three promises to keep where the native element already keeps them, on the
 * one element that exists FOR accessibility. It would also have to be built as
 * progressive enhancement, because this site works without scripting and the
 * recording is what a visitor gets instead of the engraving.
 *
 * The controls follow the theme without any of that: color-scheme is set per
 * theme in terminal.css, which is what tells the UA to draw dark media
 * controls. The frame around it carries the site's own voice. */
.piece__audio {
  display: block;
  width: 100%;
  margin-block: 0;
}

/* Full width, matching the column. This used to be capped at 34rem, which was
 * right while the browser drew the controls: a native player is a finished
 * widget, and the extra space only stretched it.
 *
 * The built bar spends it instead. The button and the clock are fixed, so
 * every additional pixel goes to the seek input -- and a longer track is a
 * finer one, both to drag and to step through with the arrow keys. The page
 * itself is already bounded by --measure, so "full width" here means as wide
 * as the text beside it and no wider. */

/* -- the built player ------------------------------------------------------ */

/* Only present once player.js has run: it adds .player--enhanced after
 * replacing the native controls. Nothing here hides anything -- if the script
 * never runs, <audio controls> is still in the markup and still works. */
/* The list-wide control. Given room above the pieces rather than tucked into
 * the first one: it acts on all of them. */
.playall { margin-block: 0 var(--lh); }

.player {
  display: flex;
  align-items: center;
  gap: var(--cw);
}

.player__toggle {
  flex: none;
  min-width: 7ch;
  padding: calc(var(--lh) / 6) var(--cw);
  border: 1px solid var(--rule);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  text-align: center;
  cursor: pointer;
}

.player__toggle:hover { border-color: var(--fg-dim); color: var(--fg-strong); }

/* Elapsed time. Tabular figures so the bar does not shift a pixel every
 * second as the digits change width. */
.player__time {
  flex: none;
  color: var(--fg-dim);
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The seek bar. A real <input type="range">, restyled -- which is a different
 * proposition from restyling media controls: range is a standard element with
 * documented pseudo-elements in every engine, and it keeps its own keyboard
 * behaviour whatever it looks like. */
.player__seek {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
}

.player__seek:disabled { opacity: 0.5; }

.player__seek::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--rule);
}
.player__seek::-moz-range-track {
  height: 2px;
  background: var(--rule);
}

/* A square, because everything else on this site is drawn on a character
 * grid. Sized well past the 24px target minimum in both directions once the
 * track around it is counted. */
.player__seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 10px;
  height: 10px;
  margin-block-start: -4px;
  background: var(--fg);
  border: 0;
  border-radius: 0;
}
.player__seek::-moz-range-thumb {
  width: 10px;
  height: 10px;
  background: var(--fg);
  border: 0;
  border-radius: 0;
}

/* A piece that belongs with another. Set apart from the description so the
 * relation reads as a fact about the pair rather than as more prose about
 * this one. */
.related {
  margin-block: 0 var(--lh);
  padding-inline-start: var(--pad-x);
  border-inline-start: 1px solid var(--rule);
  color: var(--fg-dim);
  font-size: 0.9375rem;
}

.pieces { margin: 0; clear: both; }

.pieces__item {
  margin-block-end: calc(var(--lh) * 1.5);
  padding-block-end: var(--lh);
  border-block-end: 1px solid var(--rule);
}

/* Sung lines, so they break where the song breaks rather than where the column
 * happens to end. A verse is a paragraph and each line is its own line; nothing
 * here reflows to fill the measure. */
.lyrics__text p {
  margin-block: 0 var(--lh);
  max-width: 46ch;
}

/* The translation reads as apparatus rather than as a second set of words:
 * dimmed and a size down, so the eye knows which one is the song. */
.lyrics__text--translation { color: var(--fg-dim); font-size: 0.9375rem; }

.lyrics__label {
  margin-block: 0 calc(var(--lh) / 2);
  color: var(--fg-dim);
  font-size: 0.875rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.pieces__item:last-child { border-block-end: 0; }
.pieces dt, .pieces dd { margin: 0; }
.pieces__title { margin-block: 0 calc(var(--lh) / 4); }
.pieces__sub { margin-block: 0 calc(var(--lh) / 2); }

/* -- the drawings, explained on /colophon/ -------------------------------- */

/* A definition list: the drawing and where it lives are the term, the reason is
 * the definition. Deliberately not .aside-figure -- that one floats inline-end
 * beside running prose, which is the wrong shape here, and its per-drawing
 * width caps are tuned for that context.
 *
 * The thumbnails share one width so the column reads as a list rather than as
 * seven differently-sized plates; the ratios below keep each drawing itself
 * undistorted. */
.drawings { margin: 0; }

.drawings__item {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 0 calc(var(--cw) * 3);
  margin-block-end: var(--lh);
  align-items: start;
}

.drawings dt { margin: 0; }
.drawings dd { margin: 0; }
.drawings dd p { margin-block: 0 calc(var(--lh) / 3); }
.drawings dd p:last-child { margin-block-end: 0; }

.drawings__fig { margin: 0 0 calc(var(--lh) / 4); line-height: 0; }

.drawings__fig svg {
  display: block;
  width: 100%;
  height: auto;
}

/* One ratio per drawing, matching its own viewBox -- same reason as the
 * .aside-figure ratios above: Safari derives no height from a viewBox alone, so
 * `height: auto` resolves to zero and the drawing is laid out but never
 * painted. The values live with each SVG's viewBox, not with this layout. */
.drawings__fig--erik   svg { aspect-ratio: 70 / 65; }
.drawings__fig--lute   svg { aspect-ratio: 74 / 81; }
.drawings__fig--writer svg { aspect-ratio: 43 / 42; }
.drawings__fig--car    svg { aspect-ratio: 846 / 62; }
.drawings__fig--hammer svg { aspect-ratio: 204 / 113; }
.drawings__fig--juggle svg { aspect-ratio: 75 / 191; }
.drawings__fig--skate  svg { aspect-ratio: 109 / 116; }

/* The car again. In a 96px column its 13.6:1 road would draw the car about
 * seven pixels tall -- present, and useless as a reference image. Scaled so the
 * car itself fills the column (96/184 per unit puts the road at ~460%) and the
 * empty remainder is clipped. */
.drawings__fig--car { overflow: hidden; }
.drawings__fig--car svg { width: 460%; }

/* Driving off the strip is the point on /writing/ and a defect here: a reader
 * looking the drawing up would find an empty box for most of the cycle. Pinned
 * to its resting frame, in this context only. */
html[data-fx="full"] .drawings .drawings__fig--car .car__drive { animation: none; }

/* Under a narrow viewport the two columns crowd each other; stack instead, with
 * the thumbnail at the size it would have had beside the text. */
@media (max-width: 34rem) {
  .drawings__item { grid-template-columns: 1fr; }
  .drawings__fig { max-width: 96px; }
}

/* No pointer, no underline, no external-link mark: meant to be stumbled upon,
 * not signposted. Everything non-visual stays normal -- a real anchor with a
 * real name, and :focus-visible still draws its ring. */
.aside-figure__link {
  display: block;
  cursor: default;
  text-decoration: none;
}

/* terminal.css appends an arrow to every target="_blank" link. These open in
 * the same tab so the rule does not apply; kept as a guard in case that
 * attribute is ever added, which would give the game away. */
.aside-figure__link::after { content: none; }

/* The block must contain its own float, or the drawing overhangs the heading
 * below it whenever the text beside it is shorter than the image. */
.intro { display: flow-root; }

/* The same work, published again elsewhere. Indented under its primary
 * citation so it reads as one article with two appearances, not as two. */
.pub__also {
  margin-block: 0 calc(var(--lh) / 4);
  padding-inline-start: calc(var(--cw) * 2);
  border-inline-start: 1px solid var(--rule);
  font-size: 0.875rem;
}

/* -- conferences ----------------------------------------------------------- */

.confs { width: 100%; border-collapse: collapse; text-align: left; }

.confs th,
.confs td {
  padding: calc(var(--lh) / 6) var(--cw) calc(var(--lh) / 6) 0;
  vertical-align: baseline;
  font-weight: 400;
}

.confs th { color: var(--fg-strong); white-space: nowrap; }

/* A date range broken across two lines stops reading as one date. */
.confs__date { white-space: nowrap; }

.confs__work { margin: 0; padding-inline-start: calc(var(--cw) * 2); }
.confs__work li { margin-block-end: calc(var(--lh) / 6); }
.confs td { font-size: 0.9375rem; }

@media (max-width: 60ch) {
  /* Each conference becomes its own stacked block rather than a squeezed row. */
  .confs, .confs tbody, .confs tr, .confs th, .confs td { display: block; width: auto; }
  .confs__date { white-space: normal; }
  .confs tr { margin-block-end: calc(var(--lh) / 2); }
  .confs td:empty { display: none; }
}

/* -- community roles ------------------------------------------------------- */

.roles dt { margin-block-start: calc(var(--lh) / 2); }
.roles dd { margin-inline-start: calc(var(--cw) * 2); color: var(--fg-dim); }
.roles dd:empty { margin: 0; }
.roles__people { margin-block: calc(var(--lh) / 4) 0; }

/* A decorative drawing inside a <dd>.
 *
 * Bounded rather than full-width: it is a joke in the margin of the text, and
 * at the column's full width it would outweigh the entry it belongs to. */
.roles__drawing {
  margin: calc(var(--lh) / 2) 0;
  margin-inline-start: calc(var(--cw) * -2);
}

.roles__drawing svg {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
}

/* A photograph inside a <dd>, spanning the whole text column.
 *
 * The negative inline start margin is exactly the indent .roles dd applies, so
 * the picture lines up with the prose above the list instead of with the note
 * beside it. */
.roles__photo {
  margin: calc(var(--lh) / 2) 0;
  margin-inline-start: calc(var(--cw) * -2);
}

/* The positioning context for any overlay. The border sits here rather than on
 * the image so the overlay's percentages resolve against the picture itself and
 * not against the picture plus two border pixels. */
.roles__frame {
  position: relative;
  display: block;
  line-height: 0;
  border: 1px solid var(--rule);
}

.roles__photo img {
  display: block;
  width: 100%;
  height: auto;
  /* Drawn in pixel art. The browser's default smoothing would average the
   * blocks into mush at any width other than the intrinsic one, and the whole
   * point of the picture is that the blocks are visible. */
  image-rendering: pixelated;
}

/* A recording light on the photograph.
 *
 * It rests LIT, which is how the lamp is drawn in the picture: with motion off,
 * or before any script runs, the overlay is indistinguishable from the image
 * underneath it. The animation only takes it away, briefly. */
.roles__led {
  position: absolute;
  left: var(--led-x);
  top: var(--led-y);
  width: var(--led-w);
  height: var(--led-h);
  background: var(--led-on);
}

html[data-fx="full"] .roles__led {
  animation: roles-led 3.4s steps(1, end) infinite;
}

/* Off for a fifteenth of the cycle: a camera light stuttering, not a beacon.
 * A long even blink at the edge of vision is the kind of motion that pulls the
 * eye away from the text for the whole time the page is open. */
@keyframes roles-led {
  0% { background: var(--led-on); }
  91% { background: var(--led-off); }
  95% { background: var(--led-on); }
}

.roles__photo figcaption {
  margin-block-start: calc(var(--lh) / 4);
  color: var(--fg-dim);
  font-size: 0.875rem;
}

/* -- books ----------------------------------------------------------------- */

.books dt { margin-block-start: var(--lh); }
.books dd { margin-inline-start: calc(var(--cw) * 2); color: var(--fg-dim); }

/* Evidence that the book works on its own, set apart from the description of
 * what it is. Green: it is a checkable claim about something that happened,
 * not a promise about the book. */
.books__use {
  margin-block: calc(var(--lh) / 4) 0;
  padding-inline-start: var(--cw);
  border-inline-start: 2px solid var(--axis-public);
  color: var(--fg);
}

/* How a book is made, where the book itself cannot be read. Dimmed and set
 * apart: it is not part of the description, it is what a reader can do about
 * it. No axis colour -- green means publicly verifiable on this site, and this
 * is an aside about method, not a claim about the work. */
.books__method {
  margin-block: calc(var(--lh) / 4) 0;
  padding-inline-start: var(--cw);
  border-inline-start: 1px solid var(--rule);
  color: var(--fg-dim);
  font-size: 0.9375rem;
}

/* -- full-width strip ------------------------------------------------------ */

/* A thin band across the column, under a heading.
 *
 * Full width AND 60px tall is not something a 300x62 drawing can be: forcing it
 * crops the art, and the first attempt cut the driver's head off. The canvas was
 * widened to 846x62 instead, which is about 13.6:1, so the natural ratio gives
 * about 60px at the full measure with nothing cropped. The car is scaled down
 * and simply has more road to cross. */
.strip {
  margin: 0 0 var(--lh);
  line-height: 0;          /* no descender gap under an inline-level child */
  /* The road runs wider than the measure -- see below. Without this the page
   * itself scrolls sideways, which nothing on this site is allowed to do. */
  overflow: hidden;
}

/* The car is 184 units of an 846-unit road: at the full measure it drew about
 * 50px tall, which is smaller than it deserves. Scaling the whole drawing up
 * and clipping the empty road is the one lever that leaves the SVG alone --
 * its animation is tuned to that geometry (steps(485) is one pixel per step,
 * and the negative delay is 184/1031 of the cycle so the car starts flush at
 * x=0). Changing the viewBox to shorten the road would silently break both.
 *
 * No inline-start offset: the car rests at x=0, so shifting to centre the
 * overflow would park it out of sight. The extra road spills off to the right,
 * where there is nothing drawn anyway. */
.strip svg {
  display: block;
  width: 150%;
  height: auto;
  /* Safari derives no height from a viewBox alone; without this the strip is
   * laid out and never painted. Ratio matches the widened viewBox. */
  aspect-ratio: 846 / 62;
}

/* -- lilypond scores ------------------------------------------------------- */

/* Scores engraved by LilyPond (the music-of-the-stars book), converted to SVG
 * and inlined into the page -- the same reason as the drawings above: inline
 * SVG inherits currentColor, while an <img src="x.svg"> inherits neither that
 * nor the theme, since it cannot see this stylesheet at all.
 *
 * LilyPond draws black on white. The staves, stems and glyphs are rewritten to
 * currentColor when the SVG is generated. The one mark that cannot simply be
 * dropped is the whiteout behind each TAB fret number: it masks the string
 * line running behind the digit, so removing it would draw the line straight
 * through the number. Painting it --bg keeps it masking in either theme
 * instead of leaving a white patch on a dark page.
 *
 * No aspect-ratio here, unlike .drawings__fig: these carry width and height
 * attributes, so Safari has an intrinsic ratio to work from.
 *
 * WIDTH COMES FROM THE PIECE, not from this rule. scores.py writes an inline
 * percentage on every system: the widest one in a piece reaches the full
 * column and the rest are drawn as their own fraction of it, so the set keeps
 * one scale. Stretching each system to 100% independently would blow up a
 * short one -- de-kelk-van-frank has systems from 7 to 351 units, and the 7
 * would become a single page-wide notehead. max-width stays as the floor for
 * anything that arrives without that inline style. */
.score {
  display: block;
  max-width: 100%;
  height: auto;
  /* Its own token, not --fg: the ink is softened in the dark themes and left
     alone in the light one. See tokens.css for why that asymmetry is right. */
  color: var(--score-ink);
}

.score__paper { fill: var(--bg); }
