/* One stylesheet for the whole site. No framework: the pages are simple and a
   utility library would outweigh the markup it styles. */
:root { /* palette comes from the host, see below */ }
/* Dark, reachable three ways.
 *
 * It used to be only `prefers-color-scheme`, and that is why a tool page came
 * out black while the site around it was light: the site stamps the reader's
 * own choice onto <html data-mode>, and this stylesheet could not see it. So
 * somebody who had chosen light, on a machine set to dark, got a light site
 * and black tools.
 *
 * Three selectors, in the order that makes an explicit choice win:
 *   [data-mode="dark"]  the reader asked for dark
 *   prefers-color-scheme, but NOT when they asked for light
 *   — so the system preference is the default and never an override.
 */



* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
.wrap { max-width: 72rem; margin: 0 auto; padding: 1.25rem 1.5rem 5rem; }
header nav { display: flex; gap: 1.25rem; align-items: baseline;
  padding: 1rem 1.25rem; border-bottom: 1px solid var(--line); }
header nav a { color: var(--muted); text-decoration: none; font-size: .95rem; }
header nav a:hover { color: var(--fg); }
header nav .brand { font-weight: 650; color: var(--fg); margin-right: auto; }
/* Big and heavy. His H1 is the loudest thing on the page by a long way,
   and a 1.6rem heading is what made ours read as a hobby page next to it. */
h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); font-weight: 800; line-height: 1.02;
  letter-spacing: -.03em; margin: .6rem 0 .6rem; }
h2 { font-size: 1.15rem; font-weight: 650; letter-spacing: -.01em; }
.lede { color: var(--muted); font-size: 1.05rem; line-height: 1.55;
  margin: 0 0 1.4rem; max-width: 34em; }
.local { font-size: .85rem; color: var(--ok); margin: 0 0 1.5rem; }

.drop {
  border: 2px dashed var(--line); border-radius: var(--radius);
  padding: 2.5rem 1.5rem; text-align: center; background: var(--panel);
  cursor: pointer; transition: border-color .15s, background .15s;
}
.drop:hover, .drop.over { border-color: var(--accent); }
.drop p { margin: .35rem 0; color: var(--muted); }
.drop strong { color: var(--fg); }

.controls { display: grid; gap: 1rem; margin: 1.5rem 0;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.field { display: flex; flex-direction: column; gap: .3rem; }
.field label { font-size: .82rem; color: var(--muted); }
/* textarea belongs here as much as the others do. Left out, it kept the
   browser's own white box and dark text, which in the dark theme is a white
   rectangle in the middle of the page. */
select, input[type=number], input[type=text], textarea {
  font: inherit; padding: .45rem .55rem; border: 1px solid var(--line);
  border-radius: 7px; background: var(--panel); color: var(--fg);
}
textarea { resize: vertical; }
input[type=range] { width: 100%; }
.modes { display: flex; gap: .4rem; }
.modes button {
  /* nowrap: "Target size" wrapped to two lines and made that button taller
     than its siblings, which reads as a rendering bug rather than a choice. */
  flex: 1; font: inherit; padding: .4rem .3rem; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line); background: var(--panel); color: var(--muted);
  border-radius: 7px;
}
.modes button[aria-pressed=true] { border-color: var(--accent); color: var(--fg); font-weight: 600; }

.results { list-style: none; padding: 0; margin: 1.5rem 0 0; display: grid; gap: .6rem; }
.row {
  display: grid; grid-template-columns: 3.5rem 1fr auto; gap: .9rem; align-items: center;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .6rem .8rem;
}
.row img { width: 3.5rem; height: 3.5rem; object-fit: cover; border-radius: 6px; background: var(--line); }
.row .name { font-size: .9rem; word-break: break-all; }
.row .stat { font-size: .85rem; color: var(--muted); }
.row .stat b { color: var(--ok); font-weight: 650; }
.row .stat .none { color: var(--muted); }
button.act {
  font: inherit; padding: .4rem .8rem; cursor: pointer; border-radius: 7px;
  border: 1px solid var(--accent); background: var(--accent); color: #fff;
}
.act.ghost, button.act.ghost { background: transparent; color: var(--accent); }
.bar { display: flex; gap: .6rem; margin-top: 1.25rem; }
/* A utility class has to beat whatever display rule the element already has.
   Without the flag, .toolbar-group.hidden stayed visible — same specificity,
   and .toolbar-group is declared later in the file, so it won. That is a bug
   that hides nothing and is invisible until you look at the wrong screen. */
.hidden { display: none !important; }

/* Cropper stage: the selection box sits over the preview, so the container is
   the positioning context and the image must not be draggable — a browser's
   native image drag fights the selection drag and wins. */
#stage { position: relative; display: inline-block; margin-top: 1.25rem; max-width: 100%; }
#stage img { display: block; max-width: 100%; border-radius: 10px; user-select: none; -webkit-user-drag: none; }
#box {
  position: absolute; border: 2px solid var(--accent);
  background: rgba(31, 111, 235, .15); pointer-events: none;
}
#readout { color: var(--muted); font-size: .9rem; margin-top: .75rem; }

/* Text tools */
textarea.pad {
  width: 100%; min-height: 12rem; font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--panel); color: var(--fg); border: 1px solid var(--line);
  border-radius: 10px; padding: .75rem; resize: vertical;
}
.pair { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
.stats { display: grid; gap: .75rem; margin: 1.5rem 0;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); }
.stat-card { background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: .75rem .9rem; }
.stat-card b { display: block; font-size: 1.4rem; font-weight: 650; }
.stat-card span { font-size: .8rem; color: var(--muted); }
#output.diff { white-space: pre-wrap; font: 14px/1.5 ui-monospace, monospace;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: .75rem; max-height: 30rem; overflow: auto; }
/* Markers are text as well as colour: a red row and a green row are the same
   row to anyone who cannot distinguish them. */
.diff-added { color: var(--ok); }
.diff-removed { color: #c9484d; }
.diff-same { color: var(--muted); }
label.check { color: var(--fg); font-size: .9rem; display: flex; gap: .4rem; align-items: center; }

/* Calculator */
.result { font: 650 2rem/1.2 ui-monospace, monospace; margin: 1rem 0; word-break: break-all; }
.result.error { font-size: 1rem; font-weight: 400; color: var(--muted); }
#keys { display: grid; gap: .4rem; grid-template-columns: repeat(5, 1fr); margin: 1rem 0; }
#keys button {
  font: inherit; padding: .7rem .2rem; cursor: pointer; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel); color: var(--fg);
}
#keys button:hover { border-color: var(--accent); }
/* Timer */
#clock { font: 650 4rem/1 ui-monospace, monospace; letter-spacing: -.02em; margin: 1.5rem 0 .25rem; }
/* Typing: a mistake should be visible where it happened, not only in a score */
#target { font: 15px/1.9 ui-monospace, monospace; background: var(--panel);
  border: 1px solid var(--line); border-radius: 10px; padding: 1rem; margin: 1rem 0; }
#target .ok { color: var(--ok); }
#target .bad { color: #c9484d; background: rgba(201, 72, 77, .15); border-radius: 2px; }
/* Flashcard */
#face { font-size: 1.5rem; min-height: 6rem; display: flex; align-items: center;
  justify-content: center; text-align: center; background: var(--panel);
  border: 1px solid var(--line); border-radius: 10px; padding: 2rem 1rem; margin: 1rem 0; }

/* --- The head every tool page shares ---------------------------------------
   Someone arriving from a search meets a file picker and learns nothing. This
   is the part above it: where they are, what it costs, what comes out, and a
   drawing of the idea. Same markup on every tool, so it cannot drift. */
.crumbs { font-size: .8rem; color: var(--muted); margin: 1.25rem 0 0; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--fg); }
.badges { display: flex; flex-wrap: wrap; gap: .4rem; margin: .9rem 0 0; }
.badge { font-size: .75rem; padding: .2rem .65rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel); color: var(--muted); }
.badge.good { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, var(--line)); }
.claims { list-style: none; padding: 0; margin: 0 0 1.25rem; display: grid; gap: .45rem; }
.claims li { font-size: .95rem; padding-left: 1.6rem; position: relative; color: var(--fg); }
.claims li::before { content: "✓"; position: absolute; left: 0; color: var(--ok); font-weight: 700; }

/* The drawing. Ours, not anyone else's: the pattern is worth taking and the
   artwork is not. It explains the outcome, which is why it moves — a still
   diagram of a thing that changes over time is a worse diagram. */
.schematic { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .9rem 1.1rem; margin: 0 0 1.5rem; }
/* The cap was 9rem, from when the hero was a single column and a tall
   drawing pushed the file picker past the fold. The hero is two columns now —
   the figure sits beside the heading and claims rather than above the tool —
   so its height costs nothing, and 9rem was shrinking a 320x180 diagram to
   about half the size it is drawn at. */
.schematic svg { display: block; width: 100%; height: auto; max-height: 18rem;
  margin: 0 auto; }
.schematic figcaption { font-size: .8rem; color: var(--muted); margin-top: .6rem; }
/* Motion is decoration here; the caption carries the same information. */
@media (prefers-reduced-motion: reduce) {
  .schematic svg * { animation: none !important; }
}

/* --- The media tools ------------------------------------------------------- */
.player { position: relative; margin: 1.25rem 0 0; background: #000;
  border-radius: var(--radius); overflow: hidden; }
.player video { display: block; width: 100%; max-height: 60vh; background: #000; }
.meta { display: flex; flex-wrap: wrap; gap: 1.25rem; margin: .9rem 0 0;
  font-size: .85rem; color: var(--muted); }
.meta b { color: var(--fg); font-weight: 600; font-variant-numeric: tabular-nums; }

.frames { list-style: none; padding: 0; margin: 1.5rem 0 0;
  display: grid; gap: .6rem; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); }
.frames li { border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  background: var(--panel); }
.frames img { display: block; width: 100%; height: auto; background: var(--line); }
.frames .cap { display: flex; justify-content: space-between; align-items: center;
  gap: .5rem; padding: .45rem .55rem; font-size: .78rem; color: var(--muted); }
.frames .cap button { border: 0; background: none; color: var(--accent);
  font: inherit; cursor: pointer; padding: 0; }

.wave { width: 100%; height: 9rem; display: block; border-radius: var(--radius);
  background: var(--panel); border: 1px solid var(--line); touch-action: none;
  cursor: ew-resize; margin-top: 1.25rem; }

.progress { height: .35rem; border-radius: 999px; background: var(--line);
  overflow: hidden; margin: 1.25rem 0 0; }
.progress i { display: block; height: 100%; width: 0; background: var(--accent);
  transition: width .12s linear; }
.note { font-size: .85rem; color: var(--muted); margin: .6rem 0 0; }
.note.warn { color: #c9484d; }
input.bad { border-color: #c9484d; }

/* Swapping the file out.
   Every one of these tools hid its drop zone the moment a file arrived, and
   put nothing in its place — so the second video of a session meant reloading
   the page. This is the way back, and it sits next to the file it would
   replace rather than at the bottom with the actions. */
.loaded-as { display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem;
  margin: 1.1rem 0 0; font-size: .88rem; color: var(--muted); }
.loaded-as b { color: var(--fg); font-weight: 600; word-break: break-all; }
.loaded-as button { border: 0; background: none; color: var(--accent);
  font: inherit; cursor: pointer; padding: 0; }

/* ═══ The shell every tool page wears ═══════════════════════════════════════
 *
 * Built after putting our pages next to the originals. The gap was not one
 * missing rule, it was that we had no components at all: every page laid its
 * controls out as a bare grid of labelled inputs, so nothing had a primary
 * action, nothing grouped, and a tool with four options looked exactly like a
 * tool with twenty.
 *
 * What follows is the small set of parts his pages are actually made of.
 */

/* ── The hero: heading on the left, a drawing of the idea on the right ──────
 * Side by side, not stacked. Stacked, the drawing pushes the tool below the
 * fold and someone has to scroll before they can do anything — which is
 * exactly what happened, and what was reported. */
/* Not `.tool-hero`. The host site owns that class and renders a 3D model into
   anything carrying it — so a tool page using the name got a landing-page
   hero drawn over its diagram, on all forty-nine of them. A shared class
   name across two stylesheets is a collision waiting for the moment they
   are served together, which is exactly what happened. */
.tool-hero { display: grid; gap: 2rem; align-items: start; margin: .5rem 0 2.5rem; }
@media (min-width: 62rem) {
  .tool-hero { grid-template-columns: 1.05fr .95fr; gap: 3rem; }
}
.tool-hero-figure { margin: 0; }
.tool-hero-figure .schematic { margin: 0; }

/* ── Panels ───────────────────────────────────────────────────────────────── */
.panel { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); }
.panel + .panel { margin-top: 1rem; }
.panel-body { padding: 1.1rem; }

/* ── The toolbar ───────────────────────────────────────────────────────────
 * Primary controls on the left, primary actions right-aligned. This is the
 * single biggest difference: it gives a tool a place where the important
 * things live, so "Save all" is not the fourteenth button down the page. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.1rem;
  padding: .7rem .8rem; border-bottom: 1px solid var(--line); }
.toolbar-actions { margin-left: auto; display: flex; flex-wrap: wrap;
  align-items: center; gap: .5rem; }
.toolbar-label { font-size: .85rem; color: var(--muted); white-space: nowrap; }
.toolbar-group { display: flex; align-items: center; gap: .5rem; }

/* ── Segmented control ─────────────────────────────────────────────────────
 * One control with the options visible, rather than a dropdown that hides
 * them. When there are three or four choices and the choice matters, showing
 * them is the whole point. */
.seg { display: inline-flex; padding: 3px; gap: 2px; background: var(--sunken);
  border: 1px solid var(--line); border-radius: 10px; }
.seg button { font: inherit; font-size: .88rem; padding: .35rem .8rem; cursor: pointer;
  border: 0; border-radius: 7px; background: transparent; color: var(--muted); }
.seg button:hover { color: var(--fg); }
.seg button[aria-pressed="true"] { background: var(--panel); color: var(--fg);
  font-weight: 600; box-shadow: var(--shadow); }
.seg button:disabled { opacity: .4; cursor: not-allowed; }

/* ── A slider that shows its number ────────────────────────────────────────
 * A slider with the value only in a label somewhere else is a slider you
 * have to hunt for. The readout is tabular so it does not jump as it moves. */
.slider { display: inline-flex; align-items: center; gap: .6rem; }
.slider input[type=range] { width: 8rem; }
.slider output { font-size: .85rem; color: var(--fg); font-variant-numeric: tabular-nums;
  min-width: 3ch; text-align: right; }

/* ── Buttons ──────────────────────────────────────────────────────────────── */
.act, button.act { font: inherit; text-decoration: none; font-size: .9rem; font-weight: 600; cursor: pointer;
  padding: .5rem .95rem; border-radius: 9px; border: 1px solid transparent;
  background: var(--accent); color: var(--accent-fg);
  display: inline-flex; align-items: center; gap: .45rem; }
.act:hover, button.act:hover { filter: brightness(1.08); }
.act:disabled, button.act:disabled { opacity: .45; cursor: not-allowed; filter: none; }
.act.ghost, button.act.ghost { background: var(--panel); color: var(--fg); border-color: var(--line); }
.act.ghost:hover, button.act.ghost:hover { border-color: var(--line-strong); filter: none; }
.act.quiet, button.act.quiet { background: transparent; color: var(--muted); border-color: transparent; }
.act.quiet:hover, button.act.quiet:hover { color: var(--fg); }
.act.danger, button.act.danger { background: transparent; color: var(--bad); border-color: transparent; }
.act svg, button.act svg { width: 1em; height: 1em; flex: none; }

/* ── Notice ───────────────────────────────────────────────────────────────
 * For the things a tool knows about itself that the person cannot see — that
 * this browser will not write AVIF, that a fade has turned the lossless path
 * off. Not an error: an explanation, styled like one. */
.notice { display: flex; gap: .7rem; align-items: flex-start; margin: .9rem;
  padding: .8rem .9rem; border-radius: var(--radius-sm);
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: .88rem; color: var(--muted); line-height: 1.5; }
.notice svg { width: 1.05rem; height: 1.05rem; flex: none; margin-top: .12rem;
  color: var(--faint); }
.notice.warn { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 30%, var(--line)); }
.notice.good { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, var(--line)); }
.notice b { color: var(--fg); font-weight: 600; }

/* ── Drop zone ────────────────────────────────────────────────────────────── */
.drop { border: 1.5px dashed var(--line-strong); border-radius: var(--radius);
  padding: 3rem 1.5rem; text-align: center; cursor: pointer; margin: .9rem;
  background: var(--sunken); transition: border-color .12s, background .12s; }
.drop:hover, .drop.over { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, var(--sunken)); }
.drop svg { width: 2rem; height: 2rem; color: var(--faint); margin-bottom: .6rem; }
.drop strong { display: block; color: var(--fg); font-size: 1.05rem; margin-bottom: .3rem; }
.drop p { margin: .2rem 0; color: var(--muted); font-size: .9rem; }

/* ── Side panel, for tools with a lot of state ────────────────────────────── */
.with-side { display: grid; gap: 1rem; }
@media (min-width: 60rem) { .with-side { grid-template-columns: 1fr 19rem; } }
.side { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .9rem; align-self: start; }
.side h3 { font-size: .7rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--faint); margin: 1.1rem 0 .55rem; }
.side h3:first-child { margin-top: .2rem; }
.side-row { display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; font-size: .88rem; padding: .45rem .55rem; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--panel-2); margin-bottom: .4rem; }
.presets { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.presets button { font: inherit; cursor: pointer; padding: .55rem .2rem; line-height: 1.15;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--panel-2); color: var(--fg); }
.presets button:hover { border-color: var(--line-strong); }
.presets button[aria-pressed="true"] { border-color: var(--accent); }
.presets button span { display: block; font-size: .68rem; color: var(--muted);
  font-weight: 400; margin-top: .15rem; }
.presets button b { font-size: 1.05rem; font-weight: 700; }

/* ── The index ────────────────────────────────────────────────────────────
 * Was reusing the image-result row, which expects a thumbnail and a button,
 * so every link rendered unstyled and stretched. A tool listing is a grid of
 * cards; this is that. */
.tool-grid { list-style: none; padding: 0; margin: 0 0 2.5rem;
  display: grid; gap: .7rem;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); }
.tool-card { border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); transition: border-color .12s, transform .12s; }
.tool-card:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.tool-card a { display: block; padding: .95rem 1rem; text-decoration: none; color: inherit; }
.tool-card .name { font-weight: 650; font-size: .98rem; margin-bottom: .2rem; }
.tool-card .what { font-size: .87rem; color: var(--muted); line-height: 1.45; }
.section-head { display: flex; align-items: baseline; gap: .7rem; margin: 2.2rem 0 .9rem; }
.section-head h2 { margin: 0; }
.section-head span { font-size: .8rem; color: var(--faint); }

/* ── Reference tables ─────────────────────────────────────────────────────
 * Layer three of a tool page. His pages carry a real table of the thing the
 * tool deals in — every thumbnail size YouTube makes, what each is for — and
 * it is a large part of why someone searching finds them. */
.ref { width: 100%; border-collapse: collapse; font-size: .9rem;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; }
.ref-scroll { overflow-x: auto; margin: 1rem 0 2rem; }
.ref th { text-align: left; font-weight: 600; color: var(--fg); font-size: .82rem;
  padding: .7rem .9rem; background: var(--panel-2); border-bottom: 1px solid var(--line); }
.ref td { padding: .7rem .9rem; border-bottom: 1px solid var(--line);
  color: var(--muted); vertical-align: top; }
.ref tr:last-child td { border-bottom: 0; }
.ref td:first-child { color: var(--fg); font-family: ui-monospace, monospace; font-size: .85rem; }
.ref td:nth-child(2), .ref td:nth-child(3) { white-space: nowrap; }

/* ── Long-form sections below the tool ──────────────────────────────────── */
.prose { max-width: 42em; margin: 3rem 0 0; }
.prose h2 { font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; margin: 2.5rem 0 .8rem; }
.prose h3 { font-size: 1.05rem; font-weight: 650; margin: 1.8rem 0 .5rem; }
.prose p { color: var(--muted); margin: 0 0 1rem; line-height: 1.65; }
.prose li { color: var(--muted); margin-bottom: .5rem; line-height: 1.6; }
.prose a { color: var(--accent); }
.prose code { font-family: ui-monospace, monospace; font-size: .88em;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 5px; padding: .1em .35em; color: var(--fg); }
.prose b, .prose strong { color: var(--fg); font-weight: 650; }

/* A row of links to the tools this one leads to. His pages cross-link
   heavily and it is the difference between a tool and a site. */
.related { display: flex; flex-wrap: wrap; gap: .5rem; margin: 1rem 0 0; }
.related a { font-size: .88rem; text-decoration: none; color: var(--fg);
  border: 1px solid var(--line); background: var(--panel);
  border-radius: 999px; padding: .35rem .85rem; }
.related a:hover { border-color: var(--accent); }

/* An input with a button living inside its right edge. */
.input-group { display: flex; gap: .5rem; align-items: stretch; }
.input-group .grow { position: relative; flex: 1; display: flex; }
.input-group input { flex: 1; }
.input-group .inset { position: absolute; right: .3rem; top: 50%; transform: translateY(-50%); }

/* A guide's link back to the tool it is about. Sits above and below the
   prose: someone who arrives from a search and is convinced in the first
   paragraph should not have to scroll to the end to act on it. */
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem;
  justify-content: space-between; margin: 1.5rem 0;
  padding: 1rem 1.1rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel); }
.cta-title { font-weight: 650; }
.cta-what { font-size: .88rem; color: var(--muted); }
.cta a { text-decoration: none; }

/* ── Transport ─────────────────────────────────────────────────────────────
 * A media tool needs a transport, not a button labelled "play". Skip to
 * start, play, loop, the position against the total, and the zoom — all in
 * one row, in the order every editor has put them for thirty years. */
.transport { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  padding: .7rem .8rem; border-bottom: 1px solid var(--line); }
.transport .spacer { margin-left: auto; }
.icon-btn { display: inline-flex; align-items: center; justify-content: center;
  width: 2.2rem; height: 2.2rem; padding: 0; cursor: pointer;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--panel); color: var(--fg); }
.icon-btn:hover { border-color: var(--line-strong); }
.icon-btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.icon-btn:disabled { opacity: .4; cursor: not-allowed; }
.icon-btn svg { width: 1.05rem; height: 1.05rem; }
.icon-btn.big { width: 2.8rem; height: 2.8rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-fg); border-color: transparent; }
.icon-btn.big svg { width: 1.2rem; height: 1.2rem; }
.timecode { font: 1.05rem/1 ui-monospace, monospace; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em; }
.timecode .total { color: var(--faint); font-size: .9rem; }

/* ── A field with a button welded to it ─────────────────────────────────── */
.timefield { display: inline-flex; align-items: center; gap: .4rem; }
.timefield label { font-size: .7rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--faint); }
.timefield input { width: 8ch; font-family: ui-monospace, monospace;
  font-variant-numeric: tabular-nums; text-align: center; }
.timefield .set { font-size: .78rem; padding: .3rem .5rem; }
.timefield .set kbd { margin-left: .3rem; }

/* ── The keyboard legend ───────────────────────────────────────────────────
 * On the page, not in a paragraph someone has to read. Every shortcut this
 * tool has, visible while they use it. */
kbd { font: .72rem/1 ui-monospace, monospace; padding: .25rem .4rem;
  border: 1px solid var(--line-strong); border-bottom-width: 2px;
  border-radius: 5px; background: var(--panel-2); color: var(--fg); }
.keys { display: flex; flex-wrap: wrap; gap: .4rem 1rem; margin: .9rem;
  font-size: .8rem; color: var(--muted); }
.keys span { display: inline-flex; align-items: center; gap: .35rem; }

/* ── The result strip ──────────────────────────────────────────────────── */
.outcome { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem;
  margin: .9rem; padding: .9rem 1rem; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--panel-2); }
.outcome .verdict { display: flex; align-items: center; gap: .5rem;
  font-weight: 650; color: var(--ok); }
.outcome .verdict.decoded { color: var(--muted); }
.outcome .figure { display: grid; gap: .1rem; }
.outcome .figure dt { font-size: .68rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--faint); }
.outcome .figure dd { margin: 0; font-variant-numeric: tabular-nums; font-weight: 600; }
.outcome .grow { margin-left: auto; }

/* ── Numbered how-to steps ─────────────────────────────────────────────── */
.steps { display: grid; gap: 1.5rem; margin: 1.5rem 0 0;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.steps h3 { margin: .6rem 0 .35rem; font-size: 1.05rem; font-weight: 650; }
.steps p { margin: 0; color: var(--muted); font-size: .92rem; line-height: 1.6; }
.steps .n { display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border-radius: 999px; font-weight: 650;
  font-size: .9rem; border: 1px solid var(--line-strong); color: var(--fg); }

/* ── FAQ ───────────────────────────────────────────────────────────────── */
.faq { margin: 1.5rem 0 0; max-width: 44em; }
.faq details { border-bottom: 1px solid var(--line); padding: .9rem 0; }
.faq summary { cursor: pointer; font-weight: 600; list-style: none; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before { content: "+"; display: inline-block; width: 1.2rem;
  color: var(--faint); font-weight: 400; }
.faq details[open] summary::before { content: "−"; }
.faq p { color: var(--muted); margin: .6rem 0 0 1.2rem; line-height: 1.6; }

/* Full screen: the tool alone, for when the waveform is the work. */
.panel.full { position: fixed; inset: 0; z-index: 50; margin: 0;
  border-radius: 0; overflow: auto; }

/* ── The page canvas ───────────────────────────────────────────────────────
 * One tile per page, from every file at once. This is the difference between
 * a merger and a list of filenames with arrows beside it. */
.canvas { display: grid; gap: .7rem; margin: .9rem;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); }
.tile { position: relative; border: 2px solid var(--line); border-radius: 10px;
  background: var(--panel-2); cursor: grab; overflow: hidden;
  transition: border-color .1s, transform .1s; }
.tile:hover { border-color: var(--line-strong); }
.tile[aria-selected="true"] { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--panel-2)); }
.tile:active { cursor: grabbing; transform: scale(.98); }
.tile .thumb { display: flex; align-items: center; justify-content: center;
  aspect-ratio: 3 / 4; padding: .4rem; }
/* The turn is shown by turning the thumbnail, so what is on screen is what
   comes out. A badge reading "90°" would describe the result instead. */
.tile .thumb canvas { max-width: 100%; max-height: 100%; display: block;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transform: rotate(var(--turn, 0deg)); transition: transform .15s; }
.tile .cap { padding: .3rem .5rem; font-size: .75rem; color: var(--muted);
  text-align: center; border-top: 1px solid var(--line);
  font-variant-numeric: tabular-nums; }
.empty-state { padding: 3rem 1.5rem; text-align: center; color: var(--muted); }

/* A seam between two page tiles: click it to cut the document there. */
.seam { align-self: stretch; width: 1.6rem; padding: 0; cursor: pointer;
  border: 0; background: none; color: var(--faint);
  display: flex; align-items: center; justify-content: center;
  border-radius: 6px; }
.seam svg { width: 1rem; height: 1rem; opacity: .35; transition: opacity .1s; }
.seam:hover svg { opacity: 1; color: var(--accent); }
.seam[aria-pressed="true"] { background: color-mix(in srgb, var(--ok) 14%, transparent); }
.seam[aria-pressed="true"] svg { opacity: 1; color: var(--ok); }
/* The cut itself, drawn as a line through the gap. */
.seam[aria-pressed="true"]::after { content: ""; position: absolute;
  width: 2px; height: 60%; background: var(--ok); border-radius: 2px; }
.seam { position: relative; }
/* A page that Extract mode is going to drop. */
.tile.dropped { opacity: .35; }
.tile.dropped .thumb canvas { filter: grayscale(1); }
/* The stat rail's good state — the one that says nothing was touched. */
.outcome .figure dd.ok { color: var(--ok); }
.canvas .tile { position: relative; }
.canvas .tile .thumb img { max-width: 100%; max-height: 100%; display: block; }

/* ── Signing: the page, and the things placed on it ───────────────────────── */
.sheet { position: relative; display: inline-block; margin: .9rem;
  box-shadow: var(--shadow); background: #fff; }
.sheet canvas { display: block; }
.placed-layer { position: absolute; inset: 0; cursor: crosshair; }
.placed { position: absolute; cursor: move; border: 1px solid transparent; }
.placed img { width: 100%; height: 100%; display: block; pointer-events: none; }
.placed[aria-selected="true"] { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent); }
.placed .handle { position: absolute; right: -5px; bottom: -5px;
  width: 11px; height: 11px; border-radius: 3px; cursor: nwse-resize;
  background: var(--accent); border: 2px solid var(--panel); opacity: 0; }
.placed[aria-selected="true"] .handle { opacity: 1; }

/* The numbered page row, with a mark on the ones already signed. */
.page-row { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
.page-dot { font: inherit; font-size: .78rem; cursor: pointer; min-width: 1.9rem;
  padding: .25rem .4rem; border-radius: 7px; border: 1px solid var(--line);
  background: var(--panel); color: var(--muted); position: relative; }
.page-dot[aria-pressed="true"] { border-color: var(--accent); color: var(--fg); font-weight: 600; }
.page-dot.signed::after { content: ""; position: absolute; top: 2px; right: 2px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--ok); }

/* The signature pad and its tabs. */
.pad { width: 100%; height: 9rem; display: block; cursor: crosshair;
  border: 1px dashed var(--line-strong); border-radius: var(--radius-sm);
  background: var(--panel); touch-action: none; }
.pad-hint { font-size: .8rem; color: var(--faint); margin: .4rem 0 0; }
.photo-preview { display: flex; align-items: center; justify-content: center;
  min-height: 6rem; border: 1px dashed var(--line-strong);
  border-radius: var(--radius-sm); background: var(--panel); padding: .5rem; }
.photo-preview canvas { max-width: 100%; max-height: 8rem;
  background: repeating-conic-gradient(var(--line) 0 25%, transparent 0 50%) 0 0 / 14px 14px; }

/* ── The captures shelf ───────────────────────────────────────────────────
 * Frames grabbed while hunting for a moment, kept until they are saved or
 * thrown away. Capturing straight to a download forces a decision per frame
 * and fills the Downloads folder with things nobody has looked at yet. */
.shelf { display: grid; gap: .5rem; align-content: start;
  max-height: 34rem; overflow-y: auto; padding-right: .2rem; }
.capture { border: 1px solid var(--line); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--panel-2); }
.capture img { display: block; width: 100%; height: auto; background: #000; }
.capture .cap { display: flex; align-items: center; gap: .4rem;
  padding: .3rem .45rem; font-size: .74rem; color: var(--muted); }
.capture .cap span { flex: 1; font-variant-numeric: tabular-nums; }
.capture .cap button { padding: .1rem .4rem; font-size: .74rem; }
.shelf-empty { border: 1px dashed var(--line-strong); border-radius: var(--radius-sm);
  padding: 1.6rem 1rem; text-align: center; font-size: .85rem;
  color: var(--muted); line-height: 1.5; }

/* ── The crop rectangle ───────────────────────────────────────────────────── */
.crop-frame { position: relative; display: block; line-height: 0;
  background: repeating-conic-gradient(var(--line) 0 25%, transparent 0 50%) 0 0 / 18px 18px;
  border-radius: var(--radius-sm); overflow: hidden; touch-action: none; }
.crop-frame img { display: block; width: 100%; height: auto; user-select: none;
  -webkit-user-drag: none; }
.crop-box { position: absolute; cursor: move; box-sizing: border-box;
  border: 1px solid #fff; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55); }
/* The rule of thirds. How anyone frames a picture, and the difference
   between a crop tool and a rectangle. */
.crop-grid { position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to right, transparent 33.33%, rgba(255,255,255,.45) 33.33%,
      rgba(255,255,255,.45) calc(33.33% + 1px), transparent calc(33.33% + 1px),
      transparent 66.66%, rgba(255,255,255,.45) 66.66%,
      rgba(255,255,255,.45) calc(66.66% + 1px), transparent calc(66.66% + 1px)),
    linear-gradient(to bottom, transparent 33.33%, rgba(255,255,255,.45) 33.33%,
      rgba(255,255,255,.45) calc(33.33% + 1px), transparent calc(33.33% + 1px),
      transparent 66.66%, rgba(255,255,255,.45) 66.66%,
      rgba(255,255,255,.45) calc(66.66% + 1px), transparent calc(66.66% + 1px)); }
.crop-handle { position: absolute; width: 11px; height: 11px; background: #fff;
  border-radius: 2px; transform: translate(-50%, -50%); }
.crop-nw, .crop-se { cursor: nwse-resize; }
.crop-ne, .crop-sw { cursor: nesw-resize; }
.crop-n, .crop-s { cursor: ns-resize; }
.crop-e, .crop-w { cursor: ew-resize; }
.crop-label { position: absolute; left: 6px; bottom: 6px; padding: .15rem .45rem;
  border-radius: 4px; background: rgba(0, 0, 0, .7); color: #fff;
  font: .72rem/1.4 ui-monospace, monospace; pointer-events: none; }

/* Source and result, side by side. */
.compare { display: grid; gap: 1rem; margin: .9rem; }
@media (min-width: 60rem) { .compare { grid-template-columns: 1fr 1fr; } }
.compare > section { border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .9rem; }
.compare h3 { margin: 0 0 .7rem; font-size: .95rem; font-weight: 650;
  display: flex; align-items: baseline; gap: .6rem; }
.compare h3 .stat { margin-left: auto; font-weight: 400; font-size: .8rem;
  font-family: ui-monospace, monospace; }
.result-frame { background: repeating-conic-gradient(var(--line) 0 25%, transparent 0 50%) 0 0 / 18px 18px;
  border-radius: var(--radius-sm); display: flex; align-items: center;
  justify-content: center; min-height: 12rem; overflow: hidden; }
.result-frame img { max-width: 100%; max-height: 22rem; display: block; }

/* ── The empty state is the promise ───────────────────────────────────────
   Both panels are on the page from the moment it loads. They used to appear
   only after a file was chosen, with a dashed drop rectangle standing where
   they would be — so the first thing anybody saw gave no hint that the point
   of the tool is watching the real encoded file change beside the original.

   The drop zone therefore lives INSIDE the left panel and fills it, and the
   right panel says what will appear there. Both need a minimum height or the
   empty panels collapse to their padding and the page looks broken in a
   different way. */
.crop-frame { min-height: 12rem; }
.crop-frame .drop {
  position: absolute; inset: 0; margin: 0; border: 0; background: transparent;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: .3rem; line-height: 1.5;
}
.result-waiting {
  margin: 0; padding: 0 1.5rem; max-width: 22rem;
  text-align: center; color: var(--muted); font-size: .9rem; line-height: 1.6;
}

/* ── File transfer ────────────────────────────────────────────────────────
 *
 * One list holds files and messages together, in the order the two people
 * experienced them, so the page reads as a conversation rather than as two
 * unrelated queues.
 */
.stat.warn, .name.warn { color: var(--bad); }
.act.small { padding: .25rem .6rem; font-size: .8rem; }

.code-big {
  font: 600 clamp(2rem, 9vw, 3.4rem)/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .12em; text-align: center; padding: 1rem 0;
}
.safety {
  font: 700 2rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .25em; color: var(--fg); margin: .3rem 0 0;
}
#safety-panel { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.qr { display: grid; place-items: center; padding: .5rem; }
.qr svg { width: min(180px, 46vw); height: auto; }

.thread {
  list-style: none; margin: 0; padding: .5rem; display: grid; gap: .75rem;
  max-height: 22rem; overflow-y: auto; background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.panel.full .thread { max-height: none; height: calc(100vh - 16rem); }
/* Before anything has been sent the list is an empty bordered pill, which
   reads as a rendering fault. Say what it is for instead. */
.thread:empty { display: grid; place-items: center; min-height: 7rem; padding: 1.5rem; }
.thread:empty::before {
  content: "Files and messages appear here, both directions.";
  color: var(--faint); font-size: .9rem; text-align: center;
}
.msg {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: .6rem .75rem; max-width: 40rem;
}
/* Mine on the right, theirs on the left. The only cue needed, and it works
   before any label is read. */
.msg.me { margin-left: auto; border-color: color-mix(in srgb, var(--accent) 30%, var(--line)); }
.msg.them { margin-right: auto; }
.msg-text { margin: 0 0 .4rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-media { display: block; max-width: 100%; max-height: 16rem; border-radius: var(--radius-sm); margin: .5rem 0; }
audio.msg-media { max-height: none; width: 100%; }

.meter {
  height: 6px; border-radius: 3px; background: var(--sunken);
  overflow: hidden; margin: .4rem 0;
}
.meter span { display: block; height: 100%; width: 0; background: var(--accent); transition: width .2s linear; }

/* A file can be dropped anywhere on the page once connected, so the whole
   panel is the target and says so while something is over it. */
.panel.over { outline: 2px dashed var(--accent); outline-offset: 4px; }

/* ── The layered-text editor ──────────────────────────────────────────────
 *
 * A picture on the left and a tall rail of controls on the right, which is
 * the arrangement every image editor has settled on: the thing being changed
 * stays still while the controls scroll past it.
 */
.editor { display: grid; grid-template-columns: minmax(0, 1fr) 20rem; gap: 1rem; align-items: start; }
@media (max-width: 860px) { .editor { grid-template-columns: 1fr; } }

.editor-stage { min-width: 0; }
.editor-stage canvas {
  display: block; width: 100%; height: auto; max-height: 70vh; object-fit: contain;
  border-radius: var(--radius-sm); background: var(--sunken); touch-action: none;
}
.panel.full .editor-stage canvas { max-height: calc(100vh - 12rem); }

.editor-rail {
  display: grid; gap: .6rem; align-content: start;
  max-height: 72vh; overflow-y: auto; padding: 0 .75rem .75rem 0;
}
@media (max-width: 860px) { .editor-rail { max-height: none; overflow: visible; } }
.rail-head {
  font-size: .7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); margin: .8rem 0 0; display: flex; justify-content: space-between;
  align-items: baseline; gap: .5rem;
}
.rail-head:first-child { margin-top: 0; }

/* The preset chips show the look rather than describing it. */
.presets button b[data-preset="poster"]   { font-weight: 900; letter-spacing: -.04em; }
.presets button b[data-preset="outline"]  { color: transparent; -webkit-text-stroke: 1px var(--fg); }
.presets button b[data-preset="glow"]     { text-shadow: 0 0 7px var(--fg); }
.presets button b[data-preset="wide"]     { font-weight: 300; letter-spacing: .22em; }
.presets button b[data-preset="ghost"]    { opacity: .4; font-weight: 900; }
.presets button b[data-preset="stamp"]    { font-weight: 900; letter-spacing: .1em; }

.layer-row {
  display: flex; align-items: center; gap: .5rem; padding: .35rem .5rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel);
}
.layer-row.on { border-color: var(--accent); }
.layer-name {
  flex: 1; min-width: 0; font: inherit; text-align: left; cursor: pointer;
  background: none; border: 0; color: var(--fg); padding: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A colour well and a slider, on one line, in a rail too narrow for two rows. */
.swatch-pair { display: flex; align-items: center; gap: .5rem; }
.swatch-pair input[type=color] { flex: 0 0 2.2rem; height: 2rem; padding: 0; border-radius: 6px; }
.swatch-pair input[type=range] { flex: 1; }

.act.block { display: block; width: 100%; text-align: center; }
.toolbar .grow { flex: 1; }
.field.inline { display: flex; align-items: center; gap: .4rem; }
.field.inline span { font-size: .8rem; color: var(--muted); }
.notice.good { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, var(--line)); }

/* Full-screen view widths. A portrait photo filling the height of a wide
   monitor is right; a landscape one is a letterbox. No single answer, so
   three buttons — shown only where they mean anything. */
#shell:not(.full) #view-group { display: none; }
#stage[data-view="narrow"] canvas { max-width: 42rem; margin: 0 auto; }
#stage[data-view="wide"] canvas { max-width: 68rem; margin: 0 auto; }
#stage[data-view="full"] canvas { max-width: none; }

/* A cutout needs something behind it to be seen against, and the convention
   every image editor uses is a chequerboard.
   Built from the ink colour at low opacity rather than from two palette
   tokens: --panel and --sunken are a visible pair in the light theme and
   nearly the same colour in the dark one, where the chequer disappeared and
   transparency looked like a black fill. */
/* `canvas.checks`, not `.checks`: `.editor-stage canvas` is a class plus a
   type and wins a specificity tie against a bare class, so the chequer was
   losing to it and being erased by its `background` shorthand. */
canvas.checks {
  --check: color-mix(in srgb, var(--fg) 11%, transparent);
  background-image:
    linear-gradient(45deg, var(--check) 25%, transparent 25%, transparent 75%, var(--check) 75%),
    linear-gradient(45deg, var(--check) 25%, transparent 25%, transparent 75%, var(--check) 75%);
  background-size: 18px 18px;
  background-position: 0 0, 9px 9px;
  background-color: var(--panel);
}
canvas.picking { cursor: crosshair; }
.swatch {
  width: 2.2rem; height: 2rem; border-radius: 6px; border: 1px solid var(--line-strong);
  background: #fff; flex: 0 0 auto;
}

/* Generated clips: a player, what it is, and the two ways to keep it. */
.clips { list-style: none; margin: .5rem 0 0; padding: 0; display: grid; gap: .6rem; }
.clip {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: .6rem .75rem; background: var(--panel); display: grid; gap: .4rem;
}
.clip-player { width: 100%; height: 2.2rem; }
#shell[data-view="narrow"] { max-width: 42rem; margin-inline: auto; }
#shell[data-view="wide"] { max-width: 68rem; margin-inline: auto; }
#shell:not(.full) #view-group { display: none; }

/* The rail is a fixed 20rem column, so everything in it has to agree to fit.
   Three mode buttons and a select full of long voice descriptions both push
   past it otherwise, and the column runs off the side of the panel. */
.editor-rail .modes { flex-wrap: wrap; }
.editor-rail .modes button { flex: 1 1 auto; min-width: 0; }
.editor-rail select,
.editor-rail input,
.editor-rail textarea { width: 100%; max-width: 100%; min-width: 0; }
.editor-rail > * { min-width: 0; }
.rail-head .stat { font-weight: 400; letter-spacing: 0; text-transform: none; }

/* OCR output. The marked view is the text with the doubtful words called
   out, which is the whole point of the tool — a clean transcript with no
   marks is one you have to proofread against the original word by word. */
.ocr-text {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--panel); padding: .7rem .85rem; min-height: 8rem;
  max-height: 24rem; overflow-y: auto; white-space: pre-wrap;
  font: inherit; line-height: 1.6; width: 100%;
}
.unsure {
  text-decoration: underline wavy var(--bad);
  text-underline-offset: 3px; cursor: help;
}
.check { display: flex; align-items: center; gap: .5rem; font-size: .9rem; color: var(--muted); }
.check input { width: auto; }
.act.on, .act.ghost.on { border-color: var(--accent); color: var(--fg); }

/* ── The focus timer ──────────────────────────────────────────────────────*/
.timer-stage { display: grid; justify-items: center; gap: .9rem; padding: 1rem 0; }
.ring-wrap { position: relative; width: min(300px, 70vw); aspect-ratio: 1; }
.ring { width: 100%; height: 100%; transform: rotate(-90deg); }
.ring-track { fill: none; stroke: var(--sunken); stroke-width: 10; }
/* The sweep is set from the tool in stroke-dashoffset, so the ring is the
   same number the clock is rather than a second animation to keep in step. */
.ring-fill {
  fill: none; stroke: var(--accent); stroke-width: 10; stroke-linecap: round;
  transition: stroke-dashoffset .25s linear;
}
.ring-face {
  position: absolute; inset: 0; display: grid; place-content: center; text-align: center;
}
.clock {
  font: 700 clamp(2.4rem, 9vw, 3.4rem)/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.phase { font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); margin-top: .4rem; }
.panel.full .ring-wrap { width: min(440px, 60vh); }

.dots { display: flex; gap: .5rem; }
.dots .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line-strong); }
.dots .dot.on { background: var(--accent); }
.dots .dot.now { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }

.timer-buttons { justify-content: center; }
#ends-wrap.dim { opacity: .5; }
.modes.wrap { flex-wrap: wrap; }

.tasks { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .35rem; }
.task {
  display: flex; align-items: center; gap: .5rem; padding: .35rem .5rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel);
}
.task.current { border-color: var(--accent); }
.task.complete .task-name { text-decoration: line-through; color: var(--faint); }
.task-name {
  flex: 1; min-width: 0; font: inherit; text-align: left; cursor: pointer;
  background: none; border: 0; color: var(--fg); padding: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.today { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; }
.today div {
  background: var(--sunken); border-radius: var(--radius-sm); padding: .6rem .5rem; text-align: center;
}
.today b { display: block; font-size: 1.4rem; font-variant-numeric: tabular-nums; }
.today span { font-size: .68rem; color: var(--muted); }

/* The settings panel: one button, and everything the presets do not cover. */
.settings-toggle {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  margin-top: 1rem; padding: .75rem .9rem; font: inherit; cursor: pointer;
  background: var(--panel); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.settings-toggle:hover { border-color: var(--line-strong); }
.settings-toggle[aria-expanded="true"] { border-color: var(--accent); }
.settings-toggle svg { width: 16px; height: 16px; }
.settings { display: grid; gap: .75rem; margin-top: .75rem; }
.settings-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1rem;
  padding: .8rem .9rem; background: var(--sunken); border-radius: var(--radius);
}
.settings-row label { font-size: .85rem; color: var(--muted); }
.settings-row input[type=number] { width: 4.5rem; }
.settings-row .unit { font-size: .78rem; color: var(--faint); margin-left: -.5rem; }

/* ── Notepad ──────────────────────────────────────────────────────────────*/
.notes-strip {
  display: flex; gap: .3rem; overflow-x: auto; padding: .5rem 0;
  border-bottom: 1px solid var(--line);
}
.note-tab {
  flex: 0 0 auto; max-width: 12rem; padding: .35rem .7rem;
  font: inherit; font-size: .85rem; cursor: pointer;
  background: var(--sunken); color: var(--muted);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.note-tab:hover { color: var(--fg); }
.note-tab.on { background: var(--panel); border-color: var(--accent); color: var(--fg); }

.pad-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; padding: .7rem 0; }
.find-group { display: flex; align-items: center; gap: .4rem; }
.find-group input { width: 12rem; }
.look { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.look .modes button { font-size: .78rem; padding: .3rem .55rem; }

/* The pad itself is centred at the chosen line length rather than stretched:
   a column of text is easier to read than a full-width one, and the width is
   the setting that governs it. */
#pad {
  display: block; width: 100%; margin: 0 auto; min-height: 26rem;
  line-height: 1.7; resize: vertical;
}
.panel.full #pad { min-height: calc(100vh - 18rem); }

/* ── Typing practice ──────────────────────────────────────────────────────*/
.live { display: flex; gap: 1.5rem; padding: .8rem 0; }
.live div { text-align: left; }
.live b { display: block; font-size: 1.5rem; font-variant-numeric: tabular-nums; line-height: 1; }
.live span { font-size: .7rem; color: var(--muted); }

.words {
  font: 400 1.35rem/2 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--sunken); border-radius: var(--radius);
  padding: 1rem 1.2rem; min-height: 8.5rem; cursor: text;
  display: flex; flex-wrap: wrap; gap: 0 .7ch; overflow: hidden;
}
.tw { color: var(--faint); }
.tw.done { color: var(--muted); }
/* The word being typed is underlined rather than boxed: a box shifts the
   line every time the word changes width. */
.tw.now { color: var(--fg); box-shadow: 0 2px 0 var(--accent); }
.tw .ok { color: var(--ok); }
.tw .bad { color: var(--bad); text-decoration: underline wavy var(--bad); }

.type-input {
  width: 100%; margin-top: .8rem;
  font: 400 1.05rem/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.results-grid { grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr)); }

.heatmap { display: grid; gap: .3rem; justify-items: center; margin: .4rem 0; }
.key-row { display: flex; gap: .3rem; }
.key {
  width: 2.1rem; height: 2.1rem; display: grid; place-items: center;
  border-radius: 6px; background: var(--sunken); font-size: .85rem;
  font-family: ui-monospace, monospace; border: 1px solid transparent;
}
.key.unused { opacity: .35; }
.key.shaky { border-color: var(--bad); }

.fingers, .worst { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .35rem; }
.fingers li { display: grid; grid-template-columns: 8rem 1fr auto; align-items: center; gap: .6rem; }
.fingers .name { font-size: .85rem; }
.worst li { font-size: .9rem; }

/* ── Flashcards ───────────────────────────────────────────────────────────*/
.card-face {
  display: grid; place-items: center; gap: .6rem; min-height: 13rem; cursor: pointer;
  padding: 1.5rem; text-align: center;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}
.card-face.back { border-color: var(--accent); }
.card-face p { font-size: clamp(1.1rem, 3.5vw, 1.7rem); margin: 0; }

.boxes { display: flex; gap: 2px; margin: .4rem 0; }
.box {
  flex: 1; text-align: center; padding: .5rem .3rem; background: var(--sunken);
  border-radius: var(--radius-sm); min-width: 3rem;
}
.box.learned { background: color-mix(in srgb, var(--ok) 20%, var(--sunken)); }
.box b { display: block; font-size: 1.1rem; font-variant-numeric: tabular-nums; }
.box span { font-size: .62rem; color: var(--muted); }

.cards-list { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .3rem; }
.card-row {
  display: grid; grid-template-columns: 1fr 1fr auto auto; gap: .5rem; align-items: center;
  padding: .35rem .5rem; border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: .85rem;
}
.card-row .back { color: var(--muted); }
.card-row .front, .card-row .back { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The printed sheets exist only on paper. On screen they would be a second
   copy of the deck nobody asked to see. */
.print-only { display: none; }
@media print {
  body > *:not(.print-only) { display: none !important; }
  .print-only { display: block; }
  .sheet {
    display: grid; gap: 4mm; page-break-after: always;
    width: 190mm; min-height: 270mm; padding: 5mm;
  }
  .print-card {
    display: grid; place-items: center; text-align: center; padding: 4mm;
    border: 1px dashed #999; font-size: 12pt; min-height: 30mm;
  }
}

/* Transcript cues, with the unreadable ones marked. */
.cues { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .25rem;
  max-height: 22rem; overflow-y: auto; }
.cue { display: grid; grid-template-columns: 5.5rem 1fr auto; gap: .6rem; align-items: baseline;
  padding: .3rem .5rem; border-radius: var(--radius-sm); font-size: .88rem; }
.cue:nth-child(odd) { background: var(--sunken); }
.cue.fast { box-shadow: inset 3px 0 0 var(--bad); }
.drop.has-file { border-style: solid; border-color: var(--accent); }

/* The limits a counter is actually being used against. */
.limits { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .4rem; max-width: 34rem; }
.limit { display: grid; grid-template-columns: 10rem 1fr 6rem; gap: .7rem; align-items: center; font-size: .85rem; }
.limit .left { text-align: right; font-variant-numeric: tabular-nums; }
.limit.over .meter span { background: var(--bad); }
.limit.over .left { color: var(--bad); }

/* Two actions on a result row — save it, or go back to where it came from. */
.row-actions { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }

/* A QR code shown full screen, for scanning across a room.
   The code takes the shorter edge so it is square and as large as the display
   allows, and the light colour is painted behind it — a code on a dark page
   at a small size is the one that will not scan. */
.qr-full { display: grid; place-items: center; width: 100%; height: 100%; }
.qr-full svg { width: min(92vw, 92vh) !important; max-width: none !important; height: auto; }

/* Two controls that belong on one line: a checkbox and the colour it enables,
   a slider and the button that clears it. */
.inline { display: flex; align-items: center; gap: .5rem; }
.inline input[type="range"] { flex: 1; min-width: 0; }

/* The crop box takes focus for keyboard nudging, so it needs to show that it
   has it — without a ring it is impossible to tell whether the arrows will
   move the crop or scroll the page. */
.crop-box:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Comparing a result with its original ─────────────────────────────────
   Three stacked layers in one frame, so the same pixels sit in the same place
   in every view and the eye compares rather than remembers. Which layer shows
   is decided by clip-path rather than by moving anything, because a layout
   that shifts between views makes the comparison worthless. */
.cmp-pane {
  position: relative; overflow: hidden; border-radius: var(--radius-sm);
  background: repeating-conic-gradient(var(--line) 0 25%, transparent 0 50%) 0 0 / 18px 18px;
  min-height: 14rem; touch-action: none; --cmp-split: 50%; --cmp-scale: 1;
  --cmp-pan-x: 0px; --cmp-pan-y: 0px;
}
.cmp-pane > img, .cmp-pane > canvas {
  display: block; width: 100%; height: auto;
}
.cmp-a, .cmp-b, .cmp-diff { position: absolute; inset: 0; margin: auto; }

/* Split: the result is clipped from the left, revealing the original beneath
   it. One frame, one position, a divider you move. */
.cmp-pane[data-view="split"] .cmp-b { clip-path: inset(0 0 0 var(--cmp-split)); }
.cmp-pane[data-view="split"] .cmp-diff { display: none; }

/* Side by side: a fixed half each, with a hairline between. No interaction —
   there is nothing to drag, which is the difference from split. */
.cmp-pane[data-view="side"] .cmp-a { clip-path: inset(0 50% 0 0); }
.cmp-pane[data-view="side"] .cmp-b { clip-path: inset(0 0 0 50%); }
.cmp-pane[data-view="side"] .cmp-diff { display: none; }
.cmp-pane[data-view="side"]::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; background: rgba(255, 255, 255, .7); pointer-events: none;
}

.cmp-pane[data-view="diff"] .cmp-a,
.cmp-pane[data-view="diff"] .cmp-b { display: none; }

.cmp-divider {
  position: absolute; top: 0; bottom: 0; left: var(--cmp-split);
  width: 2px; margin-left: -1px; background: #fff; cursor: ew-resize;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
}
.cmp-divider::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 28px; height: 28px; transform: translate(-50%, -50%);
  border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.cmp-divider:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Zoomed in, pixels are shown as pixels. Smoothing at 200% would blur away
   the single-pixel artefacts that are the entire reason for zooming. */
.cmp-pane[data-zoom="in"] { cursor: grab; }
.cmp-pane[data-zoom="in"]:active { cursor: grabbing; }
.cmp-pane[data-zoom="in"] > img, .cmp-pane[data-zoom="in"] > canvas {
  width: calc(100% * var(--cmp-scale)); max-width: none;
  image-rendering: pixelated;
  transform: translate(var(--cmp-pan-x), var(--cmp-pan-y));
}

.compare-shell { margin: 1.2rem 0; }

/* The held or last-captured frame, shown at the size it will be saved at
   rather than scaled to fill — a preview that stretches is a preview that
   lies about what is in the file. */
.shot-preview {
  display: block; max-width: 100%; height: auto; margin: .9rem 0 0;
  border-radius: var(--radius-sm); background: var(--sunken);
  cursor: crosshair; touch-action: none;
}
.shot-preview.frozen { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── The tools listing ────────────────────────────────────────────────────
   One page, one shape. There were two listings of the same forty-seven tools
   in two different shapes, and they had already drifted apart. */
.tools-search { margin: 1.6rem 0 .4rem; }
.tools-search input {
  width: 100%; padding: .7rem .9rem; font: inherit; font-size: 1rem;
  color: var(--fg); background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.tools-search input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent;
}
.tools-group { margin-top: 2.4rem; }
.tools-group[hidden] { display: none; }
/* The category, and how many are in it. The count is the useful half: it
   tells somebody whether a heading is worth opening before they scroll. */
.section-head {
  display: flex; align-items: baseline; gap: .6rem; margin: 0 0 .9rem;
}
.section-head h2 {
  margin: 0; font-size: 1.05rem; font-weight: 600; letter-spacing: -.01em;
}
.section-head span {
  font-size: .8rem; color: var(--muted); font-family: ui-monospace, monospace;
}
.tdir-card[hidden] { display: none; }

/* ── The tools directory, shared with the site ───────────────────────────
 *
 * These were only ever in the site's own stylesheet, because the card grid
 * was only ever rendered there. Unifying the two listings changed that: the
 * standalone index at /all/ now emits the same markup, loads THIS stylesheet,
 * and had none of the rules for it — so every card collapsed into a pile of
 * icons in the middle of the page.
 *
 * Kept as a copy rather than an import: these two stylesheets are served from
 * different places and neither can reach the other, and a copy that is
 * generated from one builder's markup is a copy both halves are told about by
 * the same test.
 */
/* ── The tools directory ───────────────────────────────────────────────────
   A list of forty-five tools is a directory, not a set of feature tiles, and
   the first attempt reused .tool for it. That was wrong in a way worth
   recording: .tool-grid is six fixed columns on 150px rows, and .tool spans
   exactly one of them. Cards carrying a real sentence overflowed their row
   and printed straight over their neighbours.

   This has its own grid with auto rows, so a card is as tall as its content
   and the row is as tall as its tallest card. */
.tools-cat {
  margin: 44px 0 14px; font-size: 1.05rem; font-weight: 600; letter-spacing: -.01em;
}

.tdir {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: 1px; background: var(--border);
  border: 1px solid var(--border); border-radius: 14px; overflow: hidden;
}

.tdir-card {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--bg-2); color: inherit; text-decoration: none;
  transition: background .2s var(--ease);
}
.tdir-card:hover { background: var(--surface); }
.tdir-card:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The tool's own diagram, lifted out of its page by the build so there is
   one illustration per tool rather than two to keep in step. Clipped to a
   fixed height so every card in a row starts at the same place. */
.tdir-stage {
  display: grid; place-items: center;
  height: 112px; padding: 14px 16px 0; overflow: hidden;
  opacity: .55; transition: opacity .2s var(--ease);
}
.tdir-card:hover .tdir-stage { opacity: .9; }
.tdir-stage svg { width: 100%; height: 100%; max-height: 98px; }
/* Tools with no diagram yet show their icon instead, at the same height —
   a row where one card is empty and its neighbours are not reads as a
   loading fault rather than as a difference. */
.tdir-glyph { display: grid; place-items: center; }
.tdir-glyph svg { width: 34px; height: 34px; max-height: none; color: var(--dim); }

.tdir-body { display: block; padding: 4px 16px 16px; min-width: 0; }
.tdir-name {
  display: flex; align-items: center; gap: 8px;
  font-size: .92rem; font-weight: 600; letter-spacing: -.01em; color: var(--text);
}
.tdir-name .ti { width: 15px; height: 15px; flex: 0 0 15px; color: var(--dim); }
.tdir-card:hover .tdir-name .ti { color: var(--accent); }
/* Clamped rather than left to run: these are one-line summaries and a card
   three times its neighbour's height for the sake of a long sentence makes
   the grid unreadable. */
.tdir-what {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; margin-top: 6px;
  font-size: .8rem; line-height: 1.5; color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .tdir-stage svg * { animation: none !important; }

}

/* Compress PDF: a small label where an image thumbnail would sit. */
.pdf-badge { width: 3.5rem; height: 3.5rem; display: grid; place-items: center; border-radius: 6px;
  background: var(--sunken); border: 1px solid var(--line); font-size: .7rem; font-weight: 600;
  letter-spacing: .06em; color: var(--muted); }

/* ── Password generator ────────────────────────────────────────────────────
 * The result is the point of the page, so it is large, monospaced and wraps
 * anywhere: a 64-character password has no spaces to break at. The meter is
 * a bar and a sentence, never a colour alone. */
.pw-body { padding: 1rem .9rem 1.1rem; }
.pw-out { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; }
.pw-value { flex: 1 1 18rem; min-height: 2.6rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(1.05rem, 2.6vw, 1.35rem); line-height: 1.4; overflow-wrap: anywhere; word-break: break-all;
  padding: .6rem .8rem; background: var(--sunken); border: 1px solid var(--line); border-radius: 10px; color: var(--fg); }
.pw-buttons { display: flex; gap: .5rem; }
.pw-meter { height: 8px; margin: 1rem 0 .5rem; border-radius: 99px; background: var(--sunken); overflow: hidden; }
.pw-meter span { display: block; height: 100%; width: calc(var(--fill, 0) * 100%); border-radius: inherit;
  background: var(--bad); transition: width .25s ease, background-color .25s ease; }
.pw-meter[data-level="1"] span { background: color-mix(in srgb, var(--bad) 45%, var(--ok)); }
.pw-meter[data-level="2"] span, .pw-meter[data-level="3"] span { background: var(--ok); }
.pw-options { gap: .6rem 1.2rem; }

/* ── Note taker ────────────────────────────────────────────────────────────
 * Four screens in one panel: setup, recording, working, result. Plain on
 * purpose: the controls are the ones every other tool here uses, the record
 * button is the one large thing, and the transcript reads like a document.
 * Speaker colours mark a line's edge and a dot, never its text, which stays
 * in the page's own ink so it reads in both themes. */
.nt { padding: 1.1rem; }
.nt .s0 { --who: #3b7dd8; } .nt .s1 { --who: #c2702a; } .nt .s2 { --who: #2f9a6a; }
.nt .s3 { --who: #a24fc0; } .nt .s4 { --who: #c24a5a; } .nt .s5 { --who: #7a8a2a; }

.nt-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1rem; }
.nt-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr)); gap: .9rem 1.1rem; margin: 1.1rem 0 .4rem; }
.nt-field { display: flex; flex-direction: column; gap: .35rem; font-size: .85rem; color: var(--muted); }
.nt-field select { width: 100%; }
.nt-inline { flex-direction: row; align-items: center; gap: .5rem; }
.nt-inline select { width: auto; max-width: 100%; }
.nt-switch { display: inline-flex; align-items: center; gap: .55rem; cursor: pointer; }
.nt-consent { margin: .9rem 0 0; color: var(--fg); }
.nt-start { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; margin-top: 1.2rem; }
.act.nt-big { font-size: 1.05rem; padding: .85rem 1.5rem; border-radius: 999px; display: inline-flex; align-items: center; gap: .6rem; }
.act.nt-big:disabled { opacity: .45; cursor: not-allowed; }
.nt-dot { width: .7rem; height: .7rem; border-radius: 50%; background: #d23b3b; display: inline-block; flex: none; }

.nt-live-top { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem 1.4rem; }
.nt-rec { display: inline-flex; align-items: center; gap: .45rem; font-weight: 600; font-size: .9rem; }
.nt-rec .nt-dot { animation: nt-blink 1.2s ease-in-out infinite; }
.nt-rec.paused .nt-dot { animation: none; background: var(--faint); }
@keyframes nt-blink { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .nt-rec .nt-dot { animation: none; } }
.nt-time { font-size: clamp(2rem, 7vw, 3rem); font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.nt-level { flex: 1 1 8rem; height: .5rem; border-radius: 99px; background: var(--sunken); overflow: hidden; min-width: 6rem; }
.nt-level span { display: block; height: 100%; width: 0; background: var(--ok); transition: width .08s linear; }
.nt-live-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: 1rem 0; }
.nt-live-actions #stop { margin-left: auto; }
.nt-live-actions kbd { font-size: .75rem; padding: 0 .3rem; border: 1px solid var(--line); border-radius: 4px; margin-left: .3rem; color: var(--muted); }
.nt-note-in { display: flex; flex-direction: column; gap: .4rem; }
.nt-note-in input { width: 100%; font-size: 1rem; }
.nt-notes { list-style: none; padding: 0; margin: .7rem 0; max-height: 12rem; overflow: auto; font-size: .9rem; }
.nt-notes li { padding: .3rem 0; border-bottom: 1px solid var(--line); }
.nt-notes b { font-variant-numeric: tabular-nums; color: var(--muted); font-weight: 600; margin-right: .3rem; }

.nt-steps { list-style: none; padding: 0; margin: 0 0 1rem; display: grid; gap: .45rem; }
.nt-steps li { color: var(--faint); padding-left: 1.5rem; position: relative; }
.nt-steps li::before { content: ""; position: absolute; left: .2rem; top: .45em; width: .6rem; height: .6rem; border-radius: 50%; border: 2px solid currentColor; }
.nt-steps li.now { color: var(--fg); font-weight: 600; }
.nt-steps li.now::before { background: var(--accent); border-color: var(--accent); }
.nt-steps li.done { color: var(--muted); }
.nt-steps li.done::before { background: var(--ok); border-color: var(--ok); }

.nt-result-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; justify-content: space-between; }
.nt-people { display: flex; flex-wrap: wrap; gap: .4rem; }
.nt-person, .nt-person-edit { font: inherit; font-size: .88rem; padding: .3rem .7rem .3rem .55rem; border-radius: 99px;
  border: 1px solid var(--line); background: var(--panel); color: var(--fg); cursor: pointer;
  box-shadow: inset 3px 0 0 var(--who); }
.nt-person:hover { border-color: var(--who); }
.nt-person-edit { cursor: text; width: 10rem; }
.nt-result-bar input[type=search] { flex: 1 1 12rem; max-width: 20rem; }
.nt audio { width: 100%; margin: .9rem 0 .3rem; }
.nt-follow { font-size: .85rem; }
.nt-lines { list-style: none; padding: 0; margin: .8rem 0 0; }
.nt-lines li { display: grid; grid-template-columns: auto auto 1fr; gap: .15rem .7rem; align-items: start;
  padding: .55rem .6rem; border-left: 3px solid transparent; border-radius: 4px; }
.nt-lines li.nt-line { border-left-color: var(--who); }
.nt-lines li.now { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.nt-lines li.nt-mark, .nt-lines li.nt-note { grid-template-columns: auto 1fr; color: var(--muted); font-size: .9rem; }
.nt-lines li.nt-mark p { font-weight: 600; color: var(--fg); }
.nt-lines p { margin: 0; }
.nt-t { font: inherit; font-size: .8rem; font-variant-numeric: tabular-nums; color: var(--muted); background: none; border: 0; padding: .15rem 0; cursor: pointer; }
.nt-t:hover { color: var(--accent); text-decoration: underline; }
.nt-who { font: inherit; font-size: .85rem; font-weight: 600; border: 0; background: transparent; color: var(--fg); padding: .1rem 0; max-width: 11rem; cursor: pointer; }
.nt-text { line-height: 1.55; outline: none; border-radius: 3px; }
.nt-text:focus { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); background: var(--panel); }
@media (max-width: 560px) {
  .nt-lines li.nt-line { grid-template-columns: auto 1fr; }
  .nt-lines li.nt-line .nt-text { grid-column: 1 / -1; }
  .nt-live-actions #stop { margin-left: 0; width: 100%; }
}

.nt-export { margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--line); display: grid; gap: .7rem; }
.nt-export-row { display: flex; flex-wrap: wrap; align-items: center; gap: .55rem; }
.nt-export .notice { margin: 0; }
.nt-end { justify-content: space-between; }
.nt-more { margin-top: .9rem; font-size: .85rem; color: var(--muted); }
.nt-more summary { cursor: pointer; width: max-content; }
.nt-more[open] summary { margin-bottom: .5rem; }
.nt-more .stat { margin: .3rem 0 0; max-width: 40rem; }
.nt-private-chip { font-size: .8rem; color: var(--muted); border: 1px solid var(--line); border-radius: 99px; padding: .15rem .6rem; }
/* The player stays in reach while the transcript scrolls: sticky under the
   host site's fixed header (66px there; nothing to clear on its own site). */
.nt-player { position: sticky; top: 72px; z-index: 5; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .6rem;
  margin: .9rem 0 .4rem; padding: .55rem .6rem; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-shadow: var(--shadow); }
.nt-player #seek { flex: 1 1 10rem; min-width: 6rem; }
.nt-player output { font-size: .82rem; font-variant-numeric: tabular-nums; color: var(--muted); white-space: nowrap; }
.nt-player .act { padding: .4rem .75rem; }
.nt-player #pp { min-width: 4.6rem; }
.nt-player .nt-follow { font-size: .82rem; }
.nt-howto { margin: .2rem 0 0; font-size: .82rem; }
.nt-howto kbd { font-size: .75rem; padding: 0 .3rem; border: 1px solid var(--line); border-radius: 4px; }
@media (max-width: 560px) {
  .nt-player { top: 62px; }
  .nt-player #seek { order: 10; flex-basis: 100%; }
}
.nt-tabs { margin: .9rem 0 .2rem; }
.nt-notes-pane, #transcript-pane { margin-top: .6rem; }
.nt-notes-start { padding: 1rem; border: 1px dashed var(--line-strong); border-radius: var(--radius-sm); }
.nt-notes-start p:first-child { margin-top: 0; }
.nt-notes-start .stat { margin-bottom: 0; }
.nt-sec { margin: 1.1rem 0; }
.nt-sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem;
  border-bottom: 1px solid var(--line); padding-bottom: .3rem; }
.nt-sec h3 { font-size: 1rem; margin: 0; letter-spacing: .02em; text-transform: uppercase; }
.nt-sec h4 { font-size: .85rem; color: var(--muted); font-weight: 600; margin: .8rem 0 .2rem; }
.nt-sec ul { list-style: none; padding: 0; margin: .3rem 0 0; }
.nt-sec li { padding: .45rem 0 .45rem .8rem; border-left: 2px solid var(--line); margin: .3rem 0; }
.nt-sec li.flagged { border-left-color: #c28a00; }
.nt-point { margin: 0; font-weight: 500; line-height: 1.45; outline: none; border-radius: 3px; }
.nt-point:focus { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); }
.nt-src { margin: .15rem 0 0; font-size: .82rem; color: var(--muted); display: flex; gap: .5rem; align-items: baseline; }
.nt-src span { min-width: 0; }
.nt-flag { margin: .25rem 0 0; font-size: .82rem; color: #9a6700; font-weight: 600; }
.act.small { padding: .25rem .6rem; font-size: .8rem; }

/* ── Note taker on a phone ──
   Held in one hand, often mid-conversation: every control is a thumb-sized
   target, the two actions that matter (Record, Stop) span the width, and
   nothing scrolls sideways. */
.nt .seg { flex-wrap: wrap; }
@media (max-width: 640px) {
  .nt { padding: .8rem; }
  .nt-start { flex-direction: column; align-items: stretch; }
  .nt-start > .stat { text-align: center; }
  .act.nt-big { justify-content: center; width: 100%; }
  .nt .act, .nt select, .nt input[type=text], .nt input[type=search] { min-height: 44px; }
  .nt .seg button { min-height: 40px; }
  .nt-live-actions .act { flex: 1 1 auto; }
  .nt-time { font-size: 3.2rem; }
  .nt-note-in input { font-size: 16px; } /* 16px stops iOS zooming into the field */
  .nt-player { gap: .4rem; padding: .45rem; }
  .nt-player .act { flex: 1 1 auto; }
  .nt-player select { flex: 0 0 auto; }
  .nt-t { padding: .5rem .2rem; font-size: .85rem; }
  .nt-export-row .act { flex: 1 1 8rem; }
  .nt-inline { flex-wrap: wrap; width: 100%; }
  .nt-inline select { width: 100%; }
  .nt-end { flex-direction: column; align-items: stretch; }
  .nt-result-bar input[type=search] { max-width: none; }
  .nt-src { flex-wrap: wrap; }
}
.nt-times { margin: 0 0 .6rem; font-size: .82rem; }
.nt-t small { color: var(--faint); font-size: .75rem; margin-left: .25rem; }
.nt-save { padding: .9rem; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); display: grid; gap: .7rem; }
.nt-what { border: 0; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; }
.nt-what legend { font-size: .85rem; color: var(--muted); padding: 0; margin-bottom: .4rem; width: 100%; }
.nt-what input:disabled + * , .nt-what label:has(input:disabled) { opacity: .55; }
.nt-save .stat { margin: 0; }
/* Writing the notes: a pulse, a running clock, a bar that moves even within
   a part, and the words themselves arriving. */
.nt-notes-work { padding: 1rem; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.nt-working { display: flex; align-items: center; gap: .6rem; font-weight: 600; }
.nt-pulse { width: .65rem; height: .65rem; border-radius: 50%; background: var(--accent); animation: nt-pulse 1.1s ease-in-out infinite; flex: none; }
@keyframes nt-pulse { 0%, 100% { transform: scale(.7); opacity: .5; } 50% { transform: scale(1.15); opacity: 1; } }
.nt-meter-live { margin: .8rem 0 .6rem; position: relative; }
.nt-meter-live.busy::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 45%, transparent), transparent);
  background-size: 40% 100%; background-repeat: no-repeat; animation: nt-sweep 1.4s linear infinite; }
@keyframes nt-sweep { from { background-position: -40% 0; } to { background-position: 140% 0; } }
.nt-live-line { min-height: 1.5em; margin: 0; color: var(--muted); font-size: .92rem; }
.nt-live-line:not(:empty)::after { content: "▍"; margin-left: 2px; animation: nt-blink 1s steps(1) infinite; color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .nt-pulse, .nt-meter-live.busy::after, .nt-live-line::after { animation: none; }
}
.nt-tidy { margin: .7rem 0; font-size: .9rem; }
.nt-tidy summary { cursor: pointer; color: var(--muted); width: max-content; }
.nt-tidy[open] { padding: .7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); }
.nt-tidy[open] summary { margin-bottom: .6rem; color: var(--fg); }
.nt-tidy-row { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .4rem 0; }
.nt-tidy-row input { flex: 1 1 9rem; min-width: 0; }
.nt-suggest { list-style: none; padding: 0; margin: .5rem 0 0; }
.nt-suggest li { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding: .35rem 0; border-top: 1px solid var(--line); }
.nt-suggest s { color: var(--muted); }
.nt-tidy #tidy-msg { margin: .4rem 0 0; }


/* ── The host's palette, under the names these pages use ─────────────────
 * Written by the build, not by hand: this file is the tools' own stylesheet
 * and has its own palette when it is its own site. Inside a host the two
 * would fight over the four names they share, so the palette above is removed
 * and this maps what is left onto the host's.
 *
 * The reader's theme choice therefore reaches these pages the same way it
 * reaches every other page on the site — one switch, one answer.
 */
:root {
  /* --bg, --accent, --muted and --ok are absent on purpose: the host uses
   * those exact names, so they arrive already correct. Writing
   * "--bg: var(--bg)" would be a cycle, and a custom property that refers to
   * itself is invalid at computed-value time — it would take the colour with
   * it and leave the page unpainted. */
  --fg: var(--text);
  --faint: var(--dim);
  --line: var(--border);
  --line-strong: var(--border-hi);
  --accent-fg: var(--on-accent);
  --bad: var(--danger);
  --panel: var(--bg-2);
  --panel-2: var(--surface);
  /* --surface-2, not --surface: toolmotion redefines --surface as the panel
   * colour inside .tm, so mapping --sunken onto it made every inset in every
   * diagram exactly the colour of the panel it sits in — present, and
   * invisible. --surface-2 is a host name nothing downstream shadows. */
  --sunken: var(--surface-2);
  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 8px 24px rgba(0, 0, 0, .06);
}

/* The host's header is fixed, so its own pages clear it with padding on
 * .page-head. These pages bring their own head instead, and without the same
 * clearance the title sits underneath the nav. The figure is 140px there, so
 * it is 140px here — one number, not two that drift apart. */
main.wrap { padding-top: 140px; }
