/* Self-contained: system fonts only, no external requests. */
:root {
  --bg: #fff;
  --paper: #fff;
  --surface: #f5f6f7;     /* quiet plates: code, prompt, zoom panels */
  --fill: #eef0f2;        /* resting fill of buttons and segmented tracks */
  --fill-hover: #e3e6ea;
  --ink: #1d1d1f;
  --ink-soft: #3a3a3f;    /* long secondary text that still has to read as content, e.g. prompts */
  --muted: #5d6167;       /* 6.2:1 on white, 5.4:1 on --fill, 5.0:1 on --fill-hover */
  --line: #d9dce1;
  --line-soft: #e9ebee;
  /* #39c before. #1f74a3 reached 4.5:1 on white but only just 4.5:1 on --fill, so the accent is one
     step darker again: 5.9:1 on white, 5.3:1 on --accent-soft, 5.2:1 on --fill, 4.7:1 on hover. */
  --accent: #1b6a96;
  --accent-strong: #185e85; /* hover on filled accent buttons */
  --accent-soft: #edf4f9;
  --accent-mid: #9cc7e6;    /* decoration only: bars, ticks, hover hairlines */
  --accent-ring: #548fb0;   /* the lightest accent that still reaches 3:1 on white and on the tint */
  --video-ar: 1.7333;
  --stage-h: min(56vh, 540px);
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* One content column for every section. It widens with the window so large monitors keep
     big videos, but stops well short of the edges so a laptop still gets real margins. The gutter
     grows steeply from a phone's 20px, so a tablet from 766px up still has the 720px column the
     training drawings need at their smallest (a flat 5vw clipped their right edge below 800px).
     From a laptop up the column's maximum width binds first, so the layout there is unchanged. */
  --gutter: clamp(20px, 7.8vw - 37px, 80px);
  --col-max: clamp(1120px, 72vw + 172px, 1760px);
  --col: min(100% - 2 * var(--gutter), var(--col-max));
  --measure: 42em;        /* ~78 characters at any text size: the longest line a lede or note should run */
  /* The diagrams' drawn size before, up to a 1920 monitor; wider only inflates their labels. Past
     1920 the column keeps growing to 1760px, and a 1120px drawing on its left edge left a third of
     it empty, so the cap then rises gently to 1320px at 2560. The height cap still binds first on
     a short window, so the drawing always fits the screen. */
  --figure-max: clamp(1120px, 31.25vw + 520px, 1320px);
  /* The hairline between sections does the separating, so the air on either side of it only has
     to let the eye reset: about 43px at 1280 and 47px at 1440. It stops growing at 48px, because
     on a big monitor more air only pushes each diagram further from its heading. */
  --space-section: clamp(32px, 2.4vw + 12px, 48px);
  /* The gap between blocks inside a section (heading to content, content to its note). */
  --space-block: clamp(18px, 1.5vw + 2px, 26px);

  /* The type scale is in rem, so it follows the fluid root size set on html below. */
  --fs-display: clamp(2rem, 1rem + 2.2vw, 3.25rem);
  --fs-h2: clamp(1.5rem, 1rem + 1.1vw, 2.125rem);
  --fs-h3: 1.0625rem;
  --fs-lede: 1.0625rem;
  --fs-body: 1rem;
  --fs-small: .9375rem;
  --fs-ui: .875rem;
  --fs-micro: .8125rem;
  /* The smallest size still carries content (shortcut names, chips), so it never drops below 12px
     even where the fluid root would take it to 11.3px. */
  --fs-tiny: max(12px, .75rem);

  --radius-s: 8px;
  --radius-m: 12px;
  --control-h: 34px;
  --shadow-raised: 0 1px 2px rgba(16, 24, 40, .12), 0 0 0 .5px rgba(16, 24, 40, .06);
  --shadow-media: 0 1px 2px rgba(16, 24, 40, .06), 0 12px 28px -14px rgba(16, 24, 40, .35);
}

* { box-sizing: border-box; }

/* The page used to sit at a flat 135%, which is right on a 27-inch monitor and feels zoomed on a
   laptop. The root now grows with the window instead: about 15.5px at 1280, 16px at 1440 and
   17.6px at 1920, so a laptop gets laptop density while a big monitor keeps today's. The terms are
   in rem, not px, so a reader who set a larger default text size in the browser still gets it. */
html { font-size: clamp(.9375rem, .7rem + .333vw, 1.1875rem); }

body {
  min-height: 100vh;
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  counter-reset: section;
}

body.has-zoom { overflow: hidden; }

button, input, select { font: inherit; color: inherit; }

[hidden] { display: none !important; }

.page {
  width: var(--col);
  margin: 0 auto;
  padding: var(--space-section) 0 0;
}

/* The comparison opens like every later section: a hairline across the column, then air. */
.viewer {
  margin-top: var(--space-section);
  padding-top: var(--space-section);
  border-top: 1px solid var(--line);
}

/* ---------- Header ---------- */

.page-head {
  margin-bottom: 22px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}

/* The paper title heads the page; each section below carries its own h2. A heavy weight with
   tight tracking reads as a display face, which the system font becomes at this size. */
.page-head h1 {
  max-width: 24em;
  margin: 0 auto;
  text-align: center;
  font-size: var(--fs-display);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.06;
  text-wrap: balance;
}

.lede {
  max-width: var(--measure);
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-lede);
  line-height: 1.55;
  text-wrap: pretty;
}

.lede b { color: var(--ink); font-weight: 600; }
.lede b.is-ours { color: var(--accent); }

/* ---------- Tabs + navigation bar ---------- */

/* The two filters (length, methods) sit together on the left and the sample arrows are pinned to
   the right. Spreading all three evenly left the method chips floating in the middle of a wide
   bar, lined up with nothing. */
.viewer-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 12px 28px;
  margin-bottom: var(--space-block);
}

.viewer-bar .nav { margin-left: auto; }

/* Length tabs and every segmented control share one look: a quiet track with the chosen option
   lifted onto a white thumb. The chosen label turns accent, so the brand colour marks the state
   without a slab of it. White on the pale track is only 1.1:1, so the thumb also carries a
   hairline in --accent-ring (3.5:1 on the thumb, 3.1:1 on the track), the same ring the section
   bar gives its current link: the selection never rests on the shadow alone. */
.length-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: var(--fill);
}

.length-tabs button {
  display: inline-flex;
  height: calc(var(--control-h) - 6px);
  align-items: center;
  gap: 7px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-ui);
  font-weight: 500;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
}

.length-tabs button:hover,
.length-tabs button:focus-visible {
  color: var(--ink);
}

.length-tabs button[aria-pressed="true"] {
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--accent-ring), var(--shadow-raised);
  color: var(--accent);
  font-weight: 600;
}

/* ---------- Method picker ---------- */

.methods {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.methods-label {
  color: var(--muted);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1;
}

.methods-options {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* A hairline outline while off, a soft fill while on: the checkbox stays for clarity, the fill
   carries the state at a glance. */
.method-chip {
  display: inline-flex;
  height: var(--control-h);
  align-items: center;
  gap: 7px;
  padding: 0 13px 0 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.method-chip input {
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.method-chip:hover { border-color: var(--accent-mid); color: var(--ink); }

/* .is-on is kept in step with the checkbox by the script, so the chip does not depend on :has(). */
.method-chip.is-on {
  border-color: transparent;
  background: var(--fill);
  color: var(--ink);
}

.method-chip.is-on.is-ours {
  background: var(--accent-soft);
  color: var(--accent);
}

.method-chip input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.nav {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.nav-status {
  min-width: 64px;
  color: var(--muted);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  text-align: center;
  white-space: nowrap;
}

.nav-button {
  display: grid;
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--fill);
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}

.nav-button:hover,
.nav-button:focus-visible {
  background: var(--accent-soft);
  color: var(--accent);
}

/* ---------- Sample card ---------- */

.sample-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px 24px;
  padding: 0 0 18px;
}

.sample-heading {
  min-width: 0;
  flex: 1 1 420px;
}

.sample-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.sample-title {
  font-size: 1.125rem;
  font-weight: 650;
  letter-spacing: -.01em;
  line-height: 1.2;
}

.chip {
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--muted);
  font-size: var(--fs-tiny);
  font-weight: 500;
  line-height: 1.4;
}

.chip:empty { display: none; }

/* The prompt keeps to the page's measure like the lede above it; at the old 54em its two lines ran
   past 100 characters on a laptop, the only text on the page that broke the measure. */
.prompt-wrap {
  max-width: var(--measure);
  margin-top: 8px;
}

/* The prompts run to several hundred characters, so the card shows the opening lines. */
.sample-prompt {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: var(--ink-soft);
  font-style: italic;
  line-height: 1.55;
}

.prompt-wrap.is-open .sample-prompt {
  display: block;
  overflow: visible;
}

.sample-prompt::before { content: "\201C"; }
.sample-prompt::after { content: "\201D"; }

.prompt-toggle {
  min-height: 24px;
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: 500;
  cursor: pointer;
}

.prompt-toggle:hover { text-decoration: underline; }

.sample-actions,
.zoom-actions {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/* Every action button on the page: sentence case on a soft fill, the same height as the
   segmented controls beside it. The minimum width keeps Pause and Play from nudging the row. */
.pill {
  display: inline-flex;
  min-width: 4.6rem;
  height: var(--control-h);
  align-items: center;
  justify-content: center;
  padding: 0 15px;
  border: 0;
  border-radius: 999px;
  background: var(--fill);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}

.pill:hover,
.pill:focus-visible {
  background: var(--fill-hover);
}

/* A stopped player's Play is the one thing to press next, so it is the only filled accent button:
   white on the accent is 5.9:1, and nothing else on the page looks like it. */
.pill.is-paused {
  background: var(--accent);
  color: #fff;
}

.pill.is-paused:hover,
.pill.is-paused:focus-visible { background: var(--accent-strong); }

/* Zooming in is what the comparison card is for, so it leads the row in accent. An outline rather
   than a fill keeps it a step below Play, which fills in when the videos are paused. */
.sample-actions [data-action="zoom"] {
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--accent);
  color: var(--accent);
  font-weight: 600;
}

.sample-actions [data-action="zoom"]:hover,
.sample-actions [data-action="zoom"]:focus-visible { background: var(--accent-soft); }

/* ---------- Transport (time, seek, speed) ---------- */

/* An open row rather than a box: the seek bar is the frame the eye needs. */
.transport {
  display: grid;
  grid-template-columns: auto minmax(140px, 1fr) auto;
  align-items: center;
  gap: 16px;
  margin: 0 0 18px;
}

.time {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  white-space: nowrap;
}

.time-input {
  box-sizing: content-box;
  width: 6.2ch;
  height: calc(var(--control-h) - 8px);
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  background: var(--fill);
  color: var(--ink);
  font: inherit;
  text-align: right;
}

.time-input:hover { border-color: var(--line); }

.time-input:focus {
  border-color: var(--accent);
  background: var(--paper);
  outline: 2px solid var(--accent-mid);
}

.time-input.is-invalid {
  border-color: #a33e2d;
  outline-color: #f3ddd5;
}

/* A 24px-tall box with the track centred in it: the slider looks the same, but a finger or a
   shaky pointer gets a full-size target instead of the 16px strip the browser draws. */
.seek {
  display: block;
  width: 100%;
  height: 24px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.segmented {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}

.segmented-options {
  display: inline-flex;
  height: var(--control-h);
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: var(--fill);
}

.segmented-options button {
  min-width: 0;
  height: 100%;
  padding: 0 11px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
}

.segmented-options button:hover,
.segmented-options button:focus-visible {
  color: var(--ink);
}

.segmented-options button[aria-pressed="true"] {
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--accent-ring), var(--shadow-raised);
  color: var(--accent);
  font-weight: 600;
}

.magnify [data-magnify-value] { min-width: 4.6ch; }

/* ---------- Video ---------- */

.video-grid {
  display: grid;
  grid-template-columns: repeat(var(--tiles, 3), minmax(0, 1fr));
  gap: clamp(12px, 1.2vw, 20px);
}

.tile {
  min-width: 0;
  margin: 0;
}

.tile figcaption {
  padding: 0 1px 8px;
  font-size: var(--fs-ui);
  font-weight: 600;
  line-height: 1.2;
}

.tile.is-ours figcaption { color: var(--accent); }

/* A single tile would otherwise grow as tall as the window is wide. Rounded corners and a soft
   shadow set the picture off the page without a drawn border. */
.video-frame {
  max-width: calc(var(--stage-h) * var(--video-ar));
  margin: 0 auto;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-m);
  background: #111;
  box-shadow: var(--shadow-media);
  aspect-ratio: var(--video-ar);
}

.video-frame video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  cursor: pointer;
}

.loading-bar {
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  opacity: 0;
  background: linear-gradient(90deg, transparent, var(--accent-mid), transparent) 0 0 / 40% 100% no-repeat;
  pointer-events: none;
}

.video-frame.is-loading .loading-bar {
  animation: loading-slide 1.1s linear infinite;
  opacity: 1;
  transition: opacity .2s ease .25s;
}

@keyframes loading-slide {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}

.video-error {
  margin: 10px 0 0;
  color: #a33e2d;
  font-size: var(--fs-small);
}

.sample-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 24px;
  margin-top: var(--space-block);
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.jump {
  display: inline-flex;
  min-width: 0;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: var(--fs-micro);
  font-weight: 500;
}

/* Without min-width the option text (a whole prompt) sets a floor that max-width cannot lower, and
   without overflow the text that the box cuts off still widens the page's scrollable area. The
   native chrome is swapped for the soft fill, with the chevron drawn back in. */
.jump select {
  min-width: 0;
  max-width: min(460px, 70vw);
  height: var(--control-h);
  overflow: hidden;
  padding: 0 32px 0 12px;
  border: 0;
  border-radius: 999px;
  background: var(--fill) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235d6167' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 13px center;
  color: var(--ink);
  text-overflow: ellipsis;
  white-space: nowrap;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

.jump select:hover { background-color: var(--fill-hover); }

.jump select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Reference, not content: one small muted line under the videos. The two switches keep their
   weight because they change what the videos show. */
.shortcuts {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-tiny);
}

/* The line stays small, but each switch is a 24px-tall target, so a finger can hit it. */
.shortcut-toggle {
  display: inline-flex;
  min-height: 24px;
  align-items: center;
  gap: 6px;
  margin-right: 14px;
  color: var(--ink-soft);
  font-weight: 500;
  cursor: pointer;
}

.shortcut-toggle input {
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* Switched-off shortcuts dim, but only to 4.7:1 on white, so the list still reads as text if
   someone wants it: it is information about the keys, not an inactive control. */
.shortcuts.is-off .shortcut-list { opacity: .88; }

/* A key name is set apart by weight alone: a row of drawn keycaps shouts louder than the videos. */
kbd {
  padding: 0 .15em;
  color: var(--ink-soft);
  font-family: inherit;
  font-weight: 600;
}

/* ---------- Zoom dialog ---------- */

/* Browsers without <dialog> would otherwise render the zoom view inline under the card. */
dialog:not([open]) { display: none; }

.zoom {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 0;
  background: var(--paper);
  color: var(--ink);
}

.zoom::backdrop { background: rgba(17, 17, 17, .78); }

.zoom-shell {
  display: flex;
  width: 100%;
  height: 100%;
  flex-direction: column;
  padding: 10px clamp(10px, 1vw, 16px) 10px;
}

.zoom-shell:focus { outline: none; }

.zoom-toolbar {
  display: grid;
  flex: 0 0 auto;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 20px;
  padding-bottom: 10px;
}

.zoom-toolbar .transport {
  margin: 0;
}

.zoom-grid {
  display: grid;
  flex: 1 1 auto;
  min-height: 0;
  align-content: center;
  justify-content: center;
  gap: 8px;
  overflow: hidden;
}

.zoom-grid { grid-template-columns: repeat(var(--cols, 2), var(--panel-w, 40vw)); }

.zoom-grid .zoom-prompt { max-height: var(--prompt-h, 110px); }

/* Either the prompt takes a spare cell of the panel grid, or a band across the bottom. */
.zoom-grid[data-prompt="band"] .zoom-prompt { grid-column: 1 / -1; }

/* The layout script budgets 10px across and 29px down for padding, border and caption, so the
   border stays in the box model and only its colour goes quiet. */
.panel {
  min-width: 0;
  margin: 0;
  padding: 3px 4px 4px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: var(--surface);
}

.panel.is-ours {
  border-color: var(--accent-mid);
  background: var(--accent-soft);
}

.panel figcaption {
  height: 20px;
  padding: 0 4px;
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 20px;
}

.panel.is-ours figcaption { color: var(--accent); }

.panel-stage {
  position: relative;
  overflow: hidden;
  border-radius: 7px;
  aspect-ratio: var(--panel-ar, 1.7333);
  background: #111;
  cursor: pointer;
  touch-action: none;
}

.zoom-grid.is-magnified .panel-stage { cursor: grab; }
.zoom-grid.is-dragging .panel-stage { cursor: grabbing; }

.panel-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.zoom-prompt {
  min-width: 0;
  min-height: 0;
  padding: 14px 18px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: 10px;
  background: var(--surface);
}

.zoom-prompt:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.zoom-prompt-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.zoom-prompt-head span {
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: 600;
}

.zoom-prompt-head small {
  color: var(--muted);
  font-size: var(--fs-tiny);
  white-space: nowrap;
}

.zoom-prompt-text {
  margin: 8px 0 0;
  color: var(--ink-soft);
  font-size: 1.0625rem;
  font-style: italic;
  line-height: 1.55;
}

.zoom-hint {
  margin: 12px 0 0;
  color: var(--muted);
  font-size: var(--fs-micro);
  line-height: 1.5;
}

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

/* ---------- Keyboard focus ---------- */

button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The focus ring sits just outside the option, in the track's padding, so it never looks like
   the selection's hairline drawn heavier. A focused option that is not chosen also takes the hover
   fill and ink text rather than the white thumb and accent, so a keyboard user can always tell
   the value they are on from the value that is set. */
.length-tabs button:focus-visible,
.segmented-options button:focus-visible {
  outline-offset: 1px;
}

.length-tabs button:not([aria-pressed="true"]):focus-visible,
.segmented-options button:not([aria-pressed="true"]):focus-visible {
  background: var(--fill-hover);
  color: var(--ink);
}

/* ---------- Narrow screens ---------- */

@media (max-width: 1100px) {
  .zoom-toolbar { grid-template-columns: auto minmax(0, 1fr); }
  .zoom-toolbar .transport { grid-column: 1 / -1; grid-row: 2; }
}

@media (max-width: 860px) {
  /* Three tiles side by side stop being readable long before the page gets this narrow. */
  .video-grid { grid-template-columns: minmax(0, 1fr); }
  .viewer-bar { gap: 10px 12px; }
}

@media (max-width: 720px) {
  .transport { grid-template-columns: auto minmax(0, 1fr); gap: 10px 14px; }
  .transport .segmented { grid-column: 1 / -1; justify-content: flex-start; }
  .length-tabs button { padding: 0 13px; }
  .sample-actions { justify-content: flex-start; }
  /* The zoom toolbar keeps time, seek and speed on one row so the panels keep their height. */
  .zoom-toolbar .transport { grid-template-columns: auto minmax(0, 1fr) auto; }
  .zoom-toolbar .transport .segmented { grid-column: auto; }
}

/* On a phone the toolbar stacks into five rows, and every pixel it takes comes out of the panels
   below it, because the layout script gives the grid whatever height is left. The controls step
   down to 30px (still well above the 24px minimum target) and the rows sit closer, so the toolbar
   ends up shorter than base's and each panel keeps its width. */
@media (max-width: 520px) {
  .zoom-toolbar { --control-h: 30px; grid-template-columns: 1fr; gap: 6px 12px; padding-bottom: 6px; }
  .zoom-toolbar .transport { grid-row: auto; grid-template-columns: auto minmax(0, 1fr); row-gap: 6px; }
  .zoom-toolbar .transport .segmented { grid-column: 1 / -1; }
  .zoom-actions { justify-content: flex-start; row-gap: 6px; }
}

@media (max-height: 520px) {
  .zoom-shell { padding-top: 4px; padding-bottom: 4px; }
  .zoom-toolbar { gap: 4px 10px; padding-bottom: 4px; }
}

@media (forced-colors: active) {
  /* The pressed and paused rules above outrank this plain one on specificity, and with the
     system's repaint switched off they would paint the thumb white or the Play pill dark accent
     behind HighlightText. Each state is therefore restated here at the same specificity. */
  [aria-pressed="true"],
  .pill.is-paused,
  .pill.is-paused:hover,
  .pill.is-paused:focus-visible,
  .length-tabs button[aria-pressed="true"],
  .segmented-options button[aria-pressed="true"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
    box-shadow: none;
  }

  /* The soft fills that mark buttons vanish in forced colours, so give them their outlines back. */
  .pill,
  .nav-button,
  .length-tabs,
  .segmented-options,
  .method-chip,
  .jump select,
  .time-input { border: 1px solid ButtonText; }

  /* Outside the thumb, the focus ring of a chosen option would land on the track, which the system
     paints Canvas, and HighlightText on Canvas is close to 1:1 in several contrast themes. The
     ring therefore moves back inside the Highlight thumb, where HighlightText is always legible. */
  .length-tabs button[aria-pressed="true"]:focus-visible,
  .segmented-options button[aria-pressed="true"]:focus-visible {
    outline-color: HighlightText;
    outline-offset: -3px;
  }

  .pill.is-paused:focus-visible { outline-color: CanvasText; }

  /* The drawn chevron is a fixed grey picture the system cannot recolour, so the select gets its
     native arrow back. */
  .jump select {
    appearance: auto;
    -webkit-appearance: auto;
    padding-right: 12px;
    background-image: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .video-frame.is-loading .loading-bar { animation: none; }
}

/* ---------- Sections ---------- */

.mechanism-inner {
  width: var(--col);
  margin: var(--space-section) auto 0;
  padding-top: var(--space-section);
  border-top: 1px solid var(--line);
}

/* page.css closes the page with a hairline above this padding. A section and a half of air under
   the rule lets it read as the page's end rather than as a cut: with only a sliver below it, the
   last screen looked as if the document had been truncated. */
.mechanism:last-of-type .mechanism-inner { padding-bottom: calc(var(--space-section) * 1.5); }

.mechanism-head { margin-bottom: var(--space-block); }

.mechanism-head h2 {
  margin: 0;
  font-size: var(--fs-h2);
  font-weight: 650;
  letter-spacing: -.022em;
  line-height: 1.12;
  text-wrap: balance;
}

.mechanism-head .lede { margin-top: 12px; }

/* The five content sections are numbered, so the long page reads as a sequence. The number is
   decoration, so the alternative text after the slash keeps it out of the heading's name. */
.viewer,
.mechanism:not(#bibtex) { counter-increment: section; }

.viewer > .mechanism-head::before,
.mechanism:not(#bibtex) .mechanism-head::before {
  content: counter(section, decimal-leading-zero);
  content: counter(section, decimal-leading-zero) / "";
  display: block;
  margin-bottom: 8px;
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .04em;
}

.mech-stage {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* The training parts each scroll on their own, so their shared stage must not clip. */
[data-training] .mech-stage { overflow-x: visible; }

/* The panel is laid out for print at 9 cm wide; below this the 4.8 pt labels stop being legible,
   so the diagram keeps its size and the stage scrolls instead of shrinking it further. */
.mech-svg {
  display: block;
  width: 100%;
  min-width: 680px;
  height: auto;
}

/* The drawings stop at their old size on the column's left edge, in line with the heading above
   and the control bar below (centred, they drifted away from both on a wide column). They never
   grow taller than the window minus the section bar and the control rail under them, so a laptop
   sees a whole step with its buttons and caption. The rail budget covers the bar's top margin, a
   two-line caption, a training part's title above its drawing and some air, so the whole part
   fits one screen. A capped height letterboxes the drawing inside its box instead of distorting it.
   The cap has a 480px floor: on a landscape phone, a short window or a laptop at 200% zoom, fitting
   the screen would shrink the labels to 5-8px, and scrolling past a legible diagram is better
   than squinting at a whole one. 480px keeps them near the size a portrait phone shows. */
.mech-svg,
.train-svg {
  --rail-h: 140px;
  --svg-cap: max(480px, calc(100vh - var(--toc-h, 52px) - var(--rail-h)));
  max-width: min(var(--figure-max), var(--svg-cap) * var(--svg-ar, 99));
  max-height: var(--svg-cap);
  margin-inline: 0 auto;
}

/* A height cap alone letterboxes the drawing in the middle of a full-width box, so a tall part
   started up to 107px right of the title and buttons it belongs to. Each drawing's width is
   therefore capped at its capped height times its own proportions (the viewBox, rounded down so
   the height cap never letterboxes it by a sliver), and the box shrinks to the drawing from the
   left edge. A drawing not listed here keeps the letterbox, which still fits the screen. */
.mech-svg[viewBox="0 0 900 470"] { --svg-ar: 1.914; }
.train-svg[viewBox="0 0 900 432"] { --svg-ar: 2.083; }
.train-svg[viewBox="0 0 900 604"] { --svg-ar: 1.49; }
.train-svg[viewBox="0 0 900 530"] { --svg-ar: 1.698; }

.mech-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 22px;
  margin: 16px 0 0;
}

.mech-actions {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.mech-step {
  min-width: 52px;
  color: var(--muted);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  text-align: center;
}

.mech-caption {
  /* The scripts write plain text here, so a flex box can centre one line against the buttons
     while the reserved height keeps a two-line step from shifting the page. The measure keeps a
     long takeaway from running the full width of the column beside the buttons. */
  flex: 1 1 320px;
  max-width: var(--measure);
  min-height: 2.9em;
  display: flex;
  align-items: center;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.45;
}

/* On a phone the four buttons fill the first row, so the step counter used to drop onto a row of
   its own, centred in its box. The buttons' wrapper steps aside here so the counter and the
   caption share the second row, starting at the left edge. The caption keeps its reserved height
   even while it is empty: it is a live region, and hiding it would drop the first step's
   announcement and jump the page by a line when the first caption arrives. */
@media (max-width: 720px) {
  .mech-bar { gap: 8px; }
  .mech-actions { display: contents; }
  .mech-step { min-width: 0; margin-right: 6px; text-align: left; }
  .mech-caption { flex-basis: 200px; }
}

/* ---------- Mechanism diagram ---------- */

/* Everything the animation reveals starts hidden and fades in; the step function only ever
   adds or removes .on, so stepping backwards plays the same transitions in reverse. */
.mech-svg text { font-family: var(--sans); }
.mech-svg .mini { font-size: 15.5px; }
.mech-svg .tny { font-size: 20.3px; }
.mech-svg .lbl { font-size: 22px; }
.mech-svg .rowlbl { font-size: 23.8px; font-weight: 700; }
.mech-svg .bold { font-weight: 700; }

.mech-svg .fade {
  opacity: 0;
  transition: opacity .45s ease;
}

.mech-svg .fade.on { opacity: 1; }

.mech-svg .cell rect { transition: stroke .35s ease, stroke-width .35s ease; }
.mech-svg .cell .clean { opacity: 0; transition: opacity .45s ease; }
.mech-svg .cell .clean.on { opacity: 1; }

.mech-svg .anchor-stack .card { transition-delay: calc(var(--i) * .18s); }

.mech-svg .ribbon {
  opacity: 0;
  transition: opacity .35s ease;
}

.mech-svg .ribbon.on { opacity: 1; }

/* The line extends, then the head lands on the end of it. */
.mech-svg .draw .line {
  stroke-dashoffset: 1;
  stroke-dasharray: 1;
  transition: stroke-dashoffset .4s ease;
}

.mech-svg .draw.on .line { stroke-dashoffset: 0; }
.mech-svg .draw .line.is-dashed { stroke-dashoffset: 0; opacity: 0; transition: opacity .4s ease; }
.mech-svg .draw.on .line.is-dashed { opacity: 1; }

.mech-svg .draw .head {
  opacity: 0;
  transition: opacity .2s ease .32s;
}

.mech-svg .draw.on .head { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .mech-svg .fade,
  .mech-svg .ribbon,
  .mech-svg .cell rect,
  .mech-svg .cell .clean,
  .mech-svg .draw .line,
  .mech-svg .draw .head { transition: none; }
}

/* ---------- Training section ---------- */

.train-svg {
  display: block;
  width: 100%;
  min-width: 720px;
  height: auto;
}

.train-svg text { font-family: var(--sans); }
.train-svg .tny { font-size: 18px; }
.train-svg .lab { font-size: 19.8px; }
.train-svg .sm { font-size: 16.5px; }
.train-svg .h { font-size: 21.6px; font-weight: 700; }

.train-svg .fade { opacity: 0; transition: opacity .45s ease; }
.train-svg .fade.on { opacity: 1; }

.train-svg .frame-edge {
  stroke: #94a3b8;
  stroke-width: 1.3;
  transition: stroke .35s ease;
}

.train-svg .block.is-anchor .frame-edge { stroke: #B45309; stroke-width: 1.8; }
.train-svg .block.is-dense .frame-edge { stroke: #2563B8; stroke-width: 1.8; }

/* A frame that time rebasing drops fades most of the way out and stays as a ghost. */
.train-svg .frame { opacity: 1; transition: opacity .45s ease; }
.train-svg .frame.is-dropped { opacity: .14; }

.train-svg .chip-body {
  fill: #FEF3C7;
  stroke: #B45309;
  stroke-width: 1.8;
  transition: fill .4s ease, stroke .4s ease;
}

.train-svg .chip[data-role="dense"] .chip-body { fill: #E0F2FE; stroke: #2563B8; }

.train-svg .chip-noise {
  opacity: 0;
  stroke: none;
  transition: opacity .4s ease;
}

.train-svg .chip.is-noisy .chip-noise { opacity: 1; }

.train-svg .draw .line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset .4s ease;
}

.train-svg .draw.on .line { stroke-dashoffset: 0; }
.train-svg .draw .line.is-dashed { stroke-dashoffset: 0; opacity: 0; transition: opacity .4s ease; }
.train-svg .draw.on .line.is-dashed { opacity: 1; }
.train-svg .draw .head { opacity: 0; transition: opacity .2s ease .32s; }
.train-svg .draw.on .head { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .train-svg .fade,
  .train-svg .frame,
  .train-svg .chip-body,
  .train-svg .chip-noise,
  .train-svg .draw .line,
  .train-svg .draw .head { transition: none; }
}

/* The three parts stay stacked and visible; the walkthrough fills them in as it runs. The rule
   between them is a shade lighter than the section rule and has less air around it, so the parts
   read as steps of one section rather than three more sections. */
.train-part + .train-part {
  margin-top: calc(var(--space-section) * .75);
  padding-top: var(--space-block);
  border-top: 1px solid var(--line-soft);
}

.train-part-title {
  margin: 0 0 12px;
  font-size: var(--fs-h3);
  font-weight: 600;
  letter-spacing: -.005em;
}

.train-part-stage {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* Two drawings carry a wide empty band in their own frame: part 2 below its loss box (40 of 604
   units) and part 3 above its planner row (39 of 530). Neither band is ever drawn into, so the
   neighbour moves over it and the title, drawing and buttons keep the rhythm of the other parts.
   The overlap is on the stage, not the drawing, so the scroller never gains a vertical overflow.
   The drawings never render below 0.8 scale (720px wide), where each band is still 31px, so the
   trims stay clear of the content at every size. Without :has() the gaps simply stay. */
.train-part-stage:has(> .train-svg[viewBox="0 0 900 604"]) { margin-bottom: -24px; }
.train-part-stage:has(> .train-svg[viewBox="0 0 900 530"]) { margin-top: -28px; }

.train-svg .mask-cell { transition: fill .3s ease; }
.train-svg .mask-cell.is-on { fill: #FBDF93; }
.train-svg .mask-cell.is-on.is-dense { fill: #A8CFF0; }
/* An attended cell is speckled when the key it reads is a noised latent. */
.train-svg .mask-cell.is-on.is-noisy { fill: url(#tr-mask-anchor-noisy); }
.train-svg .mask-cell.is-on.is-dense.is-noisy { fill: url(#tr-mask-dense-noisy); }
.train-svg .bold { font-weight: 700; }

/* Each training part carries its own controls, right under its own diagram. */
.train-bar { margin-top: 12px; }

/* On a phone every drawing is wider than the column and scrolls sideways, which nothing showed:
   half the diagram was simply off the edge. The stage now fades out on its right edge, and each
   drawing carries the fade's width as end padding (its minimum width grows to match, so the
   drawing itself keeps its legible size), so scrolled to the end it comes fully clear of the fade.
   The training parts' shared wrapper is left alone, because it also holds their buttons. */
@media (max-width: 760px) {
  [data-mech-stage],
  .train-part-stage {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }

  .mech-svg { min-width: 704px; padding-inline-end: 24px; }
  .train-svg { min-width: 744px; padding-inline-end: 24px; }
}

/* With reduced motion the parts open on their last step and never autoplay, but the script never
   marks their Play as paused either, so it stayed a plain grey pill while every other stopped
   player's Play is filled. Here nothing plays unless the reader asks, so the button takes the
   filled look for good (it then reads Pause in the same fill while a requested replay runs). */
@media (prefers-reduced-motion: reduce) {
  .train-bar [data-act="play"] { background: var(--accent); color: #fff; }
  .train-bar [data-act="play"]:hover,
  .train-bar [data-act="play"]:focus-visible { background: var(--accent-strong); }
}

/* ---------- Latency explorer ---------- */

.lat-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
  margin-bottom: var(--space-block);
}

.lat-controls .segmented-options button:disabled {
  color: #aeb3ba;
  cursor: not-allowed;
}

/* The two columns span the whole content column, so the chart lines up with the heading. */
.lat-chart {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-block) clamp(40px, 5vw, 88px);
}

@media (max-width: 900px) {
  .lat-chart { grid-template-columns: minmax(0, 1fr); }
}

.lat-block-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: var(--fs-micro);
}

.lat-block-head strong {
  color: var(--ink);
  font-size: var(--fs-h3);
  font-weight: 650;
  letter-spacing: -.01em;
}

.lat-row {
  display: grid;
  grid-template-columns: 9.4em minmax(0, 1fr) 6.6em 3.6em;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  font-size: var(--fs-micro);
}

/* The names are the rows' labels, so they wrap rather than being cut to an ellipsis: at normal
   spacing they fit the column on one line, and a reader who widens letter or word spacing gets a
   second line instead of "Ours (CP pla…". The column keeps its fixed width, so the bars still line
   up from row to row, and the row's centring keeps the bar level with a two-line name. */
.lat-name {
  color: var(--muted);
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.lat-row.is-main .lat-name { color: var(--accent); font-weight: 600; }

.lat-track {
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface);
}

.lat-bar {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: #bfc6ce;
  transition: width .35s ease;
}

.lat-row.is-ours .lat-bar { background: var(--accent-mid); }
.lat-row.is-main .lat-bar { background: var(--accent); }

.lat-value {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* The spread is part of the result, so it keeps the page's 12px floor rather than shrinking with
   the row to 10.5px; the value column above is widened to hold it. */
.lat-value small { color: var(--muted); font-size: max(12px, .84em); }

.lat-ratio {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.lat-row.is-main .lat-ratio { color: var(--accent); font-weight: 600; }

/* On a phone the name and the two figures leave the bar a sliver between them (5px at 320), too
   short to compare against the rows above and below. The bar drops to a full-width line of its
   own under its row instead, so every bar is measured against the same, whole column. */
@media (max-width: 420px) {
  .lat-row { grid-template-columns: minmax(0, 1fr) 6.6em 3.6em; row-gap: 3px; padding: 3px 0 5px; }
  .lat-track { grid-column: 1 / -1; grid-row: 2; }
}

/* The speed-up under each block is the answer the chart exists for, so it sits on a tinted pill
   of its own rather than trailing as another line of text. */
.lat-speed {
  display: inline-block;
  margin: 10px 0 0;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1.4;
}

.lat-note {
  max-width: var(--measure);
  margin: var(--space-block) 0 0;
  color: var(--muted);
  font-size: var(--fs-micro);
  /* A capped measure makes a lone last word likelier, and a one-word line reads as a mistake. */
  text-wrap: pretty;
}

/* These follow the bar rules above so they win on source order. Forced colours would flatten the
   bars' background to the page colour and leave a chart of bare numbers, and a reader who asked
   for less motion should not watch them slide on every switch. */
@media (forced-colors: active) {
  .lat-bar,
  .lat-row.is-ours .lat-bar { forced-color-adjust: none; background: CanvasText; }
  .lat-row.is-main .lat-bar { background: Highlight; }
  /* The tint that makes the speed-up a pill is dropped, so an outline keeps it one grouped figure. */
  .lat-speed { border: 1px solid CanvasText; }
}

@media (prefers-reduced-motion: reduce) {
  .lat-bar { transition: none; }
}

/* ---------- Anchor frame markers ---------- */

.seek-wrap {
  position: relative;
  display: block;
  width: 100%;
}

/* The thumb centre travels inset by half its width, so the ticks are inset to match. */
.anchor-ticks {
  position: absolute;
  inset: auto 7px -1px;
  height: 5px;
  pointer-events: none;
}

.anchor-ticks i {
  position: absolute;
  top: 0;
  width: 2px;
  height: 5px;
  margin-left: -1px;
  border-radius: 1px;
  background: var(--accent);
  opacity: .75;
}

/* The ticks are drawn only as a background, which forced colours would flatten to Canvas, and the
   anchor frames would vanish from the track. */
@media (forced-colors: active) {
  .anchor-ticks i {
    forced-color-adjust: none;
    background: CanvasText;
    opacity: 1;
  }
}

.tile figcaption::after {
  content: "";
  transition: none;
}

/* The chip rides in the caption row, never over the frame itself. */
body.show-anchors .video-grid.is-anchor .tile.is-ours figcaption::after {
  content: "anchor latent";
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-tiny);
  font-weight: 500;
  vertical-align: 1px;
}

/* The second encoding sits after a divider in the same picker. */
.methods-sep {
  width: 1px;
  height: 18px;
  margin: 0 4px;
  background: var(--line);
}

.method-chip.is-alt span { font-weight: 500; }
.method-chip.is-alt.is-on { border-color: var(--accent-mid); border-style: dashed; }

/* The measure is in em, so at this smaller size it would end well short of the lede above; the
   extra tenth brings the two right edges back together and keeps the note to two lines. The
   instruction in bold stays on one line wherever it fits, so it never reads as two fragments. */
.caveat {
  max-width: calc(var(--measure) * 1.1);
  text-wrap: pretty;
  margin: 14px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-micro);
  line-height: 1.55;
}

.caveat b { color: var(--ink); font-weight: 600; }

@media (min-width: 480px) {
  .caveat b { white-space: nowrap; }
}

/* ---------- Issues section ---------- */

.issue-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 28px clamp(16px, 1.8vw, 28px);
}

.issue { min-width: 0; margin: 0; }

.issue h3 {
  margin: 0 0 10px;
  font-size: var(--fs-body);
  font-weight: 600;
  letter-spacing: -.005em;
  line-height: 1.3;
}

.issue .video-frame { max-width: none; }

.issue figcaption {
  margin-top: 14px;
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.55;
}

.issue-bar { margin: var(--space-block) 0 0; }

@media (max-width: 860px) {
  .issue-grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }
}
