/* Euclid Circular A is licensed to Triglav Modular and served from
   triglavmodular.hu, where that licence already applies.  It is deliberately
   NOT copied into this repository: the repo is public and Unlicense-d, and a
   licence to use the face is not a licence to redistribute the files.
   Loading it cross-origin needs one header on that server:

       <FilesMatch "\.(woff2?)$">
         Header set Access-Control-Allow-Origin "*"
       </FilesMatch>

   Without it the page falls back to the system sans and still works — which
   is also what happens offline, opening web/index.html from a local clone.

   IBM Plex Mono is SIL OFL 1.1 and ships here; licence beside the files. */
@font-face {
  font-family: "Euclid Circular A";
  font-weight: 300;
  font-display: swap;
  src: url("https://triglavmodular.hu/wp-content/uploads/EuclidCircularA-Light-WebXL.woff2") format("woff2");
}
@font-face {
  font-family: "Euclid Circular A";
  font-weight: 400;
  font-display: swap;
  src: url("https://triglavmodular.hu/wp-content/uploads/EuclidCircularA-Regular-WebXL.woff2") format("woff2");
}
@font-face {
  font-family: "Euclid Circular A";
  font-weight: 500;
  font-display: swap;
  src: url("https://triglavmodular.hu/wp-content/uploads/EuclidCircularA-Medium-WebXL.woff2") format("woff2");
}
@font-face {
  font-family: "Euclid Circular A";
  font-weight: 700;
  font-display: swap;
  src: url("https://triglavmodular.hu/wp-content/uploads/EuclidCircularA-Bold-WebXL.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 400;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Regular.woff2?v=ba204497") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 600;
  font-display: swap;
  src: url("fonts/IBMPlexMono-SemiBold.woff2?v=6a825b48") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 700;
  font-display: swap;
  src: url("fonts/IBMPlexMono-Bold.woff2?v=ea576f38") format("woff2");
}
/* sync-site:begin */
/* The palette and the hover sweep belong to the theme on
   triglavmodular.hu, and are pulled from it so that the two cannot
   drift apart by hand - which they had, twice, before this existed.
   Only the values come across; the rules that use them are this
   page's own, further down.  Written by tools/sync-site.py: change
   the theme, not this block. */
:root {
  --bg: #171716;
  --panel: #1e1e1c;
  --panel2: #292928;
  --line: #2e2e2b;
  --ink: #fff;
  --muted: #9d9a93;
  --accent: #FFDA6C;
  --accent2: #F477BA;
  --ok: #8fd6a0;
  --bad: #ff8b7d;
  --sweep: linear-gradient(120deg,
                        var(--muted) 0 33.334%,
                        var(--accent2) 50%,
                        var(--accent) 66.666% 100%);
  --sweep-slide: .5s ease-in-out;
  --sweep-pass: .35s .15s ease-out;
  --brand-left: 8px;
  --nav-icon-cart-right: 44px;
  --nav-icon-account-right: 16px;
}
/* sync-site:end */
/* This page's own, which the theme carries no equivalent of.  --warn reads
   from the accent rather than repeating its hex: it is the accent, used to
   mean something, and one of them moving without the other would be a bug. */
:root {
  --warn: var(--accent);
  --sans: "Euclid Circular A",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --mono: "IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 4.5l7.5 7.5-7.5 7.5"/></svg>');
}
* {
  box-sizing: border-box;
  font-variant: tabular-nums;
}
/* --- site header ----------------------------------------------------------
   Matches the navigation on triglavmodular.hu: uppercase, 2px tracking,
   right-aligned against the mark.  Rebuilt in this sheet rather than pulled
   from the theme - the page has to stand up on its own on GitHub Pages too,
   and one stylesheet is easier to keep true than a copy of someone else's. */
.sitenav {
  position: relative;
  z-index: 2;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
}
/* The article column exactly, so the menu starts on the line the page does.
   It only fits there because the mark left the row: the nav needs 765px on
   one line and the column leaves 780, which was not enough while the mark
   and its gap were still spending 54 of them. */
.sitenav-in {
  max-width: 820px;
  margin: 0 auto;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  gap: 20px;
}
.brand {
  position: absolute;
  left: var(--brand-left);
  top: 50%;
  transform: translateY(-50%);
  display: block;
  line-height: 0;
  color: #fffdfb;
  transition: color .15s;
}
/* The mark is a glyph on nothing now, not a badge, so it takes no rounding
   and no box - only the room it draws in.  Inlined rather than referenced,
   because its colour is the one thing about it that now has to change and an
   <img> carries its own: this way fill and stroke are currentColor and the
   mark follows the link it sits in.  It was a mask first, which is how the
   disclosure chevron does the same trick, and it went invisible - a mask
   image that does not load masks everything away rather than failing
   loudly, and it has an origin and an engine to satisfy before it loads.
   Two paths and no fetch has neither problem.

   The file's own <style> is dropped on the way in: it names .cls-1 and
   .cls-2, which would leak into the page as the banana's would.  Its two
   stroke widths are the only thing it carried that the page cannot say, so
   they come across as attributes; #fffdfb, the white it drew in, is on
   .brand above.

   margin and overflow are reset for the same reason the slot icons reset
   theirs: the svg rule further down this sheet dresses the page's diagrams,
   and it reaches any svg that is not told otherwise.  Its margin-top put
   10px above the glyph, which centring the box then hid by leaving the mark
   sitting low in it. */
.brand .mark {
  display: block;
  width: 34px;
  height: 34px;
  margin: 0;
  overflow: hidden;
  fill: currentColor;
}
/* White into accent, so it takes the pass like the rest of the row. */
.brand:hover,
.brand:focus-visible {
  color: var(--accent2);
  animation: sweep-ink var(--sweep-pass) forwards;
}
.sitenav nav {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
  justify-content: flex-start;
}
.sitenav nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  transition: color .15s;
}
.sitenav nav a:hover {
  color: var(--accent);
}
.sitenav nav a[aria-current=page] {
  color: var(--accent);
}
/* Cart and account belong to the shop rather than to this page, so they sit
   back a step without leaving the row. */
.sitenav nav a.minor {
  opacity: .62;
}
/* Those two are marks rather than words on triglavmodular.hu, and this row is
   meant to be that row, so they are marks here too - the classes come across
   with the rest of the menu, from the same block that draws them there.
   The icons are data rather than fetched: a mask image that fails to load
   masks its element away instead of failing loudly, which this page has
   already had once from the brand mark, and data has nothing to fail at. */
.sitenav {
  --icon-cart: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M1.9 3.1h2.9l2.4 11.1a1.7 1.7 0 0 0 1.7 1.4h8.3a1.7 1.7 0 0 0 1.7-1.4L20.5 6.9H5.7"/><circle cx="9.6" cy="20" r="1.5"/><circle cx="17.4" cy="20" r="1.5"/></svg>');
  --icon-account: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="3.9"/><path d="M4.7 20.4a7.3 7.3 0 0 1 14.6 0"/></svg>');
}
.sitenav nav a.nav-icon {
  display: flex;
  align-items: center;
}
/* The word stays for anyone who is not looking at the row. */
.sitenav nav a.nav-icon span {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* The same sweep the words take, on the mark instead of on letters: the
   gradient is three times the width, the mask cuts the icon out of it, and
   hovering slides it from the muted end to the accent through the pink. */
.sitenav nav a.nav-icon::before {
  content: "";
  display: block;
  width: 18px;
  height: 18px;
  background-image: var(--sweep);
  background-size: 300% 100%;
  background-position: 0 0;
  background-repeat: no-repeat;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
  transition: background-position var(--sweep-slide);
}
.sitenav nav a.nav-icon-cart::before {
  --icon: var(--icon-cart);
}
.sitenav nav a.nav-icon-account::before {
  --icon: var(--icon-account);
}
.sitenav nav a.nav-icon:hover::before,
.sitenav nav a.nav-icon:focus-visible::before {
  background-position: 100% 0;
}
/* Above the breakpoint the pair leaves the row and pins to the window's right
   edge, the way it does on the site: the menu reads left from the column and
   the shop reads right from the corner, and the gap between them is the header
   rather than a gap between words.  Below it they stay in the row, which is
   where the mark goes at that width too.  Their offsets are the theme's; the
   breakpoint cannot be, because a media query cannot read a custom property. */
@media (min-width: 901px) {
  .sitenav nav a.nav-icon {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
  }
  .sitenav nav a.nav-icon-cart {
    right: var(--nav-icon-cart-right);
  }
  .sitenav nav a.nav-icon-account {
    right: var(--nav-icon-account-right);
  }
}
/* A mark at 18px carries less ink than a word at 12, so .minor's .62 would put
   these further back than the step they are meant to take. */
.sitenav nav a.nav-icon.minor {
  opacity: .8;
}
.sitenav nav a.nav-icon.minor:hover,
.sitenav nav a.nav-icon.minor:focus-visible {
  opacity: 1;
}

/* No widows or orphans.  text-wrap inherits, so pretty goes on the root and
   reaches every block on the page - including ones added later, which a list
   of selectors would have missed each time the markup changed.  It asks the
   line breaker to look ahead rather than leave one word alone on a last line.
   Headings and short labels take balance instead, which evens their lines.
   Both are ignored where unsupported, so the fallback is ordinary wrapping. */
h1,
h2,
h3,
h4,
summary,
.opt .t,
.vpo button,
.tag,
.kbdhint,
th {
  text-wrap: balance;
}
/* text-size-adjust is the off switch for Safari's own idea of how big body
   copy should be on a phone.  Left alone it inflates the text of blocks it
   judges to be the main content, block by block and by a ratio of its own
   choosing - which is why two option descriptions, the same element with
   the same 16px rule, rendered at visibly different sizes: the longer one
   wrapped enough lines to look like an article and was boosted, its
   neighbour was not.  100% means "the size I asked for", and it has to be
   set here rather than on a narrow rule, because the algorithm reads the
   block that carries the text. */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  text-wrap: pretty;
  font: 400 16px/1.62 var(--sans);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
/* The art is white strokes on transparency - 97% of the image has zero alpha -
   so it simply lies over the background at reduced opacity.  No blend mode:
   screen was only ever compensating for a flattened, opaque copy.  Fixed, so
   it sits still while the page scrolls past. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: url("images/wave_bg.png?v=22d1adf0") center/cover no-repeat;
  opacity: .5;
}
.wrap {
  position: relative;
  z-index: 1;
  max-width: 820px;
  margin: 0 auto;
  padding: 56px 20px 120px;
}
/* The banana rides with the title, scaled off the same clamp so it keeps its
   proportion to the type at every width.  Referenced rather than inlined: the
   file carries its own <style> with generic .cls-1 names, which would leak
   into the page if it were pasted in. */
.hdr {
  display: flex;
  align-items: center;
  gap: clamp(12px,2.2vw,22px);
  margin: 0 0 14px;
}
.hdr h1 {
  margin: 0;
}
.banana {
  width: 140px;
  position: absolute;
  z-index: -3;
  height: auto;
  top: 40px;
  right: 30px;
  flex: 0 0 auto;
  display: block;
  transform: rotate(-12deg) scale(1.06);
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.hdr:hover .banana {
  transform: rotate(0deg) scale(1);
}
@media (prefers-reduced-motion:reduce) {
  .banana {
    transition: none;
  }
  .hdr:hover .banana {
    transform: rotate(-12deg) scale(1.06);
  }
}
h1 {
  font-size: clamp(44px,9vw,82px);
  font-weight: 900;
  line-height: .98;
  margin: 0;
  letter-spacing: -.015em;
}
/* Positioned so the changelog panel can hang off the paragraph rather than
   off the pill inside it.  The pill sits wherever the sentence ends, which on
   a narrow window is close enough to the right edge that a panel anchored to
   it would open off-screen; the paragraph's own edges are the content
   column's, at every width. */
.sub {
  position: relative;
  color: var(--muted);
  margin: 0 0 44px;
  font-size: 18px;
}
/* The version comes from the same GEN.version the flashers are stamped with,
   so the page can never advertise a release the build does not produce. */
.chlog { display: inline-block; }
.chlog-pill {
  background: none;
  cursor: pointer;
  font: inherit;
  color: var(--muted);
}
.chlog-pill[aria-expanded="true"], .chlog-pill:hover { color: var(--accent); }
/* The panel holds every release, so it is sized to be read rather than
   glanced at: wide enough for an entry to take two lines at most, and capped
   against the viewport so a long entry cannot push the page sideways on a
   phone.  Four releases overflow a short window, hence the scroll. */
.chlog-body {
  display: block;
  position: absolute;
  top: calc(100% + 18px);
  /* Centred on the pill, which only script can do: the offset depends on where
     the sentence ends, and CSS centring on the pill would hang a 420 panel
     over the edge of a phone.  centreChangelog() writes the left, clamped to
     the paragraph; until it runs, the paragraph's right edge is the fallback,
     and it is the same guarantee the left edge was - the content column's, at
     every width.  The containing block stays the paragraph either way, so the
     percentage sizing and the off-screen guard are unchanged. */
  right: 0;
  left: var(--chlog-left, auto);
  z-index: 10;
  width: max-content;
  min-width: min(300px, 100%);
  max-width: min(420px, 100%);
  max-height: min(60vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 18px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  font-size: 14px;
  line-height: 1.5;
  text-transform: none;
  letter-spacing: normal;
}
.chlog-body.hidden { display: none; }
/* A rule between releases, not around them: the entries are the content and
   a box each would make four cards out of one list. */
.chlog-rel + .chlog-rel {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
/* The page's label treatment, as the option headings use it.  The date sits
   on the same baseline in muted, so the version stays the thing being read. */
.chlog-ver {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 8px;
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
}
.chlog-date {
  color: var(--muted);
  font-weight: 400;
  letter-spacing: .06em;
}
.chlog-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.chlog-list li {
  position: relative;
  padding-left: 16px;
}
.chlog-list li + li { margin-top: 6px; }
/* Drawn rather than a list marker, so a wrapped entry hangs off the dot
   instead of running back under it.  The offset centres the dot on the first
   line: half the line box, less half the dot. */
.chlog-list li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: calc((1.5em - 5px) / 2);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}
.ver {
  display: inline-block;
  margin-left: 10px;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  vertical-align: 2px;
}
section {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 28px;
  margin-bottom: 20px;
}
h2 {
  font-size: 20px;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--accent);
  margin: 0 0 20px;
  font-weight: 700;
  text-align: left;
}
h3,
h4 {
  font-weight: 700;
  letter-spacing: .02em;
}
/* The four option groups live inside step 2 rather than beside it: cards on a
   card, so they need their own surface a step up from the section's, or the
   nesting would not read at all. */
.group {
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 20px 22px 6px;
  margin: 0 0 14px;
}
.group:last-of-type {
  margin-bottom: 0;
}
.group>h3 {
  margin: 0 0 14px;
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--muted);
  font-weight: 700;
}
/* Inputs inside a group sit on the lighter surface, so they take the darker
   one to stay legible. */
.group td input,
.group .kbdwrap {
  background: var(--bg);
}
label.opt {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
label.opt:last-child {
  border-bottom: 0;
}
/* accent-color paints the box but leaves the browser's white tick, so the
   checkbox is drawn here instead: accent fill, dark tick. */
input[type=checkbox] {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  flex: none;
  width: 30px;
  height: 30px;
  border: 2px solid #4a4740;
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  position: relative;
  transition: .12s;
}
input[type=checkbox]:hover:not(:checked) {
  border-color: var(--accent2);
  animation: sweep-edge var(--sweep-pass) forwards;
}
input[type=checkbox]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
input[type=checkbox]:checked::after {
    content: "";
    position: absolute;
    left: 10px;
    top: 4px;
    width: 4px;
    height: 14px;
    border: solid #171716;
    border-width: 0 2.5px 2.5px 0;
    transform: rotate(45deg);
}
input[type=checkbox]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
label.opt input {
  margin-top: 6px;
}
.opt .t {
  font-weight: 700;
  display: inline-block;
}
/* The description takes the row rather than hugging its longest line.  As an
   inline-block it shrank to its widest line, which for a description with a
   forced break in it is that line exactly - so the sentence sat with nothing
   to spare and the next pixel of narrowing broke it in two.  Filling the row
   changes nothing for a description that already ran the width. */
label.opt > span {
  flex: 1;
  min-width: 0;
}
.opt .d {
  color: var(--muted);
  font-size: 16px;
  display: block;
}
.drop {
  border: 1px dashed #4a4740;
  border-radius: 16px;
  padding: 34px;
  text-align: center;
  color: var(--muted);
  cursor: pointer;
  transition: .15s;
}
.drop:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.drop:hover,
.drop.over {
  border-color: var(--accent2);
  color: var(--ink);
  animation: sweep-edge var(--sweep-pass) forwards;
}
.drop.ok {
  border-style: solid;
  border-color: var(--ok);
  animation: none;
}
.drop.err {
  border-style: solid;
  border-color: var(--bad);
  animation: none;
}
button {
  font: inherit;
  font-weight: 700;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: .08em;
  padding: 14px 26px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  transition: .15s;
}
button:hover:not(:disabled) {
  background: var(--accent);
  color: #171716;
}
button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #171716;
}
button:disabled {
  opacity: .4;
  cursor: not-allowed;
}
/* A dimmed accent fill goes olive.  A disabled primary drops to an outline
   instead, so "not yet" reads as waiting rather than as a muddy colour. */
button.primary:disabled,
button.primary:disabled:hover {
  background: transparent;
  border-color: var(--line);
  color: var(--muted);
  opacity: .75;
}
.row {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}
/* A checkbox and its label are one thing: .row's wrapping would drop the text
   onto the line below the box as soon as it stopped fitting.  The box holds
   its place and the text wraps inside its own column instead. */
label.opt.row {
  flex-wrap: nowrap;
  align-items: flex-start;
}
label.opt.row>input {
  margin-top: 3px;
}
label.opt.row>span {
  min-width: 0;
}
/* An inline-block baselines on its own last line, so a chip taller than the
   text around it drags the whole line box with it - every one of these was
   adding 6 to 7px to a line of running prose.  line-height:1 keeps the chip
   inside the line it sits on; it stays a chip, it just stops pushing.
   Chips that stand on their own line get their air back below. */
.mono {
    font-family: var(--mono);
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    margin: 0;
    display: inline-block;
    word-break: break-all;
    background: rgba(17, 16, 11, 0.51);
    padding: 3px 7px;
    border-radius: 6px;
    border: 1px solid rgba(255,255,255,0.2);
    text-decoration: none;
}
/* Alone after a break, where a taller line is the point rather than a
   side effect. */
br + .mono {
    margin: 5px 0;
}
.ok {
  color: var(--ok);
}
.bad {
  color: var(--bad);
}
.warn {
  color: var(--warn);
}
.muted {
  color: var(--muted);
}
.seg {
  display: inline-flex;
  border: 1px solid var(--accent);
  border-radius: 999px;
  overflow: hidden;
}
.seg button {
  border: 0;
  border-radius: 0;
  padding: 10px 20px;
  background: transparent;
}
.seg button[aria-pressed=true] {
  background: var(--accent);
  color: #171716;
}
details {
  margin-top: 14px;
}
#calBody {
    padding-bottom:20px;
}
summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
}
/* The knob numbers get their own right-aligned column against left-aligned
   labels, so the list scans as a list instead of as prose. */
table.knobs {
  width: auto;
  margin: 5px 0 0;
  font-size: inherit;
}
table.knobs td,
table.knobs th {
  border: 0;
  padding: 1px 0;
  background: none;
  font-size: inherit;
}
table.knobs td {
  text-align: right;
  padding-right: 10px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  width: 1%;
  white-space: nowrap;
}
table.knobs th {
  text-align: left;
  font-weight: 400;
  color: inherit;
  position: static;
  text-transform: none;
  letter-spacing: normal;
}
table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
  margin-top: 10px;
}
th,
td {
  padding: 4px 7px;
  text-align: right;
  border-bottom: 1px solid var(--line);
}
th {
  color: var(--accent);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-size: 12px;
  position: sticky;
  top: 0;
  background: var(--panel);
}
td.note {
  text-align: left;
  font-family: var(--mono);
}
td.note.white {
  background: #e8e3da;
  color: #171716;
}
td.note.black {
  background: #0e0e0d;
  color: #cfc8bd;
}
td input {
  width: 78px;
  padding: 4px 7px;
  font: inherit;
  font-family: var(--mono);
  text-align: right;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #111110;
  color: var(--ink);
}
.scroll {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 12px;
}
.tag {
  display: inline-block;
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  margin-left: 8px;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 700;
}
.msg {
  padding: 14px 16px;
  border-radius: 12px;
  font-size: 14px;
  margin: 14px 0;
  white-space: pre-wrap;
}
.msg.ok {
  background: color-mix(in srgb,var(--ok) 12%,transparent);
}
.msg.bad {
  background: color-mix(in srgb,var(--bad) 12%,transparent);
}
.msg.warn {
  background: color-mix(in srgb,var(--warn) 12%,transparent);
}
.files div {
  font-size: 13px;
  padding: 4px 0;
  font-family: var(--mono);
}
/* A slot is two lines, not three columns: the name and its controls on the
   first, the file on the second, and the explanation under both at full
   width.  Squeezed into a 150px column that note wrapped to three lines of
   12px and was the hardest thing on the page to read. */
.slot {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 14px;
  align-items: center;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}
.slot:last-child {
  border-bottom: 0;
}
.slot .who {
  grid-column: 1;
  min-width: 0;
}
.slot .who b {
  display: block;
  font-size: 16px;
}
.slot .note {
  grid-column: 1/-1;
  font-size: 14px;
  color: var(--muted);
  line-height: 1.5;
  display: block;
}
.slot .what {
  grid-column: 1/-1;
  font-family: var(--mono);
  font-size: 15px;
  min-width: 0;
  /* A slot can carry a scale, a keyboard mapping and the anchor note at
     once, which is more than one line holds: they wrap instead of being
     clipped, and only the filename itself still ellipses. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
}
.slot .what .fname {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.slot .what.empty {
  font-family: inherit;
  color: var(--muted);
  font-style: italic;
}
.slot .ctl {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  gap: 6px;
}
/* Four knobs across, as they sit on the panel, each with what it can be set
   to stacked underneath it.  No indent: the four columns want the width the
   pad map gets, and the block is a figure of the hardware rather than a note
   hanging off the option above. */
.knobsel { margin: 14px 0 6px; }
.knobsel > .knobmap:first-child { margin-top: 0; }
.knobsel.hidden { display: none; }
.knobmap {
  /* Measured off the User's Guide's cover photograph, the same one the pad's
     five-to-two came from: the leftmost preset knob is 140px across where the
     pad beneath it is 178, so a knob is 0.79 of a pad and 104 against the
     pad's 132.  It was 64, which drew it at under two thirds of its size.

     The leftmost pair on purpose.  The four knobs measure 139, 156, 166 and
     182 left to right while the four pads hold at 177 to 179 - the knobs
     stand proud of the panel so they open up as they go off-axis, and the
     flat pads do not.  Taking a knob and a pad from the same corner of the
     frame cancels what is left of that. */
  --knob-w: 104px;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: start;
  gap: 16px;
  margin-top: 10px;
}
.knobfig {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* The knob, drawn, with its number over the face.  Both layers share one grid
   cell so the number centres on the circle without either being positioned. */
.knobface {
  display: grid;
  place-items: center;
  width: var(--knob-w);
  /* Centred over its column: on the panel the knob sits above the middle of
     what it controls, and left-aligning a 64px circle over a 157px stack
     read as though it belonged to whatever was left of it. */
  margin-inline: auto;
  height: var(--knob-w);
}
/* The pattern bank is knob 2's, but it is as wide as the whole block, so it
   sits under all four rather than inside one - which leaves nothing saying
   whose it is.  A heading does, in the voice the sequencer card's state
   headings already use.

   22 above it, which is what the sequencer card puts between one state and
   the next: this is a section below the knobs, not the last thing in the
   column, and at 0 the heading landed hard against the very button that
   opens it. */
/* A card inside the card: the bank is one knob's, so it reads as a thing set
   into the group rather than the next part of it.  The surface the page uses
   for anything set into a panel - the pad plates, the drop zone - is the
   page's own bg behind a hairline, and the shadow is the tier .group takes,
   which is already the "nested card" one. */
#patternBody {
  margin-top: 22px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 18px 20px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .032),
              0 1px 2px rgba(0, 0, 0, .10),
              0 10px 22px -16px rgba(0, 0, 0, .27);
}
.patwho {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--muted);
}
.patwho strong {
  color: var(--ink);
}
.knobface svg,
.knobface b {
  grid-area: 1 / 1;
}
.knobface svg {
  width: 100%;
  height: 100%;
  margin: 0;
}
.knobface b {
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
}
/* The options run down the column now rather than across the row, so the
   count no longer has to fit a shared width: knob 2's three and knob 3's one
   both simply stack. */
/* One control, divided, rather than a stack of separate buttons: the choices
   are exclusive, and four boxes with air between them read as four things
   that could each be on.  The gap closes, the outer edge and corner move to
   the container, and a hairline separates the rows.  overflow clips the
   chosen row's fill back to the corner it sits in, so the top and bottom of
   the control stay round. */
.knobmap .vpo.knobpick {
  grid-template-columns: minmax(0, 1fr);
  grid-auto-flow: row;
  grid-auto-columns: auto;
  gap: 0;
  margin-top: 0;
  max-width: none;
  /* Mixed from --ink rather than currentColor, which is what the checkout
     lists use: there the row's colour never moves, here it sweeps on hover,
     and a rule mixed from it would sweep with it. */
  border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 8px;
  overflow: hidden;
}
/* Divided and outlined the way the shipping and payment lists on the shop's
   checkout are: the hairline is mixed from the text colour rather than named,
   the rule sits on the bottom of each row and the last one has none, and the
   chosen row takes a radius one inside the container's, comes forward, and
   drops the rule under it - as does the row above it, so nothing crosses the
   edge of the selection on either side. */
.knobmap .knobpick button {
  position: relative;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 0;
}
.knobmap .knobpick button:last-child {
  border-bottom: 0;
}
/* The shared button hover paints every border accent, and the only border a
   row still has is the rule under it - so hovering drew a coloured line
   between two rows.  The rule holds, whatever the row's state.

   .vpo is in the selector to win on specificity rather than on position.
   Without it this ties with .vpo button:hover:not([aria-pressed=true]) at
   0-3-1, and that one is 400 lines further down, so it took the tie and the
   line came back pink under whichever row the pointer was on. */
.knobmap .vpo.knobpick button:hover {
  border-bottom-color: color-mix(in srgb, var(--ink) 20%, transparent);
}
/* The type carries the hover instead - but only on a row that is not the
   chosen one.  The chosen row is dark on accent, and sweeping its type to
   the accent puts it on its own background. */
.knobmap .knobpick button:hover:not([aria-pressed=true]) {
  animation: sweep-ink var(--sweep-pass) forwards;
}
/* After the hover rule, so a selection still clears the lines around it, and
   carrying .vpo for the same reason: the chosen row ties with the two
   [aria-pressed=true] rules below, both of which paint an accent border. */
.knobmap .vpo.knobpick button[aria-pressed=true] {
  z-index: 1;
  border-bottom-color: transparent;
  border-radius: 7px;
}
.knobmap .vpo.knobpick button[aria-pressed=true]:hover,
.knobmap .vpo.knobpick button:has(+ button[aria-pressed=true]),
.knobmap .vpo.knobpick button:hover:has(+ button[aria-pressed=true]) {
  border-bottom-color: transparent;
}
/* One row per pattern: a number, the steps, how many before it repeats. */
/* Patterns can be one line of steps or four, so the number, the length and
   the remove button line up with the FIRST line rather than floating to the
   middle of however many a pattern happens to have. */
.pat {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 0;
}
/* A long pattern is several lines of steps, and those lines sit 4px apart.
   Without more than that between one pattern and the next, twenty-two of them
   read as one field of squares rather than twenty-two patterns. */
.pat + .pat {
  border-top: 1px solid rgba(157, 154, 147, .2);
}
/* The number, the length and the remove button are all one step-row tall, so
   top-aligning the row puts all three on the centre line of the first row of
   steps whether the pattern runs to one line or four. */
.patnum {
  width: 22px;
  height: 22px;
  line-height: 22px;
  text-align: right;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}
/* Groups are as long as the pattern's own length divides evenly, so they are
   not all the same width and cannot be laid on fixed columns: they run along
   the line and wrap when the next one will not fit. */
.patgrid {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  flex: 1;
  min-width: 0;
}
/* Ungrouped, the steps are the row's own items and take the step spacing. */
.patgrid.ungrouped {
  gap: 4px 3px;
}
.patbar {
  display: flex;
  gap: 3px;
}
/* A rest is an empty step, not an absent one: it needs an edge you can see
   against the panel, or the pattern reads as ending wherever its last hit
   was.  Every fourth carries a brighter one, so the bar is countable. */
.patgrid .step {
  width: 20px;
  height: 22px;
  padding: 0;
  border-radius: 3px;
  border: 1px solid var(--muted);
  background: transparent;
  cursor: pointer;
}
.patgrid .step.beat { border-color: var(--ink); }
.patgrid .step.on { background: var(--accent); border-color: var(--accent); }
.patgrid .step:hover { border-color: var(--accent2); }
/* The field and its arrows read as one control. */
.patlenbox {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 22px;
}
.patlen {
  width: 34px;
  height: 22px;
  box-sizing: border-box;
  padding: 0 6px;
  border-radius: 8px;
  /* The same edge the steps carry - against the panel, --line is no edge. */
  border: 1px solid var(--muted);
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-size: 13px;
  text-align: center;
  appearance: textfield;
  -moz-appearance: textfield;
}
/* The browser's own arrows are drawn in the platform's colours, which on this
   background is no colour at all.  Ours replace them. */
.patlen::-webkit-outer-spin-button,
.patlen::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.patsteps {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.patstep {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 10px;
  padding: 0;
  border: 1px solid var(--muted);
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  line-height: 0;
}
.patstep svg {
  width: 9px;
  height: 9px;
  margin: 0;
}
.patstep:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.patctl {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.patctl button {
  padding: 8px 14px;
  font-size: 13px;
  text-transform: none;
  letter-spacing: .04em;
}
/* line-height alone left the cross sitting on the baseline near the bottom of
   the pill; centring it is the flex container's job. */
.pat .clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  box-sizing: border-box;
  padding: 0 9px;
  line-height: 0;
}
.pat .clear svg { width: 15px; height: 15px; margin: 0; }

/* A slot's keyboard mapping, and the way to take it off again. */
.kbmchip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel2);
  font-size: 12px;
  vertical-align: 1px;
}
.slot .what .kbmchip button {
  padding: 0 4px;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  line-height: 1;
  cursor: pointer;
}
.slot .what .kbmchip button:hover { background: none; }
.kbmchip button:hover { color: var(--accent2); }
/* A scale whose degree count is not twelve does nothing until it is mapped. */
.kbmneed {
  font-family: inherit;
  font-size: 11px;
  color: var(--accent2);
}
/* The mapping button says what it takes; the icon buttons beside it are
   square, so it sets its own padding and gets its line-height back. */
.slot .ctl button.kbmbtn {
  padding: 8px 14px;
  font-size: 13px;
  line-height: 1.1;
  text-transform: none;
  letter-spacing: .04em;
}
/* The mapping button reads as set once a mapping is on the slot. */
.slot .ctl button.mapped {
  color: var(--accent);
  border-color: var(--accent);
}
.slot .ctl button {
  padding: 8px 16px;
  line-height: 0;
}
/* The icons are SVG, so they size from the button rather than from a font,
   and svg{width:100%} elsewhere on this page must not reach them. */
.slot .ctl button svg {
  width: 17px;
  height: 17px;
  margin: 0;
  display: block;
}
.slot .ctl button.clear {
  color: var(--bad);
  border-color: color-mix(in srgb,var(--bad) 55%,transparent);
}
/* A touch heavier than the arrows: it is the destructive one. */
.slot .ctl button.clear svg path {
  stroke-width: 2.9;
}
.slot .ctl button.clear:hover:not(:disabled) {
  background: var(--bad);
  color: #171716;
  border-color: var(--bad);
}
.danger {
  border: 1px solid var(--bad);
  border-radius: 16px;
  padding: 28px;
  margin: 0 0 20px;
  background: color-mix(in srgb,var(--bad) 8%,var(--panel));
}
.danger h2 {
  color: var(--bad);
  font-size: 14px;
  letter-spacing: .16em;
  margin: 0 0 14px;
}
.danger p {
  margin: 0 0 10px;
  font-size: 16px;
}
.danger strong {
  color: var(--bad);
}
.danger p:last-child {
  margin-bottom: 0;
}
.winsetup {
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 20px 22px;
  margin: 0 0 14px;
  background: color-mix(in srgb,var(--accent) 8%,var(--panel));
}
.winsetup h3 {
  margin: 0 0 14px;
  font-size: 14px;
  letter-spacing: .16em;
  color: var(--accent);
  text-transform: uppercase;
}
.winsetup p {
  margin: 0 0 10px;
  font-size: 16px;
}
.winsetup ol {
  margin: 0 0 12px;
  padding-left: 22px;
  font-size: 16px;
  line-height: 1.9;
}
.winsetup p:last-child {
  margin-bottom: 0;
}
.prereq {
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 20px 22px;
  margin: 14px 0;
}
.prereq h3 {
  margin: 0 0 14px;
  font-size: 13px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
.prereq dl {
  margin: 0;
  font-size: 16px;
  line-height: 1.65;
}
.prereq dd {
  margin: 6px 0 0;
  color: var(--muted);
}
svg {
  width: 100%;
  height: 120px;
  margin-top: 10px;
  overflow: visible;
}
#build {
  width: 100%;
  height: 74px;
  box-sizing: border-box;
  margin-bottom: 12px;
}
#dlMac,
#dlWin {
  width: calc(50% - 6px);
  height: 62px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
/* The platform marks are drawn: U+F8FF is Apple private-use and renders
   nowhere else, and Unicode has no Windows logo at all. */
#dlMac .os,
#dlWin .os {
  width: 19px;
  height: 19px;
  flex: none;
  margin: 0;
  display: block;
}
/* Matched by ink, not by box.  The Apple mark occupies 14.9 units of its 24
   where the Windows one occupies 18, so at equal box size it reads smaller;
   22px brings its width to about the Windows mark's. */
#dlMac .os {
  width: 22px;
  height: 22px;
  position: relative;
  top: -1px;
}
/* Same mark in the Windows-only heading, sized to its type and sitting on the
   text baseline rather than on its own line. */
.winsetup h3 .os {
  width: 15px;
  height: 15px;
  margin: 0 4px 0 0;
  display: inline-block;
  vertical-align: -2px;
}
a {
  color: var(--accent);
}
strong {
  color: var(--accent);
}
/* --- volts per octave: two equal choices, not a squeezed pill ------------- */
.vpo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 10px;
  max-width: 430px;
}
.vpo button {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* flex-start places the boxes; this places the lines.  The label wraps at
     narrow widths, and a button's own stylesheet centres wrapped text. */
  text-align: left;
  gap: 4px;
  padding: 14px 20px;
  border-radius: 12px;
  /* --line is right for a divider but disappears as an outline: an unchosen
     option was a card you had to hunt for.  Muted at a fifth reads as an
     edge without competing with the accent on the chosen one. */
  border: 1px solid rgba(157, 154, 147, .22);
  background: transparent;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
  font-size: 19px;
  font-weight: 500;
  line-height: 1.1;
}
.vpo button .why {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--muted);
}
/* The generic button hover fills with the accent; without resetting the
   background here that rule and this one would put accent text on an accent
   fill, which is invisible. */
.vpo button:hover:not([aria-pressed=true]) {
  background: transparent;
  border-color: var(--accent2);
  color: var(--accent2);
  animation: sweep-edge var(--sweep-pass) forwards,
             sweep-ink var(--sweep-pass) forwards;
}
.vpo button:hover:not([aria-pressed=true]) .why {
  color: var(--accent2);
  opacity: .8;
  animation: sweep-ink var(--sweep-pass) forwards;
}
.vpo button[aria-pressed=true],
.vpo button[aria-pressed=true]:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #171716;
}
.vpo button[aria-pressed=true] .why,
.vpo button[aria-pressed=true]:hover .why {
  color: #171716;
  opacity: .7;
}
/* The volts-per-octave pair as one divided rectangle: the same control the
   knob rows are, laid across instead of down, so a choice between two reads
   as a choice rather than two cards that happen to sit together.  Full width
   because the row is the choice - 430 of a 780 column left it stranded. */
.vpo.rowpick {
  gap: 0;
  max-width: none;
  /* The field row is a flex, and a grid inside one shrink-wraps to its
     content: dropping the max-width alone left it at 483 of 676. */
  flex: 1;
  min-width: 0;
  /* Mixed from --ink rather than currentColor, as the knob rows are: here the
     colour sweeps on hover, and a rule mixed from it would sweep with it. */
  border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 8px;
  overflow: hidden;
}
.vpo.rowpick button {
  position: relative;
  border: 0;
  border-right: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 0;
}
.vpo.rowpick button:last-child {
  border-right: 0;
}
/* The shared button hover paints every border accent, and the only border a
   segment still has is the rule beside it - so hovering drew a coloured line
   down the middle.  The rule holds, whatever the segment's state. */
.vpo.rowpick button:hover {
  border-right-color: color-mix(in srgb, var(--ink) 20%, transparent);
}
/* sweep-edge animates border-color, and an animation outranks every static
   rule above - so pinning border-right-color was not enough on its own and
   the line came back the moment a segment was hovered.  The type carries the
   hover instead, as it does on the knob rows, and only on a segment that is
   not the chosen one: the chosen one is dark on accent, and sweeping its type
   to the accent would put it on its own background. */
.vpo.rowpick button:hover:not([aria-pressed=true]) {
  animation: sweep-ink var(--sweep-pass) forwards;
}
/* After the hover rule, so a selection still clears the line beside it. */
.vpo.rowpick button[aria-pressed=true] {
  z-index: 1;
  border-right-color: transparent;
  border-radius: 7px;
}
.vpo.rowpick button[aria-pressed=true]:hover,
.vpo.rowpick button:has(+ button[aria-pressed=true]),
.vpo.rowpick button:hover:has(+ button[aria-pressed=true]) {
  border-right-color: transparent;
}
/* A segment's corners have to move with its fill.  The button transition
   carries background over .15s but leaves border-radius to switch outright,
   so the moment a choice moved the outgoing segment lost its corners while
   its colour was still there - a square block of accent for 150ms, which is
   the flash.  Same list as the button rule with border-radius added: the
   shorthand replaces rather than extends, so the rest has to be restated. */
.vpo.rowpick button,
.knobmap .knobpick button {
  transition: background .15s, color .15s, border-color .15s,
              border-radius .15s, transform .08s;
}
/* --- the offsets laid out as the keyboard they describe -------------------- */
.kbdwrap {
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #111110;
  margin: 0px 0 20px 0;
}
.kbd {
  position: relative;
  height: 190px;
  margin: 14px 14px 16px;
}
.key {
  position: absolute;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 5px;
  padding-bottom: 9px;
  border-radius: 0 0 5px 5px;
}
.key.white {
  width: 48px;
  height: 190px;
  background: #e8e3da;
  color: #171716;
  border: 1px solid #b9b3a8;
  border-top: 0;
}
.key.black {
  width: 32px;
  height: 120px;
  background: #0d0d0c;
  color: #e8e3da;
  border: 1px solid #000;
  border-top: 0;
  z-index: 2;
}
.key .nm {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  opacity: .7;
}
/* The spinner arrows cost about a third of the width of a key, which is the
   difference between "-1.25" fitting and being clipped. */
.key input {
  width: 40px;
  padding: 3px 1px;
  font-family: var(--mono);
  font-size: 11px;
  text-align: center;
  border-radius: 3px;
  border: 1px solid rgba(0,0,0,.28);
  background: rgba(255,255,255,.85);
  color: #171716;
  appearance: textfield;
  -moz-appearance: textfield;
}
.key input::-webkit-outer-spin-button,
.key input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.key.black input {
  border-color: #000;
  background: #2a2926;
  color: #f2f0ec;
}
.key input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 0;
}
/* A non-zero offset is worth seeing at a glance across 65 keys. */
.key input.set {
  background: var(--accent);
  color: #171716;
  font-weight: 600;
}
.kbdhint {
  font-size: 11px;
  color: var(--muted);
  margin: 0;
  text-transform: uppercase;
  letter-spacing: .16em;
  font-weight: 700;
  display: inline-block;
}
.section-intro {
  margin: 0 0 12px;
}
.section-note {
  margin: 0 0 10px;
}
.section-note:last-of-type {
    margin-bottom: 20px;
}
/* Two notes under one toggle are a single thought carried over a line break,
   not two paragraphs, so they keep the paragraph's own rhythm instead of the
   10 that separates the standalone notes below the box. */
.opt-toggle p.section-note:has(+ p.section-note) {
  margin-bottom: 0;
}
.field-label {
  font-weight: 600;
}
.field-row {
  margin-top: 8px;
}
/* A labelled row picker is one field.  The picker brings its own 10px top
   margin from .vpo, which on top of the row's 8px pushed the label away from
   its control while the next field's label sat flush against this one's
   picker - the gap was on the wrong side.  The field owns the rhythm now:
   8px label to control, 20px between fields, matching the toggle below. */
.field .vpo.rowpick {
  margin-top: 0;
}
.field + .field {
  margin-top: 20px;
}
.field-note {
  margin: 8px 0 0;
}
/* label.opt already sets gap:14px at the same weight a lone class would
   carry, so these have to match its specificity to win. */
.opt.opt-toggle {
  gap: 14px;
  margin-bottom: 10px;
}
.opt.opt-toggle.lead {
  margin-top: 20px;
}
.row-bottom {
  margin-bottom: 10px;
}
.kbd-heading {
  margin-bottom: 0;
}
/* Toggled from app.js by class rather than by writing element.style. */
.hidden {
  display: none;
}
.howto-details {
  margin: 0 0 12px;
}
.howto {
  line-height: 1.7;
  color: var(--muted);
  margin-top: 8px;
}
.howto p {
  margin: 0 0 8px;
}
.howto p:last-child {
  margin: 0;
}
/* Only the numbered openers, not every strong in the block: step 1 ends with
   an accent strong that has to stay accent. */
.howto .step {
  color: var(--ink);
}
/* Numbered like the knob table: accent, bold, right-aligned in their own
   column against left-aligned text.  A counter rather than a real marker
   because these items carry nested blocks, which a grid or table cell would
   pull onto rows of their own. */
/* One card per step, on the same surface as the option groups in step 2.
   The number is a counter rather than a real marker because these items
   carry nested blocks, which a grid or a table cell would pull onto rows of
   their own; absolute keeps it out of the text flow so long steps wrap under
   themselves rather than under the digit. */
.steps {
  margin: 12px 0 0;
  padding-left: 0;
  line-height: 1.9;
  list-style: none;
  counter-reset: step;
}
.steps > li {
  counter-increment: step;
  position: relative;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 22px 14px 50px;
  margin: 0 0 14px;
}
.steps > li:last-child {
  margin-bottom: 0;
}
/* Accent fill, dark digit - the same pairing the checked checkbox uses.
   Top is set rather than left to the static position: the circle has a fixed
   height and has to centre on the first line, not sit on its baseline. */
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 14px;
  top: 17px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  color: #171716;
  font-size: 13px;
  font-weight: 700;
  line-height: 24px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/* The list is indented so a platform mark can hang outside it.  Inline, the
   mark pushed the first line right and left the second starting further out,
   so a two-line symptom read as ragged against its neighbours. */
.faq {
  line-height: 1.6;
  margin: 6px 0 0;
  padding-left: 22px;
}
/* The symptom is the thing being scanned for, so it takes the page's label
   treatment - tracked uppercase in ink - and the answer drops to muted.  A
   literal keeps its own case: uppercasing "no device present" would misquote
   what the flasher actually prints. */
.faq dt {
  margin-top: 20px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink);
}
.faq dt:first-of-type {
  margin-top: 10px;
}
/* .mono is a chip elsewhere - inline-block with 4px of vertical padding -
   and an atomic box baselines on its own last line, which pushed the symptom
   it sits in from 21px to 64px.  Inside a symptom it stays a chip but a flat
   one, short enough to leave the line box alone. */
.faq dt .mono {
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  padding: 2px 6px;
}
.faq dt {
  position: relative;
}
.faq dt .os {
  position: absolute;
  left: -22px;
  top: 4px;
  width: 12px;
  height: 12px;
  margin: 0;
  display: block;
}
/* The whole symptom is already the emphasis, so strong inside it stays ink
   rather than pulling accent into a line that is meant to read as one. */
.faq dt strong {
  color: inherit;
}
.faq dd {
  margin: 5px 0 0;
  color: var(--muted);
}
/* Carried over from triglavmodular.hu.  The original markup set these inline
   through --wp--preset--* variables that do not exist here, so the margins and
   padding they named resolved to nothing; these are the values the browser
   actually computed. */
.triglav-title {
  margin: 300px 0 0;
  padding: 0;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #fff;
  text-align: center;
}
/* --- motion ---------------------------------------------------------------
   Only on things the reader just did: a box opening, a result arriving, a
   control being pressed.  Nothing animates on load, because the page would
   then be moving while somebody is trying to read it.  Everything here is
   turned off wholesale under prefers-reduced-motion at the end of the file. */

/* A result has to be noticed without stealing the scroll position, so it
   arrives from just below rather than being switched on. */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.msg {
  animation: rise-in .22s ease-out both;
}

/* Panels expand instantly; their contents can still arrive rather than
   appear, which reads as the box opening instead of the page jumping. */
details[open] > *:not(summary) {
  animation: rise-in .2s ease-out both;
}
summary {
  transition: color .15s;
  list-style: none;
}
summary::-webkit-details-marker {
  display: none;
}
summary:hover {
  color: var(--ink);
}
/* A > that turns to point down when the panel opens.  The summary is a flex
   row so the mark centres on the text rather than sitting on its baseline -
   the point of a > is at its own vertical middle, so centring the box is what
   puts the point on the centreline.  The turn is a transform, which takes no
   space, so the heading beside it cannot move. */
summary {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Drawn rather than typed.  A ">" glyph is not centred in its own em box -
   measured, its ink sat 5.5px above a baseline whose box centre was
   elsewhere, so the mark read low against the caps beside it.  This path
   puts the point at the exact middle of a square viewBox, so centring the
   box centres the point, and rotating about that centre leaves it put.
   Masked rather than drawn as an image so it inherits the summary colour
   and follows it on hover. */
summary::before {
  content: "";
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  background: currentColor;
  -webkit-mask: var(--chevron) center / contain no-repeat;
  mask: var(--chevron) center / contain no-repeat;
  transform-origin: 50% 50%;
  transition: transform .18s ease;
}
details[open] > summary::before {
  transform: rotate(90deg);
}

/* The whole row is the click target, so hovering the text has to answer -
   but on the box, not behind the words.  A tint under a paragraph reads as
   a selection rather than as a control. */
label.opt:hover input[type=checkbox]:not(:checked) {
  border-color: var(--accent2);
  animation: sweep-edge var(--sweep-pass) forwards;
}
label.opt:hover input[type=checkbox]:checked {
  background: color-mix(in srgb, var(--accent) 82%, #fff);
  border-color: color-mix(in srgb, var(--accent) 82%, #fff);
}

/* The tick is drawn, so it can grow into place instead of blinking on. */
input[type=checkbox]::after {
  transform-origin: center;
  transition: transform .13s cubic-bezier(.34, 1.4, .64, 1), opacity .1s;
}
input[type=checkbox]:not(:checked)::after {
  opacity: 0;
}
input[type=checkbox]:checked::after {
  opacity: 1;
}

/* Every button already carries a .15s colour transition; this is the press,
   which is the half that tells you the click landed. */
button:active:not(:disabled) {
  transform: translateY(1px);
}
button {
  transition: background .15s, color .15s, border-color .15s, transform .08s;
}

/* Dropping a file is a drag, so the target grows very slightly to meet it. */
.drop {
  transition: border-color .15s, color .15s, background .15s, transform .15s;
}
.drop.over {
  transform: scale(1.006);
}

/* The plot's frame.  The svg stretches its drawing to the column; the labels
   sit on top in ordinary HTML so the type is never scaled with it.  The curve
   already keeps out of y 16-104, which under a 120px-tall plot is exactly the
   band these occupy. */
.plotwrap {
  position: relative;
  /* The 10px the diagram rule further up puts above every svg, moved onto
     the frame so the labels and the drawing share an origin. */
  margin-top: 10px;
}
.plotwrap svg {
  display: block;
  margin-top: 0;
}
/* 11px type at 2px in from each edge spans y 2-13 and 107-118, which is why
   the curve keeps to 16-104. */
.plotlabel {
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: 11px;
  line-height: 1;
  color: var(--muted);
}
.plotlabel.lo {
  top: auto;
  bottom: 2px;
}

/* A cell that has been given an offset lights up; worth easing so a row of
   them does not flicker while values are typed. */
.key input {
  transition: background .18s, color .18s, border-color .15s;
}

/* The accent moves from Build to Download when an image exists.  Both ends
   of that already transition through the button rule above. */

/* --- the same pass, wherever the accent is what arrives -------------------
   The nav sweep is the house hover now, but only for the half of the page it
   describes: a control that is grey or white until you point at it, and
   accent once you do.  Those all take the pink on the way - the unticked
   checkbox, the drop zone, the offset cells, and a V/oct button whole, ring
   and type and label together.

   Not the ones already wearing the accent.  A button is a yellow ring with
   yellow type before the pointer arrives anywhere near it, and a ticked box
   is a yellow tile; sending those through the pink is not a greeting, it is
   a colour change, and the two read nothing alike.  Their hovers are left
   exactly as they were.

   Two legs, and no new machinery for either.  The first is the transition
   each control already carries: the hover rule names the pink, so what used
   to ease into the accent now eases into the pink over its own .15s.  The
   second is these keyframes, delayed by exactly that .15s so they pick the
   colour up where the transition drops it and carry it home.  Real
   properties rather than a custom one, so the interpolation is native and
   none of this needs @property to be smooth; forwards holds the accent for
   as long as the pointer stays.

   Letting go removes the animation and the same transition carries the
   colour back, so the return is a plain fade rather than the pass in
   reverse.  Deliberate - the pass is the greeting.

   Spatial on the nav, where a word is wide enough for a gradient to cross
   it; in time everywhere else, because a 2px edge has nowhere to cross. */
@keyframes sweep-edge {
  from { border-color: var(--accent2); }
  to { border-color: var(--accent); }
}
@keyframes sweep-ink {
  from { color: var(--accent2); }
  to { color: var(--accent); }
}
/* Two of them had no transition on the property their hover paints, so the
   first leg had nothing to run on and the pink would have snapped in. */
td input {
  transition: border-color .15s;
}
.vpo button .why {
  transition: color .15s;
}

/* --- hover, including the states that are already on -----------------------
   The generic button hover paints an accent fill, which is what a chosen
   control is already wearing: pressed V/oct, a primary button, a ticked box
   all sat dead under the pointer.  Each gets a lighter accent instead, so
   "on" answers the mouse the same way "off" does. */
.vpo button[aria-pressed=true]:hover,
input[type=checkbox]:checked:hover {
  background: color-mix(in srgb, var(--accent) 82%, #fff);
  border-color: color-mix(in srgb, var(--accent) 82%, #fff);
}
a:hover {
  color: color-mix(in srgb, var(--accent) 82%, #fff);
}
/* The number fields are click targets too, and only showed themselves on
   focus. */
.key input:hover,
td input:hover {
  border-color: var(--accent2);
  animation: sweep-edge var(--sweep-pass) forwards;
}
/* Already destructive on hover; this is the off state answering as well. */
.slot .ctl button:hover:not(:disabled) {
  border-color: var(--accent);
}
.slot .ctl button.clear:hover:not(:disabled) {
  border-color: var(--bad);
}

/* The call to action carries the shine from the add-to-cart button on
   triglavmodular.hu, toned down: the highlight is .16 where that one is .4,
   the lift is 5% where it is 15%, and the saturate() is dropped - at 10% it
   drains the accent it is supposed to warm.  The sweep is a pseudo-element
   crossing under overflow:hidden, so nothing about it changes the layout. */
button.primary:not(:disabled) {
  position: relative;
  overflow: hidden;
  transition: background .15s, color .15s, border-color .15s,
              transform .08s, filter .45s ease-in-out;
}
button.primary:not(:disabled)::before {
  content: "";
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  pointer-events: none;
  background: linear-gradient(120deg, transparent, rgba(255, 255, 255, .16), transparent);
  transition: left .7s ease-in-out;
}
button.primary:not(:disabled):hover::before {
  left: 100%;
}
button.primary:not(:disabled):hover {
  filter: brightness(1.05);
}

/* The calibration and tuning panels are shown by removing .hidden, so there
   is no state to transition between - they get the same arrival the results
   and the disclosure panels use. */
#calBody:not(.hidden),
#tuningsBody:not(.hidden) {
  animation: rise-in .24s ease-out both;
}

/* Build is the one thing on the page you cannot skip, so its sweep is the
   one you are meant to notice: brighter than the download buttons and slow
   enough to read as a pass across the button rather than a flicker.  No
   glow - the light doing the work is the light crossing it. */
#build:not(:disabled)::before {
  background: linear-gradient(120deg, transparent, rgba(255, 255, 255, .5), transparent);
  transition: left 1.1s ease-in-out;
}

/* The nav takes the same sweep, painted on the letters rather than behind
   them - at 12px there is no room for a highlight to cross a link, but the
   type itself can carry the light.  The gradient is three times the width of
   the link and slides one full width on hover, so the window over it runs
   muted, then accent yellow at the middle of the travel, then the pink of the
   mark, which is where it settles.  Leaving runs the same wipe backwards.
   Shorter than the buttons' sweep: a word is read in one glance, and .7s on
   something that small reads as lag rather than as movement.
   @supports because background-clip is what makes the transparent colour
   safe - without it the row would go blank, so anything lacking it keeps the
   plain yellow hover above.  Keyboard focus takes the sweep too; the row had
   no focus style of its own before. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .sitenav nav a {
    color: transparent;
    background-image: var(--sweep);
    background-size: 300% 100%;
    background-position: 0 0;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    transition: background-position var(--sweep-slide);
  }
  .sitenav nav a:hover,
  .sitenav nav a:focus-visible,
  .sitenav nav a[aria-current=page] {
    color: transparent;
    background-position: 100% 0;
  }
}

/* --- depth ----------------------------------------------------------------
   Two tiers, matching the two the padding already follows.  Each card gets a
   hairline of light along its top edge, as though the light were above the
   page, and a shadow in two parts: a tight one that sits the card on the
   surface, and a wide soft one that lifts it off.  A nested card gets less
   of both, so it reads as sitting on its parent rather than beside it.
   Shadows paint outside the border box and change no geometry. */
section,
.danger {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045),
              0 1px 2px rgba(0, 0, 0, .14),
              0 16px 36px -20px rgba(0, 0, 0, .36);
}
.group,
.winsetup,
.prereq,
.steps > li {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .032),
              0 1px 2px rgba(0, 0, 0, .10),
              0 10px 22px -16px rgba(0, 0, 0, .27);
}
/* The drop zone is a dashed outline rather than a panel, so it takes the edge
   light alone - a shadow under a dashed border reads as a mistake. */
.drop {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .03);
}
/* The keyboard is recessed rather than raised: it is a well the keys sit in,
   so its light is at the bottom and its shadow is inside. */
.kbdwrap {
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, .30),
              inset 0 -1px 0 rgba(255, 255, 255, .03);
}
/* The changelog panel floats over the page rather than sitting in it, so it
   carries more than a card does: the wide soft one reads as distance from the
   surface behind, over the lift the cards take.  The light along the bottom
   edge is the keyboard's, the page's mark for a well with more inside it than
   shows.  One declaration, because a second box-shadow would replace this
   rather than add to it. */
.chlog-body {
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .03),
              0 1px 42px rgba(0, 0, 0, .4),
              0 16px 36px -20px rgba(0, 0, 0, .36);
}

/* Narrow screens.  Last in the sheet on purpose: these share specificity
   with the rules they override, so order is what decides. */
/* One measured from the window, one from the column: the mark and the menu
   close on each other as the window narrows and would touch at about 888px.
   Before they do, the mark rejoins the row and the two go back to being laid
   out by the same box. */
@media (max-width:900px) {
  .brand {
    position: static;
    transform: none;
  }
}
/* On a narrow screen the absolutely-positioned banana sits on top of the
   title, so it comes back into the flow: the header stacks, the banana takes
   its own line above the heading, and it shrinks to suit the width. */
@media (max-width:640px) {
  /* Leading is set for a measure, and the measure changes here.  1.62 holds
     a line together across an 820px column, where a description is one line
     or two; on a phone the same description wraps to four, and at that width
     the gaps stop reading as breathing room and start reading as space
     between separate things.  Everything that carries its own looser leading
     - the numbered steps, the Zadig list, the how-to - comes down with it,
     because those were opened up against the same wide column. */
  body {
    line-height: 1.45;
  }
  .steps,
  .winsetup ol {
    line-height: 1.6;
  }
  .howto {
    line-height: 1.5;
  }
  .prereq dl,
  .faq {
    line-height: 1.45;
  }
  .slot .note {
    line-height: 1.4;
  }
  .hdr {
    display: block;
    margin: 0 0 12px;
  }
  .banana {
    position: static;
    width: clamp(64px,22vw,96px);
    margin: 0 0 10px;
    transform: rotate(-8deg) scale(1);
  }
  .hdr:hover .banana {
    transform: rotate(0deg) scale(1);
  }
  /* Two 49% buttons leave each about 150px wide on a phone, which is not
     enough for their labels.  Full width, stacked. */
  #build,
  #dlMac,
  #dlWin {
    width: 100%;
    height: 60px;
  }
  .row {
    gap: 10px;
  }
  /* Four knobs across leave about 70px a column at this width, which is
     narrower than any of their labels.  Two across, as the pads do. */
  .knobmap {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 18px 14px;
  }
  /* Sixteen steps beside the number, the length and the remove button leave
     about 100px for the steps here.  The steps take a line of their own, one
     bar of eight to a line, and the two controls sit at the right of the line
     above them. */
  .pat {
    flex-wrap: wrap;
  }
  .patgrid {
    order: 3;
    flex: 0 0 100%;
  }
  .patlen {
    margin-left: auto;
  }

  /* Three nested paddings between the screen edge and a knob's label - the
     page's 20, the card's 28, the group's 22 - which is 140 of a 375 phone
     spent on margin.  It cost the labels their room: RANDOMNESS wants 83 and
     had 67, so it ran out of its own button.  Each one comes down far enough
     to give the column back about 52. */
  .wrap {
    padding-left: 14px;
    padding-right: 14px;
  }
  section {
    padding: 20px 16px;
  }
  .group {
    padding: 16px 14px 6px;
  }
}

/* One switch for everything above.  The banana keeps its own rule further up
   because it also has to stop at its resting angle rather than snap upright. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* The step sequencer's pad map.  A pad means one of two things depending on
   whether pad 4 is down, which a paragraph can only say and a picture can
   show: the pads are drawn as they sit on the panel - a rounded plate with
   its LED at the bottom left - once per state, so the two readings can be
   compared down the column.  Labels are HTML, never text inside a drawing,
   so nothing is ever scaled out of proportion. */
/* The pad map explains a feature that is not in the build unless the
   sequencer is, so it appears with the option rather than beside it.  Done
   in CSS off the checkbox itself: there is no state to keep in step, and it
   is right on first paint rather than after a script has run. */
.padmap {
  /* The pad's own width, shared by the plate that draws one and the row that
     places four: as two numbers they drifted, and the drift showed up as the
     row's slack piling behind the last pad. */
  --pad-w: 132px;
  display: none;
  /* 14 above, as .knobsel takes: both are a figure a tick reveals under its
     own option, and this one sat flush against the line above it. */
  margin: 14px 0 18px;
}
.group:has(#sequencer:checked) .padmap {
  display: block;
}
.padmap-state + .padmap-state {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}
.padmap-state h4 {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--muted);
}
/* The control's name is one thing, so it does not break across a line:
   "PAD 4" split after PAD when the heading grew long enough to wrap.  A rule
   rather than a non-breaking space in the text, so it holds for every name
   set this way and leaves no invisible character in the copy. */
.padmap-state h4 strong {
  color: var(--ink);
  white-space: nowrap;
}
/* Columns the width of a pad, not a quarter of the row: at 1fr each column
   was wider than the pad it held, the pad sat at its left, and all four
   slacks collected into one gap after the fourth.  space-between spends that
   slack between the pads instead, so the row starts and ends flush.  minmax
   lets them shrink when the row cannot hold four at full width. */
.padrow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, var(--pad-w)));
  justify-content: space-between;
  gap: 16px;
}
.padfig {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* The plate: the proportions of the preset pad on the panel, with the LED
   sitting in its bottom left corner. */
.padfig .plate {
  position: relative;
  display: grid;
  place-items: center;
  /* The pad on the panel is wide and short - about five to two - not the
     square-ish plate this first drew.  Measured off the User's Guide's own
     photograph of the module. */
  aspect-ratio: 5 / 2;
  width: 100%;
  max-width: var(--pad-w);
  border-radius: 7px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink);
}
.padfig .plate b {
  font-size: 18px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* The LED has to READ as a component, not as a smudge: unlit it is still a
   ring with a face, which is what makes the plate legible as a pad rather
   than as a plain rounded box. */
/* Top left, where it sits on the module: the prominent dark circle in the
   corner of every preset pad in the manual's photograph. */
.padfig .led {
  position: absolute;
  /* Equal from both edges, so the LED sits on the corner's diagonal rather
     than a pixel off it.  8, the tighter of the two it had: at 9 the gap
     starts to read against a plate only 7px round at the corner. */
  left: 8px;
  top: 8px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #4f4b43;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .55),
              0 0 0 1px rgba(255, 255, 255, .06);
}
.padfig .fn {
  font-weight: 700;
  color: var(--ink);
  /* 1.62 is the page's reading line-height, which is loose for a two-word
     label: at that ratio a wrapped label reads as two separate lines rather
     than one name. */
  line-height: 1.25;
  /* The mark hangs into the margin so a wrapped line starts under the first
     line's text rather than under the mark.  Measured from the mark itself,
     so it stays right if the mark's size or gap changes. */
  padding-left: calc(var(--tr-w) + var(--tr-gap));
  text-indent: calc(-1 * (var(--tr-w) + var(--tr-gap)));
}
/* The hang is there to clear a mark, so a label without one keeps neither
   half of it.  Both labels that carry no mark were a word long while it
   stayed invisible; the first one to wrap indented its second line 18 to
   clear a mark that was never in front of it. */
.padfig .fn:not(:has(.tr)) {
  padding-left: 0;
  text-indent: 0;
}
/* The transport marks, on the functions they name.  Drawn rather than typed:
   as glyphs, ● ▶ ■ come from whichever font has them and ✕ is a letter in
   some, so none of the four would match the others.  Inline and on the
   baseline, the way the Windows mark sits in a heading; the explicit box and
   margin keep the diagram rule further down this sheet off them. */
.padmap {
  --tr-w: 15px;
  --tr-gap: 3px;
}
.padmap .tr {
  width: var(--tr-w);
  height: var(--tr-w);
  margin: 0 var(--tr-gap) 0 0;
  display: inline-block;
  /* Set by eye, and it lands between the two figures: the ink centres 5.0px
     above the baseline where the x-height's middle is 4.0 and the capitals'
     is 5.68.  Nearer the x-height, which is what a mark beside a mixed-case
     word wants, but not sitting all the way down on it. */
  vertical-align: -2.5px;
}
/* Coloured by what each one does, not decoratively: record takes the page's
   own red, which is also what the LED lights, play the ok green, and stop
   the accent.  Clear stays ink: red would have read as the warning red this
   page uses for what cannot be undone, and clearing a sequence you are in
   the middle of recording is not that.  The class sits on a span around the
   mark AND
   the word it names, so the two are never a different colour from each
   other; the svg draws in currentColor and follows. */
/* The triangles and the arrow read smaller and sit higher than a circle or a
   square drawn to the same box - open shapes carry their weight off-centre,
   and a point has no edge to line up.  A slightly bigger box and a little
   further down, by eye rather than by the arithmetic the others follow. */
.padmap .tr-optical {
  --tr-w: 16px;
  vertical-align: -3px;
}
.padmap .tr-rec {
  color: var(--bad);
}
.padmap .tr-play {
  color: var(--ok);
}
.padmap .tr-stop {
  color: var(--accent);
}
.padmap .tr-clear {
  color: var(--ink);
}
/* On a state heading the mark sits against 13px uppercase, so it comes down
   a size.  3px, not 7: the heading's own .1em of tracking is added after the
   mark as well as after every letter, so the gap reads wider than it is set. */
.padmap-state h4 .tr {
  width: 11px;
  height: 11px;
  margin-right: 3px;
  /* The one that stays on the cap height, because this heading is
     text-transform: uppercase - there is no lowercase body to centre on, so
     the caps are what the mark has to level with.  11px of mark against
     9.23px of cap at 13px. */
  vertical-align: -0.88px;
}
.padfig .sub {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.45;
}
/* The pad being held is the one the whole gesture hangs off, so it is lit,
   and its LED blinks the way the firmware blinks it. */
.padfig.is-held .plate {
  border-color: var(--accent);
}
.padfig .led.blink {
  background: var(--bad);
  /* Mixed from --bad rather than written out, so a red that moves in the
     theme takes its own glow with it. */
  box-shadow: 0 0 8px color-mix(in srgb, var(--bad) 55%, transparent);
  animation: padblink 1.2s steps(1, end) infinite;
}
@keyframes padblink {
  50% {
    background: #4f4b43;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .55),
                0 0 0 1px rgba(255, 255, 255, .06);
  }
}
@media (prefers-reduced-motion: reduce) {
  .padfig .led.blink {
    animation: none;
  }
}
/* A pad that does nothing in this state is dimmed rather than dropped, so
   the four pads stay in the same places in both rows and the eye can run
   straight down the column.  Dimmed PART BY PART rather than with opacity on
   the whole figure: pad 1 does nothing to the take while recording, but its
   LED is blinking the whole time, and a parent's opacity is not something a
   child can climb back out of. */
.padfig.is-off .plate {
  border-color: rgba(46, 46, 43, .55);
}
.padfig.is-off .plate b,
.padfig.is-off .fn,
.padfig.is-off .sub {
  opacity: .4;
}
.padfig.is-off .fn {
  color: var(--muted);
}
/* Narrow: four across becomes two, which keeps the plate big enough to read
   the number and the LED without squinting. */
@media (max-width: 640px) {
  .padrow {
    grid-template-columns: repeat(2, minmax(0, var(--pad-w)));
    gap: 18px 14px;
  }
}

/* The strip, drawn as it is on the panel: a long plate - about nine to one -
   with the red sawtooth running through it.  It sits under the pads in the
   recording state because it is not a pad and does not belong in their row,
   and its two halves are labelled where they actually are. */
.stripfig {
  /* 14, as the pads take from their heading: the strip is the same state
     continuing, not a new one, and 20 read as a break between them. */
  margin-top: 14px;
  /* The plate's border, named so the captions below can be inset by the same
     amount.  It is drawn in --line, which is 8 from the card behind it where
     the fill is 31 - so the eye reads the strip as starting where the fill
     does, one pixel inside the box, and captions aligned to the box sat a
     pixel outside the strip. */
  --strip-edge: 1px;
}
.stripfig .plate {
  position: relative;
  display: block;
  width: 100%;
  /* Measured off the module: 598 by 55, which is eleven to one, not the nine
     this first drew.  The pad beside it is 122 by 49 - almost exactly five
     to two - so the two are drawn to the same rule. */
  aspect-ratio: 11 / 1;
  /* A floor only for the very narrowest screens: any higher and it stops
     being eleven to one, which is the proportion that makes it read as the
     strip rather than as a bar. */
  min-height: 26px;
  border-radius: 7px;
  border: var(--strip-edge) solid var(--line);
  background: var(--bg);
  overflow: hidden;
}
.stripfig .plate svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* Sizing the chevrons off the panel put them a shade high and short once the
   viewBox was stretched to the plate; this is the correction, in user units
   so it holds at every width.  It lets the arms run past the top edge, which
   is what the module shows - they meet the edge rather than stopping at it. */
.stripfig .plate svg g {
  transform: translateY(-10.5px) scale(1.86, 1.2);
}
/* The half the touch lands in is what decides rest against tie, so the
   division is drawn rather than described. */
.stripfig .half {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 6px;
  /* Centred on the middle, not starting at it: at six wide the difference
     is three pixels of a strip whose whole point is which half you touched. */
  left: 50%;
  transform: translateX(-50%);
  background: var(--line);
}
.stripfig .halves {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 8px;
  padding-inline: var(--strip-edge);
}
.stripfig .halves > span {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.45;
}
.stripfig .halves b {
  color: var(--ink);
  font-weight: 700;
  display: block;
}
.stripfig .halves > span + span {
  text-align: right;
}
