/*
 * styles.css — the page's one stylesheet: the mockup's classes, translated to logical properties.
 *
 * Where it comes from: every rule below is the corresponding rule of `gen_mocks.py`'s `CSS` block
 * (the file the approved shots were rendered from), with three deliberate changes:
 *
 * 1. **Logical properties.** `left/right` became `inset-inline-start/end`, `margin-left` became
 *    `margin-inline-start`, `text-align: left` became `text-align: start`. Arabic is a first-class
 *    interface language: `dir="rtl"` on `<html>` must mirror the whole page without a second
 *    stylesheet and without a single `[dir="rtl"]` override in a component (spec section 5).
 * 2. **Self-hosted fonts.** The mockup imported Google Fonts; the CSP here is `default-src 'self'`
 *    with no CDN, so the three families load from `assets/fonts/` below. They are variable fonts,
 *    hence the weight RANGES.
 * 3. **Tokens.** Colours, radii, spaces and type sizes come from `tokens.css`. No colour is written
 *    out in this file, so the silver theme is one attribute away.
 *
 * There is no shadow DOM. The components are light-DOM custom elements and share this stylesheet,
 * which is what lets a later task restyle the whole page from `tokens.css` without touching nine
 * component files — the "design evolves without a rebuild" the spec asks for. The cost is that class
 * names are global, so every one of them is prefixed by its screen (`.jobs-*`, `.login-*`) unless it
 * is one of the shared primitives the mockup already named (`.panel`, `.pill`, `.btn`, `.lbl`, …).
 */

/* ---- fonts (self-hosted, variable) ------------------------------------------------------------ */
@font-face {
  font-family: "Sora";
  src: url("assets/fonts/Sora.ttf") format("truetype");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Manrope";
  src: url("assets/fonts/Manrope.ttf") format("truetype");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Cairo";
  src: url("assets/fonts/Cairo.ttf") format("truetype");
  font-weight: 200 1000;
  font-style: normal;
  font-display: swap;
}

/* ---- ground ----------------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  min-height: 100vh;
  background: linear-gradient(180deg, var(--bg0) 0%, var(--bg1) 100%) no-repeat, var(--bg1);
  color: var(--text);
  font-family: var(--stack-body);
  font-size: var(--t-base);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

body::before {                            /* the mockup's top sheen */
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: var(--sheen);
  z-index: 0;
}

me-app,
me-topbar { display: block; }

me-app {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* A screen fills what the top bar leaves. Every screen element behaves the same way, so a component
 * never has to know whether it is mounted at the top level or inside another one. */
me-login,
me-jobs {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

a { color: var(--accent); }
a:hover { color: var(--accent2); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

::selection { background: var(--glow); }

/* `hidden` must actually hide. Every `display` in this file is an AUTHOR rule and therefore beats
 * the UA stylesheet's `[hidden] { display: none }` — so `.btn[hidden]` stayed visible, which is
 * exactly how a "hidden" control becomes a control nobody meant to offer. */
[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- type helpers ----------------------------------------------------------------------------- */
.sora { font-family: var(--stack-head); }
.ar { font-family: var(--stack-ar); direction: rtl; unicode-bidi: isolate; }
.muted { color: var(--dim); }
.faint { color: var(--faint); }
.mono-time { font-family: var(--stack-head); font-variant-numeric: tabular-nums; }

/* A file name, an id, a timecode: an IDENTIFIER, wrapped in `bdi(dir="ltr")` by `dom.js` so it
 * reads as it was typed on every screen, in every interface language. There is no `plaintext` rule
 * here any more — ordering a file name "by its own direction" is correct Unicode and unreadable as
 * a name (Task 16 review, round 2). */

.lbl {
  font-family: var(--stack-head);
  font-size: var(--t-s);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--faint);
  margin-block-end: var(--s2);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--s4);
}

.spacer { flex: 1; }

/* ---- primitives (mockup classes) -------------------------------------------------------------- */
.panel {
  background: var(--panel);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius);
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  padding: 5px 11px;
  color: var(--dim);
  font-size: var(--t-sm);
  background: var(--sunk-soft);
  white-space: nowrap;
  max-width: 42ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pill.on {
  border-color: var(--accent);
  color: var(--text);
}

.tag {
  font-family: var(--stack-head);
  font-size: var(--t-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
  border: var(--hairline) solid var(--hair);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  white-space: nowrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  border-radius: var(--radius-pill);
  padding: 9px 16px;
  font-family: var(--stack-body);
  font-weight: 600;
  font-size: var(--t-base);
  border: var(--hairline) solid transparent;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;                  /* the Open control is a real link, not a link-looking one */
  background: none;
  color: inherit;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}

.btn.primary {
  background: var(--accent);
  color: var(--onaccent);
  font-weight: 700;
}

.btn.primary:hover:not(:disabled) { background: var(--accent2); }

.btn.ghost,
a.btn.ghost {
  border-color: var(--hair);
  color: var(--text);
  background: var(--sunk-faint);
}

.btn.ghost:hover:not(:disabled) { border-color: var(--accent); }

.btn.quiet {
  border-color: var(--hairsoft);
  color: var(--dim);
  background: none;
}

.btn.quiet:hover:not(:disabled) { color: var(--text); border-color: var(--hair); }

.btn.danger { border-color: var(--capred); color: var(--text); background: var(--sunk-faint); }
.btn.danger:hover:not(:disabled) { background: var(--capred); }

.btn.small { padding: 6px 11px; font-size: var(--t-sm); }

.btn:disabled { opacity: .5; cursor: default; }

.dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid var(--hair);
  display: grid;
  place-items: center;
  font-size: var(--t-s);
  font-family: var(--stack-head);
  color: var(--dim);
  flex: none;
}

.dot.done { background: var(--accent); border-color: var(--accent); color: var(--onaccent); }
/* `background` is reset here as well as set in `.dot.done`: the two classes are mutually exclusive
 * by construction (`me-rail.js` says why), and this is what makes the combination safe anyway rather
 * than drawing an accent glyph on an accent fill. */
.dot.cur {
  border-color: var(--accent);
  background: transparent;
  box-shadow: 0 0 0 4px var(--glow);
  color: var(--accent);
}

.link { flex: 1; height: 1px; background: var(--hairsoft); min-width: 18px; }
.link.done { background: var(--accent); opacity: .7; }

.tile {
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-md);
  padding: 9px var(--s4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  background: var(--sunk-soft);
}

.tile.on { border-color: var(--accent); background: var(--glow); }

.check {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid var(--hair);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--onaccent);
}

.tile.on .check { background: var(--accent); border-color: var(--accent); }

.radio {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
  color: var(--dim);
}

.radio .r {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1.5px solid var(--hair);
  display: grid;
  place-items: center;
  flex: none;
}

.radio.on { color: var(--text); }
.radio.on .r::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }

.note {
  border: var(--hairline) dashed var(--hairsoft);
  border-radius: var(--radius-md);
  padding: var(--s4) var(--s5);
  color: var(--faint);
  font-size: var(--t-sm);
}

.io {
  font-family: var(--stack-head);
  font-size: var(--t-s);
  color: var(--onaccent);
  background: var(--accent);
  border-radius: var(--radius-sm);
  padding: 3px 7px;
  white-space: nowrap;
}

/* ---- form controls ---------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--s2); }

.field > label {
  font-family: var(--stack-head);
  font-size: var(--t-s);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--faint);
}

.input {
  font: inherit;
  font-size: var(--t-md);
  color: var(--text);
  background: var(--sunk-soft);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-md);
  padding: var(--s4) var(--s5);
  width: 100%;
  text-align: start;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}

.input::placeholder { color: var(--faint); }
.input:hover { border-color: var(--hair); }
.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--glow); }
.input[aria-invalid="true"] { border-color: var(--capred); }

.form-error {
  color: var(--text);
  background: color-mix(in srgb, var(--capred) 26%, transparent);
  border: var(--hairline) solid var(--capred);
  border-radius: var(--radius-md);
  padding: var(--s3) var(--s5);
  font-size: var(--t-sm);
}

/* ---- top bar ---------------------------------------------------------------------------------- */
.topbar {
  min-height: var(--topbar-h);
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s6);
  padding-inline: var(--s8);
  padding-block: var(--s2);
  flex-wrap: wrap;
  border-block-end: var(--hairline) solid var(--hairsoft);
  background: var(--sunk);
  position: relative;
  z-index: 2;
}

.brand { display: flex; align-items: center; gap: var(--s4); }
.brand img { width: 26px; height: 26px; object-fit: contain; }

.wordmark {
  font-family: var(--stack-head);
  font-weight: 600;
  font-size: var(--t-lg);
  letter-spacing: -.01em;
  background: var(--wordmark-sheen);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  white-space: nowrap;
}

.pill.langs { gap: 2px; padding: 3px 6px; }

.lang-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  height: 20px;
  line-height: 1;
  font: inherit;
  font-family: var(--stack-head);
  font-size: var(--t-sm);
  color: var(--faint);
  background: none;
  border: 0;
  border-radius: var(--radius-pill);
  padding: 0 6px;
  cursor: pointer;
}

.lang-btn:hover { color: var(--text); }
.lang-btn[aria-pressed="true"] { color: var(--text); background: var(--glow); }
/* Cairo carries a deep descender, so ع sitting in its own line box reads as BELOW the Latin caps
 * beside it. Two decisions, both measured on the delivered page at 1440x940 (the mockup's own
 * artboard), by cropping each button's client rect out of the screenshot and finding the ink rows:
 *   size:  the glyph inherits the pill's 12 px, as in the mockup (`gen_mocks.py` topbar), where it
 *          is one 12 px `.pill` for all three. At 15 px it was 12 ink rows against the mockup's 9.
 *   nudge: EN and DE ink rows 22-30, centre 26.0. ع with no nudge: rows 25-34, centre 29.5 — 3.5 px
 *          low. At -3px: rows 22-31, centre 26.5, half a pixel from the Latin line and on the safe
 *          side of it. The transform is on the GLYPH, not the button, so the lit pill stays centred.
 * Re-measure (do not re-guess) if the size, the family or the bar's height changes. */
.lang-btn.ar { font-family: var(--stack-ar); }
.lang-btn.ar .lang-glyph { display: block; transform: translateY(-3px); }
.lang-sep { color: var(--faint); opacity: .6; }

.user-chip .who { color: var(--text); }
.user-chip .lane { color: var(--faint); }

.user-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

/* ---- page frame ------------------------------------------------------------------------------- */
.page {
  flex: 1;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--s7) var(--s8) var(--s9);
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  min-height: 0;
}

.page-head {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
}

.page-title {
  font-family: var(--stack-head);
  font-size: var(--t-xl);
  font-weight: 600;
  margin: 0;
  margin-inline-end: var(--s3);
}

/* ---- login ------------------------------------------------------------------------------------ */
.login-wrap {
  flex: 1;
  display: grid;
  place-items: center;
  padding: var(--s9) var(--s8);
}

.login-card {
  width: min(420px, 100%);
  padding: var(--s9) var(--s9) var(--s8);
  display: flex;
  flex-direction: column;
  gap: var(--s6);
}

.login-heading { display: flex; flex-direction: column; gap: var(--s2); }
.login-brand { display: flex; align-items: center; gap: var(--s4); }
.login-brand img { width: 34px; height: 34px; object-fit: contain; }

.login-title {
  font-family: var(--stack-head);
  font-size: var(--t-2xl);
  font-weight: 600;
  letter-spacing: -.01em;
  margin: 0;
}

.login-sub { color: var(--dim); font-size: var(--t-sm); margin: 0; }
.login-form { display: flex; flex-direction: column; gap: var(--s5); }
.login-actions { display: flex; align-items: center; gap: var(--s4); margin-block-start: var(--s1); }
.login-actions .btn { flex: 1; }

/* ---- jobs list -------------------------------------------------------------------------------- */
.jobs-list {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.job-card {
  padding: var(--s6) var(--s7);
  display: flex;
  align-items: flex-start;
  gap: var(--s6);
  transition: border-color var(--fast) var(--ease);
}

.job-card:hover { border-color: var(--hair); }

.job-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s2); }

.job-title {
  font-family: var(--stack-head);
  font-size: var(--t-md);
  font-weight: 600;
  margin: 0;
  overflow-wrap: anywhere;
}

.job-facts {
  display: flex;
  align-items: center;
  gap: var(--s3) var(--s5);
  flex-wrap: wrap;
  color: var(--dim);
  font-size: var(--t-sm);
}

.job-facts .sep { color: var(--faint); opacity: .5; }
.job-actions { display: flex; align-items: center; gap: var(--s3); flex: none; }
.job-confirm { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.job-confirm .q { color: var(--text); font-size: var(--t-sm); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  font-family: var(--stack-head);
  font-size: var(--t-s);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  flex: none;
}

.chip .bulb { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.chip[data-state="idle"] { color: var(--state-idle); }
.chip[data-state="live"] { color: var(--state-live); border-color: var(--hair); background: var(--glow); }
.chip[data-state="live"] .bulb { animation: me-pulse 1.6s var(--ease) infinite; }
.chip[data-state="done"] { color: var(--onaccent); background: var(--state-done); border-color: var(--accent); }
.chip[data-state="bad"] { color: var(--text); border-color: var(--state-bad); background: color-mix(in srgb, var(--capred) 30%, transparent); }

@keyframes me-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .25; }
}

@media (prefers-reduced-motion: reduce) {
  .chip[data-state="live"] .bulb { animation: none; }
  * { transition-duration: 1ms !important; }
}

.jobs-empty {
  padding: var(--s9) var(--s8);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  align-items: flex-start;
}

.jobs-empty h3 { font-family: var(--stack-head); font-size: var(--t-md); font-weight: 600; margin: 0; }
.jobs-empty p { margin: 0; color: var(--dim); font-size: var(--t-sm); max-width: 52ch; }

.jobs-status { color: var(--dim); font-size: var(--t-sm); margin: 0; }
.action-error { display: flex; align-items: center; gap: var(--s4); }

/* ---- the placeholder a route without a screen shows (Task 19 replaces it) ---------------------- */
.stub { padding: var(--s8); display: flex; flex-direction: column; gap: var(--s4); align-items: flex-start; }
.stub h2 { font-family: var(--stack-head); font-size: var(--t-xl); font-weight: 600; margin: 0; }
.stub p { margin: 0; color: var(--dim); font-size: var(--t-sm); }

/* ---- the timeline (Task 17) --------------------------------------------------------------------
 *
 * Lifted from the approved mockup's `.tl-*`, `.handle`, `.section` and `.playhead` rules
 * (`docs/plans/2026-09-09-mosaic-edit-mockups/gen_mocks.py`), with the colours moved into
 * tokens.css and three things the mockup did not have to solve because it was a picture:
 *
 * 1. **`direction: ltr`, always.** A timeline is a TIME AXIS: t=0 on the left, later to the right,
 *    in English, German and Arabic alike. The page mirrors around it (`dir="rtl"` on <html>) and
 *    this one widget does not, because mirroring it would claim that time runs backwards — no
 *    editing tool anywhere makes that claim. The words inside it still read in their own direction;
 *    that is per chip, and it is `<bdi>`'s job, not this rule's.
 *    Everything positioned here therefore uses `left`/`right` rather than logical properties, on
 *    purpose: inside a fixed-direction box the physical properties ARE the honest ones.
 * 2. **The overlay's offset**, which the mockup hard-coded as `left: 106px`, is the label column
 *    plus the row gap. Both are variables here so the two can never drift apart.
 * 3. **A hit target.** A 10 px handle and a 20 px ruler are fine to look at and unusable to grab, so
 *    the handle carries an invisible ±7 px `::before` and the seek rows say so with a cursor.
 */
me-timeline {
  display: block;
  flex: none;
  direction: ltr;                     /* see (1) — the one rule this component cannot do without */
}

.tl {
  --tl-label-w: 96px;
  --tl-gap: var(--s4);
  padding: var(--s4) var(--s8) var(--s5);
  position: relative;
}

.tl-rows { display: flex; flex-direction: column; gap: 5px; position: relative; }
.tl-row { display: flex; align-items: stretch; gap: var(--tl-gap); }

.tl-label {
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  width: var(--tl-label-w);
  flex: none;
  font-family: var(--stack-head);
  font-size: 10.5px;
  color: var(--faint);
  text-transform: uppercase;
  letter-spacing: .06em;
  display: flex;
  /* Two lines on the Words row, one on every other: `flex-start` keeps them both against the start
     of the label column, and `justify-content: center` above keeps the pair vertically centred. */
  align-items: flex-start;
}

.tl-track {
  flex: 1;
  position: relative;
  background: var(--track);
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: var(--hairline) solid var(--hairsoft);   /* the token, not the mockup's literal — the
                                                      only colour literal in this stylesheet was
                                                      here, and Tasks 18-21 copy this block */
  min-width: 0;
}

/* The rows a click means "go there" on. The Words track is deliberately not one: a click there
 * is a word, and the two gestures must not share a surface. */
.tl-seek { cursor: pointer; }

/* The one track that has to say what tapping it does, in ink rather than in a tooltip — a tooltip
 * does not exist on a touch screen, and this track shows the same chips as the transcript reader,
 * where a tap marks the section instead of seeking (review round 2). */
.tl-sub {
  text-transform: none;
  letter-spacing: 0;
  font-size: 9.5px;
  opacity: .75;
}

.ruler { height: 20px; position: relative; flex: 1; min-width: 0; }
.tick { position: absolute; top: 6px; bottom: 0; border-left: var(--hairline) solid var(--hairsoft); }
.tick > bdi {
  position: absolute;
  top: -6px;
  left: 5px;
  font-family: var(--stack-head);
  font-size: var(--t-xs);
  color: var(--faint);
  white-space: nowrap;
}

.film { height: 54px; }
.film-canvas { display: block; width: 100%; height: 100%; opacity: .88; filter: saturate(.85); }

.wave { height: 40px; color: var(--dim); }
.wave svg { display: block; width: 100%; height: 100%; opacity: .75; }

.words { height: 68px; }
.track-pending {
  position: absolute;
  left: var(--s5);
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--t-s);
  color: var(--faint);
  opacity: .7;
}

/* A word chip. It is a real <button> (a pointer target), but out of the tab order — 90 of them
 * would bury every other control on the screen; the keyboard path is the handles and Task 18's
 * transcript reader. */
.w {
  position: absolute;
  font-family: var(--stack-body);
  font-size: var(--t-sm);
  line-height: 15px;
  height: 19px;
  padding: 2px 6px;
  border: 0;
  border-radius: 5px;
  background: var(--tl-chip);
  color: var(--dim);
  white-space: nowrap;
  cursor: pointer;
  max-width: 100%;
  overflow: hidden;
}

.w:hover { color: var(--text); background: var(--glow); }
.w.sel { background: var(--glow); color: var(--text); border: var(--hairline) solid var(--hair); padding: 1px 5px; }
/* Spec section 5, step 2: the transcript is unsure about this word (p < 0.6). */
.w.low { text-decoration: underline dotted var(--accent2); text-underline-offset: 3px; }

.captrack { height: 32px; }

.section {
  position: absolute;
  top: 4px;
  bottom: 4px;
  border-radius: var(--radius-sm);
  background: var(--glow);
  border: var(--hairline) solid var(--accent);
  pointer-events: none;
}

.section .lab {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--stack-head);
  font-size: var(--t-s);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.captrack .another {
  position: absolute;
  right: var(--s5);
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--t-s);
  color: var(--faint);
  opacity: .6;
  white-space: nowrap;
  pointer-events: none;
}

/* The overlay spans exactly the tracks, so it is also what the component MEASURES to turn a
 * pointer's x into a time. (2) above: the offset is the label column plus the row gap. */
.tl-overlay {
  position: absolute;
  left: calc(var(--tl-label-w) + var(--tl-gap));
  right: 0;
  top: 0;
  bottom: 0;
  pointer-events: none;
}

.range {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--tl-range);
  border-left: var(--hairline) dashed var(--hair);
  border-right: var(--hairline) dashed var(--hair);
}

.handle {
  position: absolute;
  top: 22px;
  bottom: -4px;
  width: 10px;
  margin-left: -5px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--glow);
  pointer-events: auto;               /* the overlay itself is transparent to the pointer */
  cursor: ew-resize;
  touch-action: none;                 /* a drag on a touchscreen must not scroll the page */
}

/* (3): the grab area, ±7 px wider than the mark it draws. */
.handle::before {
  content: "";
  position: absolute;
  left: -7px;
  right: -7px;
  top: -4px;
  bottom: -4px;
}

/* The grip, from the mockup. */
.handle::after {
  content: "";
  position: absolute;
  left: 3px;
  right: 3px;
  top: 50%;
  height: 12px;
  transform: translateY(-50%);
  border-left: var(--hairline) solid var(--onaccent);
  border-right: var(--hairline) solid var(--onaccent);
  opacity: .75;
}

/* Inert while the screen is not asking for a section: still fully visible, because the section has
 * to stay READABLE on every step — it just cannot be moved (see `me-timeline.js`'s `marks-off`). */
.handle:disabled { cursor: default; }
.handle:hover:not(:disabled) { background: var(--accent2); }
.handle:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
me-timeline.dragging .handle { background: var(--accent2); }

.htag {
  position: absolute;
  top: 0;
  transform: translate(-50%, -2px);
  font-family: var(--stack-head);
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--onaccent);
  background: var(--accent);
  border-radius: 4px;
  padding: 1px 6px;
  white-space: nowrap;
}

.playhead {
  position: absolute;
  top: 18px;
  bottom: 0;
  width: 1px;
  background: var(--playhead);
  box-shadow: 0 0 6px var(--playhead-glow);
}

.playhead::before {
  content: "";
  position: absolute;
  top: -1px;
  left: -5px;
  border: 5px solid transparent;
  border-top: 7px solid var(--playhead);
}

/* ---- the player (Task 18) -----------------------------------------------------------------------
 *
 * The mockup's `.player`, `.hud`, `.caps`, `.capline`, `.ctl` and `.io`, with two changes and one
 * addition:
 *
 * 1. **The control bar is interface, so it mirrors.** Logical properties throughout; there is no
 *    rule here that mentions a language. The CAPS LAYER is the exception and is positioned
 *    physically, because it is not interface: it is the video's own frame, and a frame does not turn
 *    around when the page does. The component sets its box in pixels from the video's real rectangle
 *    (`videoBox()`), which is also why `.caps` has no `left: 0; right: 0` of its own any more.
 * 2. **The video FILLS the box and letterboxes inside it** (`position: absolute; inset: 0` +
 *    `object-fit: contain`), rather than the mockup's `height: 100%; width: auto` on an `<img>`.
 *    Two reasons, one of them measured: a landscape clip in a portrait-shaped box needs the other
 *    constraint, and `max-height: 100%` on a GRID ITEM resolves against the grid area — which an
 *    auto row sizes from the item itself, so a 478x850 video in a 671 px box came out 850 px tall
 *    and was clipped by `overflow: hidden`. Filling the box removes the circularity, and the
 *    picture's real rectangle inside it is what `videoBox()` computes from `videoWidth`.
 * 3. `.player-empty` — the mockup never had to draw a job whose upload has not finished.
 */
me-player {
  display: block;
  min-width: 0;
  min-height: 0;
}

.player {
  height: 100%;
  background: var(--videobg);
  border-radius: var(--radius);
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  border: var(--hairline) solid var(--hairsoft);
}

.player .hero {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: var(--videobg);
}

/* Positioned, so it paints above the absolutely positioned video: it is shown either when there is
 * no source at all or when the browser could not play the one there is, and the second case is drawn
 * over a black rectangle. */
.player-empty {
  position: relative;
  margin: 0;
  color: var(--faint);
  font-size: var(--t-sm);
  padding: var(--s8);
  text-align: center;
}

.hud {
  position: absolute;
  inset-inline-start: var(--s6);
  top: var(--s5);
  font-family: var(--stack-head);
  font-size: var(--t-sm);
  color: var(--onvideo);
  opacity: .9;
  pointer-events: none;
}

.hud.r {
  inset-inline-start: auto;
  inset-inline-end: var(--s6);
  font-size: var(--t-s);
  color: var(--faint);
}

/* The caption preview. Its box is the VIDEO's rectangle, set in pixels by the component; the lines
 * inside it are placed by the renderer's own percentages (see `me-player.js`). Not a control: it
 * never takes a pointer, so Task 20's twin can be laid over it without fighting for the drag. */
.caps {
  position: absolute;
  pointer-events: none;
}

/* `transform` does two things, and the vertical half is the renderer's own behaviour rather than a
 * nudge: `placement.twin_html`'s `.capwrap` is bottom-pinned and ZERO-HEIGHT, so a caption hangs
 * DOWNWARD from its rail's line. `translateY(100%)` puts the line's top on the anchor, which is why
 * a wrapped lower rail grows away from the rail above it instead of into it. */
.capline {
  position: absolute;
  transform: translate(-50%, 100%);
  font-family: var(--stack-head);
  font-weight: 600;
  color: var(--capsilver);
  text-shadow: var(--capshadow);
  text-align: center;
  line-height: 1.3;
  white-space: normal;
}

.capline.rtl { font-family: var(--stack-ar); font-weight: 700; line-height: 1.5; }
.capline b { color: var(--capred); font-weight: inherit; }

.ctl {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 46px;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: 0 var(--s6);
  background: linear-gradient(180deg, transparent, var(--scrim));
}

.ctl .ic {
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--onaccent);
  display: grid;
  place-items: center;
  flex: none;
  cursor: pointer;
  padding: 0;
}

.ctl .ic:hover { background: var(--accent2); }

/* The reason the two Set buttons are inert, with them rather than on the card: the convention is
 * that a disabled control carries its own reason, and these two live on the video. Its own line
 * directly above the bar, because the bar is full at the player's width (see `me-player.js`). */
.ctl-note {
  position: absolute;
  inset-inline-end: var(--s6);
  bottom: 46px;                       /* the control bar's height: immediately above it */
  font-size: var(--t-s);
  color: var(--onvideo);
  opacity: .85;
  white-space: nowrap;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .9);
  pointer-events: none;
}

.ctl .tc {
  font-family: var(--stack-head);
  font-size: var(--t-sm);
  color: var(--onvideo);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- the transcript (Task 18) -------------------------------------------------------------------
 *
 * The mockup's `transcript_reader()`, with the words as real buttons rather than spans — they are
 * the spec's "tap the first and the last word", so they are controls and they say so to a pointer
 * and to a keyboard alike.
 *
 * The list carries the TRANSCRIPT's direction (the component sets `dir` on `.tr-list`), which is
 * what puts the timecode on the right of an Arabic transcript and on the left of a Latin one with no
 * rule below mentioning either: the timecode is simply the first thing in the row. Everything here
 * is therefore written in LOGICAL properties again — this box mirrors, it just mirrors on the
 * transcript's word rather than the interface's.
 */
me-transcript {
  display: block;
  overflow: auto;
  overscroll-behavior: contain;
  min-height: 0;
}

.tr-list { display: flex; flex-direction: column; gap: 5px; }

.tr-row { display: flex; gap: var(--s5); align-items: flex-start; }

.tr-time {
  font-family: var(--stack-head);
  font-size: var(--t-s);
  color: var(--faint);
  direction: ltr;                    /* a timecode is a coordinate: `dom.js` isolates it, this keeps
                                        the COLUMN left-to-right so 0:07.8 never wraps oddly */
  flex: none;
  width: 44px;
  padding-top: 9px;
  font-variant-numeric: tabular-nums;
}

.tr-text {
  font-size: var(--t-xl);
  line-height: 1.9;
  padding: 5px var(--s5);
  border-radius: var(--radius-md);
  border: var(--hairline) solid transparent;
  color: var(--dim);
  min-width: 0;
}

/* The mockup's highlighted segment: this sentence is entirely inside the section. */
.tr-row.sel .tr-text { background: var(--glow); border-color: var(--hair); color: var(--text); }

/* A word. It is a button that looks like the text it is, because a transcript a worker cannot read
 * is not a transcript. */
.tw {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0 2px;
  margin: 0;
  border-radius: 5px;
  cursor: pointer;
  text-align: inherit;
}

.tw:hover { color: var(--text); background: var(--glow); }
/* Inside the section — the run's own test, so what lights up is what will be captioned. */
.tw.sel { color: var(--text); }
/* The two the taps chose: the first word and the last one, marked the way the IN/OUT chips are. */
.tw.edge { background: var(--accent); color: var(--onaccent); }
/* Spec section 5, step 2: the transcript is unsure about this word (p < 0.6). */
.tw.low { text-decoration: underline dotted var(--accent2); text-underline-offset: 4px; }

.tr-pending { margin: 0; color: var(--faint); font-size: var(--t-sm); opacity: .8; }


/* ---- the Guided Studio (Task 19) -----------------------------------------------------------------
 *
 * The mockup's own frame (`gen_mocks.py`, `guided_studio()`): a 210 px rail, the player taking what
 * is left, a 600 px step card, and the full timeline across the bottom. Three notes:
 *
 * 1. **The rail and the card are the only fixed widths on the screen.** The player is what stretches,
 *    because it is the picture -- and because a beginner's eye goes to the video first.
 * 2. **Everything scrolls INSIDE the card**, never the page: the timeline must stay in view while a
 *    section is being marked, which is the whole point of board 4. Hence the `min-height: 0` chain
 *    from `me-job` down to `me-transcript` -- one missing link and a long transcript pushes the
 *    timeline off the bottom of the window.
 * 3. **`.tile` and `.radio` are BUTTONS here**, not the mockup's divs, so they carry the usual
 *    control reset. They were always controls; the picture just could not say so.
 */
me-job {
  display: flex;
  flex-direction: column;
  flex: none;
  /* The screen IS the window, and that is a requirement rather than a nicety: board 4's promise is
   * that the player and the full timeline stay in view while a section is being marked (spec
   * section 5). With `min-height` alone the column simply grows to its content and the timeline
   * goes below the fold -- measured in the walk at a viewport of 845 px, where the Edits track
   * started at y = 879 and a drag aimed at it landed outside the window and did nothing.
   * `--topbar-h` plus its hairline is what the bar above takes. */
  height: calc(100vh - var(--topbar-h) - var(--hairline));
  min-height: 0;
}

.studio {
  flex: 1;
  display: flex;
  gap: var(--s7);
  padding: var(--s6) var(--s8) 0;
  min-height: 0;
}

/* The player and the placement layer share ONE cell: the twin is drawn on the picture, and the
 * picture is inside the player. Positioned, so the layer can be laid over it in pixels. */
.studio-stage {
  flex: 1;
  position: relative;
  min-width: 0;
  min-height: 0;
  display: flex;
}

.studio-stage > me-player { flex: 1; min-width: 0; }

/* ---- the step rail ------------------------------------------------------------------------------ */
me-rail {
  width: 210px;
  flex: none;
  display: flex;
  min-height: 0;
}

.rail {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: var(--s2) var(--s1);
  min-height: 0;
}

.rail-step {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: 9px 0;
  font: inherit;
  font-size: var(--t-base);
  text-align: start;
  background: none;
  border: 0;
  color: var(--faint);
  width: 100%;
  border-radius: var(--radius-sm);
}

button.rail-step { cursor: pointer; }
button.rail-step:hover { color: var(--text); }
.rail-step[data-state="done"] { color: var(--dim); }
.rail-step[data-state="current"] { color: var(--text); font-weight: 600; }
.rail-name { min-width: 0; }

.rail-rule {
  border-top: var(--hairline) solid var(--hairsoft);
  margin: var(--s4) 0 var(--s2);
}

.rail-later {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s3) 0;
  color: var(--faint);
  opacity: .6;
  font-size: var(--t-sm);
}

.rail-pipeline { margin-top: auto; padding-block-start: var(--s5); }
.rail-pipeline .lbl { margin-bottom: var(--s1); }
.rail-pipeline-body { font-size: var(--t-sm); line-height: 1.6; }
.rail-pipeline-sub { display: block; opacity: .6; }
.lock { flex: none; }

/* ---- the step card ------------------------------------------------------------------------------ */
me-step-card {
  width: 600px;
  flex: none;
  display: flex;
  min-height: 0;
}

.step-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding: var(--s7) var(--s8);
  min-height: 0;
}

.step-head { display: flex; flex-direction: column; gap: var(--s1); }

.step-title {
  margin: 0;
  font-family: var(--stack-head);
  font-size: var(--t-2xl);
  font-weight: 600;
  letter-spacing: -.01em;
  display: flex;
  align-items: baseline;
  gap: var(--s2);
}

.step-dot { color: var(--faint); }
.step-help { margin: 0; font-size: 12.5px; }

.step-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.step-rows {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

.step-slot { display: flex; flex-direction: column; gap: var(--s4); }
/* An empty slot must take no room at all, or step 3's reader loses its share of the card to three
 * invisible boxes. */
.step-slot:empty { display: none; }
.step-content { flex: 0 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; }

/* Hidden rather than removed when the step is not 3: detaching the reader would rebuild every word
 * button and lose the keyboard's place (see `me-job.js`). `[hidden]` above does the hiding. */
.step-reader { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.step-reader > me-transcript { flex: 1; min-height: 0; }

.step-foot {
  display: flex;
  align-items: center;
  gap: var(--s4);
  border-top: var(--hairline) solid var(--hairsoft);
  padding-top: var(--s5);
}

.step-facts { min-width: 0; }
.step-facts-inner { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.step-count { font-size: var(--t-sm); }

/* Why the next action is refused, in the footer's facts row beside the button it disables.
 * It must not read as a continuation of the muted summary it sits next to — which is what an
 * unstyled span did, inheriting body colour at the summary's size (review round 3) — so it takes
 * the accent and a rule of its own. */
.step-why {
  font-size: var(--t-sm);
  color: var(--accent2);
  border-inline-start: var(--hairline) solid var(--hair);
  padding-inline-start: var(--s3);
}

.step-line { margin: 0; font-size: 12.5px; color: var(--dim); }
.step-hint { margin: 0; font-size: var(--t-sm); }
.step-sent { color: var(--accent); }
.step-block { display: flex; flex-direction: column; gap: var(--s2); }
.step-field { gap: var(--s2); }

.step-source { display: flex; flex-direction: column; gap: 2px; }
.step-source .lbl { margin: 0; }
.step-source-name { color: var(--text); font-size: 12.5px; word-break: break-all; }
.step-source-facts { font-size: var(--t-s); }

/* The upload's own progress, which only XMLHttpRequest can report (see `api.js`). */
.step-bar {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--sunk-soft);
  overflow: hidden;
}

.step-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-pill);
  transition: inline-size var(--fast) var(--ease);
}

/* The mid-word notice -- the loudest thing on the card while it is up (spec section 5, step 3). */
.step-notice {
  border: var(--hairline) solid var(--hair);
  border-radius: var(--radius-md);
  padding: 9px 11px;
  background: var(--glow);
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.step-notice-text { margin: 0; font-size: var(--t-sm); font-weight: 600; }

/* ---- the two chips that are also inputs (the third way to mark a section) -----------------------
 *
 * There are THREE places on this screen that read "IN 0:07.8": the player's control-bar pair, the
 * two handle tags on the timeline, and these. Only these can be typed into, and the step-3
 * instruction points at them by name — so they have to be pickable out of the three by eye. The
 * label keeps the accent pill every IN/OUT chip wears; the VALUE sits in a sunken, bordered box with
 * a text cursor, which is the difference between a readout and a field. Visible at rest, not only
 * on hover: an affordance a worker has to discover by moving the mouse over it is not an affordance
 * (review round 2). The other two stay readouts, deliberately.
 */
.io-pair {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  background: var(--accent);
  color: var(--onaccent);
  border-radius: var(--radius-sm);
  padding: 2px var(--s2);
  font-family: var(--stack-head);
  font-size: var(--t-s);
  direction: ltr;                     /* "IN 0:07.8" is a label and a coordinate, never mirrored */
}

.io-label { font-weight: 700; }

.io-field {
  background: color-mix(in srgb, var(--onaccent) 14%, transparent);
  color: inherit;
  border: 1px solid color-mix(in srgb, var(--onaccent) 45%, transparent);
  padding: 1px 3px;
  font: inherit;
  width: 6ch;
  text-align: center;
  font-variant-numeric: tabular-nums;
  border-radius: 4px;
  cursor: text;
}

.io-field:hover:not(:disabled) { border-color: var(--onaccent); }
.io-field:focus {
  outline: 2px solid var(--onaccent);
  outline-offset: 1px;
  background: color-mix(in srgb, var(--onaccent) 22%, transparent);
}

.io-field:disabled { cursor: default; opacity: .55; border-style: dashed; }

/* ---- the language tiles and the output choice --------------------------------------------------- */
.tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s3); }

button.tile {
  font: inherit;
  color: var(--text);
  text-align: start;
  cursor: pointer;
  width: 100%;
}

button.tile:hover:not(:disabled) { border-color: var(--hair); }
button.tile:disabled { cursor: default; opacity: .6; }
.tile-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tile .code { font-family: var(--stack-head); font-weight: 700; font-size: var(--t-base); }
.tile .nm { font-size: var(--t-s); color: var(--dim); }
.tile-rank { font-size: var(--t-xs); font-weight: 700; line-height: 1; }

button.radio {
  font: inherit;
  font-size: 12.5px;
  background: none;
  border: 0;
  padding: 2px 0;
  text-align: start;
  cursor: pointer;
  width: 100%;
}

button.radio:hover:not(:disabled) { color: var(--text); }
button.radio:disabled { cursor: default; opacity: .55; }

/* ---- the placement layer (Task 20) ---------------------------------------------------------------
 *
 * Step 5 is the one the owner personally designed: the captions are dragged on the picture, and what
 * is dragged is the RENDERER's own stylesheet in an iframe (`placement.twin_html`), scaled from the
 * render canvas down onto the video's rectangle. So almost nothing here is a look — it is a frame,
 * a transform and two grab targets, and every colour inside the twin belongs to the identity and is
 * not reachable from this file.
 *
 * Three rules that are load-bearing rather than decorative:
 *
 * 1. **The layer takes no pointer where it is not a control.** `me-placement` and `.pl-stage` are
 *    `pointer-events: none`, and only the grab box, the corner handle and the true-frame chip take
 *    one. The player's own control bar sits under this layer and must keep working — a full-stage
 *    pointer target would swallow play/pause and Set IN/Set OUT.
 * 2. **The iframe is sized to the CANVAS and scaled**, never sized to the box: the twin's every
 *    number is a percentage of a 1080-wide render frame, and letting the browser reflow it at the
 *    player's size would be a different layout, which is the one thing a twin may not be.
 * 3. **The grab box is drawn where the captions really are** (measured inside the twin), so what a
 *    worker reaches for is the block itself, not a region that stands for it.
 */
me-placement {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;                            /* over the video and the caption layer, under nothing */
}

.pl-stage {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
  /* The captions belong to the VIDEO, not to the interface: the frame does not turn around when the
   * page does (the same rule `.caps` follows). Everything inside is positioned physically. */
  direction: ltr;
}

.pl-poster,
.pl-true {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;                      /* the stage IS the frame's aspect: no letterbox of its own */
  display: block;
}

/* `color-scheme: light` is not a look — it is what keeps the frame TRANSPARENT, and without it this
 * whole layer is a white rectangle over the worker's video (measured, in a real browser, Task 20).
 * The page declares `color-scheme: dark` (`index.html`), the twin document declares none and is
 * therefore a light-scheme document, and a frame whose scheme differs from its embedder's is given
 * an opaque backdrop by the browser so that light content stays readable on a dark page (CSS Color
 * Adjust). Saying `light` here is the honest declaration of what the framed document actually is,
 * and it is what makes the two agree. */
.pl-twin {
  position: absolute;
  left: 0;
  top: 0;
  border: 0;
  transform-origin: top left;
  background: transparent;
  color-scheme: light;
  pointer-events: none;                  /* the grab box above it owns the gesture */
}

/* The block itself: a hairline while it is left alone, the accent while it is being moved. It is a
 * button so the keyboard reaches it — arrow keys move the captions half a per cent at a time. */
.pl-grab {
  position: absolute;
  padding: 0;
  margin: 0;
  border: var(--hairline) dashed rgba(255, 255, 255, .35);
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: move;
  pointer-events: auto;
  touch-action: none;                    /* a drag is a drag, not a scroll */
}

.pl-grab:hover:not(:disabled),
.dragging .pl-grab { border-color: var(--accent); background: var(--glow); }
.pl-grab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pl-grab:disabled { cursor: default; border-style: solid; opacity: .5; }

.pl-handle {
  position: absolute;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  padding: 0;
  border: var(--hairline) solid var(--onaccent);
  border-radius: 4px;
  background: var(--accent);
  cursor: nwse-resize;
  pointer-events: auto;
  touch-action: none;
}

.pl-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The chip that says a REAL frame is on screen — on the frame it labels, and a button, because
 * flipping between the twin and the truth is the comparison the step exists for. */
.pl-chip {
  position: absolute;
  inset-inline-start: var(--s5);
  top: var(--s5);
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 4px 10px;
  border: var(--hairline) solid var(--hair);
  border-radius: var(--radius-pill);
  background: var(--scrim);
  color: var(--onvideo);
  font-size: var(--t-xs);
  cursor: pointer;
  pointer-events: auto;
}

.pl-chip .bulb {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

.pl-chip[aria-pressed="true"] { border-color: var(--accent); }
/* A frame the placement has moved under: still worth looking at, never the current answer. */
.pl-chip[data-stale] { border-color: var(--hairsoft); color: var(--faint); }
.pl-chip[data-stale] .bulb { background: var(--faint); }

/* The true frame at its own size, over the studio — a portrait clip's captions draw at about eight
 * pixels on the player's stage, and eight pixels is not something anybody can judge. Not a
 * `<dialog>`: nothing else on this page is modal, and `showModal()`'s top-layer focus move fights
 * the step card's own focus restoration. It closes three ways: the button, the backdrop, Escape. */
.overlay-slot { display: contents; }

.pl-full {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: var(--s7);
  background: rgba(0, 0, 0, .82);
}

.pl-full:focus { outline: none; }

.pl-full-close {
  position: absolute;
  inset-inline-end: var(--s7);
  top: var(--s7);
  z-index: 42;                          /* above the layer it closes */
}

/* The placement layer itself, moved over the window — the SAME element, so the twin, the variables,
 * the measurement and the grab box are the same objects and the captions can still be dragged up
 * here. Only its rectangle changes (`_stageBox`). */
me-placement.zoomed {
  position: fixed;
  inset: 0;
  z-index: 41;
}

me-placement.zoomed .pl-stage {
  border-radius: var(--radius-md);
  box-shadow: 0 24px 80px rgba(0, 0, 0, .55);
}

/* The frame the captions are judged on, copied out of the player (which the zoomed layer is no
 * longer over). Behind everything else in the stage; hidden until it has been painted. */
.pl-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ---- step 5's card ------------------------------------------------------------------------------ */
.pl-field { gap: var(--s3); }

.pl-slider { display: flex; align-items: center; gap: var(--s4); }

.pl-range {
  flex: 1;
  min-width: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.pl-range:disabled { cursor: default; opacity: .55; }

.pl-value {
  flex: none;
  min-width: 52px;
  text-align: end;
  font-family: var(--stack-head);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  color: var(--dim);
}

/* The owner's lock, said where the controls are. Quiet, but never hidden: a beginner has to be able
 * to tell what they are choosing from what the brand has already chosen. */
.step-locked { font-size: var(--t-s); color: var(--faint); }

/* ---- step 6: the summary and the live stage list ------------------------------------------------ */
.summary { display: flex; flex-direction: column; gap: var(--s3); }

.sum-row { display: flex; align-items: baseline; gap: var(--s4); }
.sum-row .lbl { flex: none; min-width: 132px; }
.sum-value { font-size: var(--t-sm); color: var(--text); min-width: 0; }

.stages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.stage {
  display: flex;
  align-items: center;
  gap: var(--s4);
  font-size: var(--t-sm);
  color: var(--dim);
}

.stage .bulb {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

.stage[data-state="live"] { color: var(--text); font-weight: 600; }
.stage[data-state="live"] .bulb { animation: me-pulse 1.6s var(--ease) infinite; }
.stage[data-state="failed"] { color: var(--capred); }
.stage-tick { color: var(--faint); width: 7px; text-align: center; flex: none; }

@media (prefers-reduced-motion: reduce) {
  .stage[data-state="live"] .bulb { animation: none; }
}

/* ---- the result screen (Task 21) -----------------------------------------------------------------
 *
 * The payoff screen, and its whole layout is one decision: **the video is the screen.** It takes the
 * wide column and everything the run has to say about itself — the cost, the notes, the three
 * actions — stands beside it in a narrower one. The panels an action opens (accept, adjust the
 * placement, request a re-edit) go UNDER both, full width, because two of them are long and none of
 * them may push the result off the top of the page.
 *
 * `me-result` renders into fixed slots rather than rebuilding its tree, so the placement stage's
 * iframe is never re-parented (re-appending one reloads the document and its brand fonts). An empty
 * slot has to take no room at all, or the page's own gap draws three invisible rows.
 */
me-result { display: flex; flex-direction: column; flex: 1; min-height: 0; }

.res-slot { display: contents; }

.res-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--s6);
  align-items: start;
}

/* …and one column when there is nothing to put in the other one (see `me-result`'s `draw`). */
.res-grid.alone { grid-template-columns: minmax(0, 1fr); }

/* The column beside the video STAYS beside it. A portrait clip is 1100 px tall on this artboard, so
 * without this the three offers — Accept, Adjust, Re-edit — scroll away the moment a worker looks at
 * the picture they are meant to be judging, and the contact sheet below pushes them further. */
.res-side {
  position: sticky;
  inset-block-start: var(--s7);
}

.res-main,
.res-side,
.res-panel {
  padding: var(--s7) var(--s8) var(--s8);
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  min-width: 0;
}

.res-h {
  font-family: var(--stack-head);
  font-size: var(--t-lg);
  font-weight: 600;
  margin: 0;
}

/* One tab per delivered video. `per_language` really is several videos, and which one is on screen
 * is the only thing on this page a worker has to pick before they can watch anything. */
.res-tabs { display: flex; flex-wrap: wrap; gap: var(--s3); }

.res-tab {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: 5px 11px;
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  background: var(--sunk-soft);
  color: var(--dim);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
}

.res-tab:hover { border-color: var(--hair); color: var(--text); }
.res-tab.on { border-color: var(--accent); background: var(--glow); color: var(--text); }
.res-tab .code { font-family: var(--stack-head); font-size: var(--t-s); letter-spacing: .04em; }
.res-tab .nm { font-size: var(--t-s); color: var(--faint); }

.res-video {
  width: 100%;
  max-block-size: 62vh;
  border-radius: var(--radius-md);
  background: var(--videobg);
  display: block;
}

.res-downloads { flex-wrap: wrap; gap: var(--s3); }
.res-downloads .btn { gap: var(--s2); }

/* Where the captions actually are. The burn writes onto the whole source, so most of a delivered
 * file has none — and this is the only place on the screen that says so. */
.res-section { color: var(--dim); font-size: var(--t-sm); }

.res-sheet-wrap { display: flex; flex-direction: column; gap: var(--s2); }

/* WHICH video the six frames are of. There is one sheet per run, made from the first video, so on a
 * two-language job the frames under the second tab carry the first one's captions. */
.res-sheet-lang {
  padding: 1px 6px;
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  font-size: var(--t-xs);
  letter-spacing: .04em;
  color: var(--accent);
}

/* Six frames from the section, whatever shape the run's own sheet came out. It is a REFERENCE, not
 * the result, so it is capped: a portrait sheet at the column's full width is 1100 px of picture
 * between the video and everything else on the page (measured in a browser, Task 21). */
/* The sheet is a link to itself at full size: six frames inside the cap below are six 90x160
 * pictures, which is a reminder rather than something anybody can look at. */
.res-sheet-link { display: block; border-radius: var(--radius-md); }
.res-sheet-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.res-sheet-link:hover .res-sheet { border-color: var(--accent); }

.res-sheet {
  max-inline-size: 100%;
  max-block-size: 320px;
  inline-size: auto;
  margin-inline: auto;
  border-radius: var(--radius-md);
  border: var(--hairline) solid var(--hairsoft);
  display: block;
}

/* ---- the column beside the video ------------------------------------------------------------- */
.res-block { display: flex; flex-direction: column; gap: var(--s2); }

.res-block + .res-block {
  border-top: var(--hairline) solid var(--hairsoft);
  padding-block-start: var(--s5);
}

.res-cost { margin: 0; font-family: var(--stack-head); font-size: var(--t-lg); color: var(--text); }

.res-flags { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s3); }

.res-flag { display: flex; align-items: baseline; gap: var(--s3); font-size: var(--t-sm); }

/* How many of this kind there were. The DETAIL never reaches this page — it is a gate's own stdout
 * (`routes/jobs.py`, `_flag_summary`) — so the count is the only number there is to show. */
.res-flag-n {
  flex: none;
  font-family: var(--stack-head);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-s);
  color: var(--accent);
  min-width: 22px;
}

.res-flag-text { color: var(--dim); }
.res-flag-at { font-size: var(--t-s); }

/* WHICH rail the run was talking about. Only the language CODE ever reaches this page. */
.res-flag-lang {
  margin-inline-start: var(--s2);
  padding: 1px 6px;
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  font-family: var(--stack-head);
  font-size: var(--t-xs);
  letter-spacing: .04em;
  color: var(--accent);
  white-space: nowrap;
}

.res-actions { gap: var(--s4); }
.res-buttons { display: flex; flex-wrap: wrap; gap: var(--s3); }
.res-buttons .btn { flex: 1 1 auto; }
.res-accepted { color: var(--accent); }

/* ---- the panels an action opens ---------------------------------------------------------------- */
.res-rows { display: flex; flex-direction: column; gap: var(--s5); }
.res-rows:empty { display: none; }

/* The free re-render, said where the button is. */
.res-free { color: var(--accent); font-size: var(--t-sm); }

/* The stage the captions are dragged on: sized in JS to the RENDER canvas's own aspect (see
 * `_layoutStage`), because `<me-placement>` is `position: absolute; inset: 0` and measures itself
 * against the box it is given. */
.res-stage {
  position: relative;
  margin-inline: auto;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--videobg);
}

.res-io { flex-wrap: wrap; gap: var(--s4); }

/* ---- the caption fix table ---------------------------------------------------------------------- */
.res-count {
  font-family: var(--stack-head);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.res-cue-block { display: flex; flex-direction: column; gap: var(--s3); margin-block-start: var(--s5); }

.res-cue-head { display: flex; align-items: baseline; gap: var(--s3); }
.res-cue-head .code { font-family: var(--stack-head); font-size: var(--t-s); color: var(--accent); }
.res-cue-head .nm { font-size: var(--t-sm); color: var(--dim); }

.res-cues {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  max-block-size: 46vh;
  overflow-y: auto;
}

.res-cue {
  display: grid;
  grid-template-columns: 54px 62px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  border: var(--hairline) solid transparent;
  border-radius: var(--radius-sm);
}

/* A line the worker has rewritten. It is the only state in this table, and it is what the count in
 * the heading counts. */
.res-cue.changed { border-color: var(--accent); background: var(--glow); }

.res-cue-n,
.res-cue-t {
  font-family: var(--stack-head);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-s);
  color: var(--faint);
}

.res-cue-text { min-width: 0; font-size: var(--t-sm); }
input.res-cue-text { padding: var(--s2) var(--s4); border-radius: var(--radius-sm); }

.res-term-label {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-s);
  color: var(--faint);
  white-space: nowrap;
  cursor: pointer;
}

/* The cue table, put away because the section moved under it: a cue's number belongs to the section
 * it was made from. Loud, like the mid-word notice, because it is the reason a control a worker was
 * about to use is not there. */
.res-cues-off {
  border: var(--hairline) solid var(--hair);
  border-radius: var(--radius-md);
  background: var(--glow);
  padding: var(--s4) var(--s5);
  color: var(--text);
  font-size: var(--t-sm);
  margin: 0;
}

/* The job state named inside a refusal — a translated word, not a code. */
.res-state { color: var(--text); font-weight: 600; }

.res-term { accent-color: var(--accent); cursor: pointer; }
.res-term:disabled { cursor: default; }
.res-term-label:has(.res-term:disabled) { opacity: .55; cursor: default; }

/* ---- the owner's inbox (Task 22, mockup shots/Inbox.png) ---------------------------------------
 * A board, not a form: a column of cards the owner works down, and a rail that says what happens to
 * the ones they approve. The geometry is the mockup's — 400 px of rail, cards on the ground, the
 * batch bar stuck to the bottom of the viewport so a selection can never scroll out of reach. */
.inbox-page { gap: var(--s5); }
.inbox-head-row { margin-block-end: 0; }
/* The rule itself, at the top of the screen it governs. Wider than a hint and quieter than a
 * heading: it is the thing to read once and remember. */
.inbox-rule { max-width: 96ch; color: var(--dim); }
.inbox-chips { flex-wrap: wrap; gap: var(--s3); }
.inbox-chips .pill { cursor: pointer; }
.inbox-chips .pill:disabled { cursor: default; opacity: .6; }
/* A count inside a chip is an identifier and reads left-to-right in every language (`bdi`); the
 * separator is the mockup's middle dot, drawn rather than written so no string carries it. */
.pill-n {
  font-family: var(--stack-head);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  opacity: .85;
}
.pill-n::before { content: "·"; opacity: .5; margin-inline-end: .4em; }
/* On the ACTIVE chip the number takes the accent, not `--onaccent`: `.pill.on` keeps the dark sunk
 * ground and only lights its border, so an on-accent number there is dark on dark — the first
 * screenshot of this board showed "New" with nothing after it (found by looking). */
.pill.on .pill-n { color: var(--accent); opacity: 1; }

.inbox-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: var(--s7);
  align-items: start;
}
.inbox-col { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
.inbox-showing { margin: 0; }
.inbox-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s4); }
.inbox-card {
  display: flex;
  gap: var(--s5);
  align-items: flex-start;
  padding: var(--s6) var(--s7);
}
.inbox-card.on { border-color: var(--accent); }
.inbox-tick { margin-block-start: 3px; accent-color: var(--accent); cursor: pointer; flex: none; }
.inbox-tick:disabled { cursor: default; }
/* An exported row has no tick, and the column still lines up: the cards are read down their left
 * edge and a missing box that shifts a card is a card that looks like a different kind of thing. */
.inbox-tick-gap { width: 13px; flex: none; }
.inbox-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s4); }
.inbox-head { gap: var(--s3); flex-wrap: wrap; }
.inbox-who { font-size: var(--t-sm); color: var(--text); }
.inbox-fact { font-size: var(--t-sm); }
.inbox-when { font-size: var(--t-s); }
.inbox-status { font-size: var(--t-s); }

/* before -> after. The struck-out half is what the run delivered; the strong half is what the
 * worker asks for. Both are isolated by `dom.js` and neither is forced into a direction here. */
.inbox-change {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s3);
  font-size: var(--t-base);
  line-height: var(--lh-read);
}
.inbox-was { color: var(--dim); text-decoration: line-through; }
.inbox-arrow { color: var(--accent); }
.inbox-now { color: var(--text); font-weight: 600; }
.inbox-reason { margin: 0; font-size: 12.5px; color: var(--dim); }
/* The owner's own wording, beside the worker's and never over it (review round 1). */
.inbox-owner-after { margin: 0; font-size: var(--t-base); line-height: var(--lh-read); }
/* A machine record in words: the sentence a non-technical owner reads instead of `30.2/50.0/1.0`. */
.inbox-gloss { display: flex; flex-direction: column; gap: 2px; }
.inbox-gloss-line { margin: 0; font-size: 12.5px; color: var(--text); }
/* What approving this kind of item does — one quiet line above the verdicts. */
.inbox-means { margin: 0; }
/* A refusal drawn inside the card whose button was pressed, where the owner is looking. */
.inbox-card .action-error { margin: 0; }
.inbox-owner-note { margin: 0; font-size: 12.5px; color: var(--text); }

/* The gate's own output. Shown to the OWNER only, and drawn as what it is: machine text, bounded,
 * scrolling inside its own box rather than stretching a card to the height of a stderr tail. */
.inbox-raw-wrap { display: flex; flex-direction: column; gap: var(--s2); }
.inbox-raw {
  font-family: var(--stack-head);
  font-size: var(--t-s);
  color: var(--dim);
  background: var(--track);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-sm);
  padding: var(--s3) var(--s4);
  max-height: 96px;
  overflow: auto;
  overscroll-behavior: contain;
  white-space: pre-wrap;
  word-break: break-word;
}

.inbox-clip { gap: var(--s4); align-items: center; flex-wrap: wrap; }
.inbox-clip-wrap { display: flex; flex-direction: column; gap: var(--s2); }
.inbox-at { font-size: var(--t-s); }
.inbox-video {
  display: block;
  width: 260px;
  max-width: 100%;
  border-radius: var(--radius-md);
  background: var(--videobg);
  border: var(--hairline) solid var(--hairsoft);
}
.inbox-actions { gap: var(--s3); flex-wrap: wrap; }
.inbox-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  border-block-start: var(--hairline) solid var(--hairsoft);
  padding-block-start: var(--s4);
}
.inbox-note { gap: var(--s2); }
.inbox-final { margin: 0; }

.inbox-rail {
  position: sticky;
  top: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding: var(--s7) var(--s8);
}
.inbox-dests { flex-wrap: wrap; gap: var(--s2); }
.inbox-rail-block {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  border-block-start: var(--hairline) solid var(--hairsoft);
  padding-block-start: var(--s5);
}
.inbox-closed { color: var(--accent); }
.inbox-queue,
.inbox-lanes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s3); }
.inbox-queue li { display: flex; align-items: baseline; gap: var(--s3); font-size: var(--t-sm); }
.inbox-queue-text {
  color: var(--dim);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inbox-lanes li { display: flex; align-items: baseline; gap: var(--s3); font-size: var(--t-sm); }
.inbox-lane-name { color: var(--text); }

/* The batch bar. Stuck to the bottom of the window, because a selection made at the top of a long
 * board must still have its buttons when the owner has scrolled to the end of it. */
.inbox-batch {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s7);
  margin-inline: calc(var(--s8) * -1);
  border-block-start: var(--hairline) solid var(--hairsoft);
  background: var(--bg1);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .35);
}

.topbar-nav,
.topbar-me { text-decoration: none; }
.topbar-nav.on,
.topbar-me.on { border-color: var(--hair); color: var(--text); }

/* ---- the accounts page (Task 24) --------------------------------------------------------------
 * The inbox's geometry, because the owner already knows it: cards down the left, a 400 px rail on
 * the right — here the rail is the "Add a person" form rather than a note about exports. */
me-admin,
me-settings { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.admin-page { gap: var(--s5); }
.admin-head-row { margin-block-end: 0; }
.admin-rule { max-width: 96ch; color: var(--dim); }
.admin-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: var(--s7);
  align-items: start;
}
.admin-col { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
.admin-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s4); }
.admin-card {
  display: flex;
  gap: var(--s6);
  align-items: flex-start;
  padding: var(--s6) var(--s7);
}
/* A disabled account is still on the list — accounts are never deleted — and reads as put away. */
.admin-card.off .admin-name,
.admin-card.off .admin-username { opacity: .6; }
.admin-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s3); }
.admin-head { gap: var(--s3); flex-wrap: wrap; }
.admin-name {
  font-family: var(--stack-head);
  font-size: var(--t-md);
  font-weight: 600;
  margin: 0;
  overflow-wrap: anywhere;
}
.admin-username { font-family: var(--stack-head); }
.admin-facts {
  display: flex;
  align-items: center;
  gap: var(--s3) var(--s5);
  flex-wrap: wrap;
  color: var(--dim);
  font-size: var(--t-sm);
}
.admin-when { font-size: var(--t-sm); }
/* Whether the account can sign in. Quieter than the job chips' "done" (a filled accent chip on every
 * row would make the list shout), louder than idle grey (which reads as "not active"). */
.admin-state {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  border: var(--hairline) solid var(--hairsoft);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  font-size: var(--t-sm);
  white-space: nowrap;
}
.admin-state .bulb { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.admin-state[data-state="on"] { color: var(--accent); border-color: var(--hair); }
.admin-state[data-state="off"] {
  color: var(--text);
  border-color: var(--capred);
  background: color-mix(in srgb, var(--capred) 30%, transparent);
}
/* The row's controls, stacked so their labels can be sentences ("Reset the password") in three
 * languages without the card growing a third column. */
.admin-actions { display: flex; flex-direction: column; align-items: stretch; gap: var(--s3); flex: none; min-width: 168px; }
.admin-own { margin: 0; font-size: var(--t-s); color: var(--dim); max-width: 22ch; }
.admin-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  border-block-start: var(--hairline) solid var(--hairsoft);
  padding-block-start: var(--s4);
}
.admin-panel.job-confirm { flex-direction: row; align-items: center; }
.admin-panel-actions { gap: var(--s4); flex-wrap: wrap; }
.admin-panel .input { max-width: 360px; }
/* The last action's outcome, in the row it happened to. The inbox card's twin (the server's
   `as_written` verdict) is the same line in the same voice. */
.admin-done, .inbox-done { margin: 0; font-size: var(--t-sm); color: var(--accent); line-height: var(--lh-read); }
.admin-card .action-error { margin: 0; }
.admin-show { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--t-sm); color: var(--dim); cursor: pointer; }
.admin-show input { accent-color: var(--accent); margin: 0; }
.admin-roles { display: flex; gap: var(--s6); flex-wrap: wrap; }
.admin-roles .radio { width: auto; }

.admin-rail {
  position: sticky;
  top: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s7) var(--s8);
}
.admin-rail-title,
.settings-heading {
  font-family: var(--stack-head);
  font-size: var(--t-lg);
  font-weight: 600;
  margin: 0;
}
.admin-form { display: flex; flex-direction: column; gap: var(--s5); margin-block-start: var(--s2); }
.admin-form .action-error { margin: 0; }

/* ---- the settings page (Task 24) --------------------------------------------------------------- */
.settings-page { gap: var(--s5); max-width: 720px; }
.settings-card { display: flex; flex-direction: column; gap: var(--s4); padding: var(--s7) var(--s8); }
.settings-langs { gap: var(--s3); flex-wrap: wrap; }
.settings-form { display: flex; flex-direction: column; gap: var(--s5); margin-block-start: var(--s2); max-width: 420px; }
.settings-form .action-error { margin: 0; }
.settings-saved { margin: 0; font-size: var(--t-sm); color: var(--accent); line-height: var(--lh-read); }
.settings-saved:focus { outline: none; }

/* ---- narrow screens (desktop first, tablet usable — spec section 1) ---------------------------- */
@media (max-width: 1180px) {
  /* Tablet: the card gives up its fixed width before the player does, because the player is the
   * picture and a shrunken video is what makes a section impossible to aim. */
  me-step-card { width: 420px; }
  .studio { gap: var(--s5); padding-inline: var(--s5); }
}

@media (max-width: 900px) {
  /* Narrower than three honest columns: the rail becomes a strip of numbers, which still says
   * where in the request you are. */
  me-rail { width: 64px; }
  .rail-name, .rail-later span, .rail-pipeline { display: none; }
  .rail-later { justify-content: center; }
  me-step-card { width: 360px; }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 980px) {
  /* The result's second column goes under the video rather than squeezing the picture: the video is
   * the thing being judged, and everything beside it is a note about it. */
  .res-grid { grid-template-columns: minmax(0, 1fr); }
  .res-cue { grid-template-columns: 46px minmax(0, 1fr); }
  .res-cue-t { display: none; }
  .res-term-label { grid-column: 1 / -1; justify-content: flex-end; }
}

@media (max-width: 720px) {
  .page { padding-inline: var(--s5); }
  .job-card { flex-direction: column; align-items: stretch; }
  .job-actions { justify-content: flex-end; }
  /* The labels are the first thing to go: the tracks are the content. */
  .tl { --tl-label-w: 0px; --tl-gap: 0px; padding-inline: var(--s5); }
  .tl-label { display: none; }
}

@media (max-width: 1180px) {
  /* The rail goes under the board before the cards get narrow: a correction the owner is reading is
     the content, and the rail is a note about what happens to it. */
  .inbox-grid { grid-template-columns: minmax(0, 1fr); }
  .inbox-rail { position: static; }
  /* …and the accounts page's form goes under its list for the same reason. */
  .admin-grid { grid-template-columns: minmax(0, 1fr); }
  .admin-rail { position: static; }
}

@media (max-width: 720px) {
  .admin-card { flex-direction: column; align-items: stretch; }
  .admin-actions { flex-direction: row; flex-wrap: wrap; min-width: 0; }
}
