/* ------------------------------------------------------------------
   Aqlama · Stereo Recording — editorial-minimal
   palette: cream paper · ink · cobalt blue · gold · moss
   type:    Fraunces (display) · Geist Sans (body) · JetBrains Mono
------------------------------------------------------------------ */

:root {
  --paper:        #f5efe4;
  --paper-elev:   #f0e8d8;
  --card:         #fdfaf2;
  --card-hover:   #fffcf3;
  --ink:          #1a1a1a;
  --ink-soft:     #2d2a23;
  --warm-1:       #5c5749;
  /* 4.66:1 on --paper-elev, the darkest surface it sits on; 4.96:1 on
     --paper, 5.44:1 on --card. It was #8a8170 = 3.36 / 3.16 / 3.69 -- under
     the 4.5:1 floor on EVERY surface, at the 9-13px sizes it is actually
     used at, on all 15 pages. admin.css found this on 2026-08-21 and fixed
     it BY SELECTOR, inside the console only, explicitly deferring the token
     "until those surfaces are rendered first" (docs/DEFERRED.md). They have
     now been rendered and measured, so this IS that deferred change. It
     stays clearly lighter than --warm-1 (5.91 on the same surface), so the
     two-step label hierarchy survives. */
  --warm-2:       #6e6657;
  --hair:         #d9d0bf;
  --hair-strong:  #b8ad96;

  --brand:        #1f4ea0;
  --brand-deep:   #163d80;
  --brand-soft:   rgba(31, 78, 160, 0.10);
  --brand-glow:   rgba(31, 78, 160, 0.25);

  --gold:         #c89414;
  /* THE STATUS COLOUR -- "pending", "retired", "unconfirmed", "too short" --
     so it is read more carefully than almost anything else on these pages,
     and at #a87a10 it was 2.84:1 at worst. Same failure as --warm-2 above,
     found in the same sweep, in eighteen places across six files.

     2.84 AND NOT 3.16, and the difference is the point: this token is nearly
     always painted ON a --gold-soft pill (rgba(200,148,20,0.14)), not on the
     bare surface, and a translucent bed is DARKER than the surface under it.
     A contrast check that walks up to the first opaque ancestor -- which is
     what the sweep did, and what most tooling does -- silently measures the
     wrong bed and reports 3.69. Every candidate here was scored against the
     COMPOSITED colour on all three surfaces; #7a570c is 4.84 at worst.

     Never a background anywhere (checked), so darkening it only ever affects
     text and borders, both of which gain. */
  --gold-deep:    #7a570c;
  --gold-soft:    rgba(200, 148, 20, 0.14);
  --gold-glow:    rgba(200, 148, 20, 0.45);

  --moss:         #4a6b3a;
  --moss-soft:    rgba(74, 107, 58, 0.10);
  --danger:       #a93226;
  --danger-soft:  rgba(169, 50, 38, 0.10);

  /* restyle 2026-10-10: the boards' in-between shades (spec 3.1) */
  --stage:        #ece4d4;
  --fill:         #efe8da;
  --fill-strong:  #eae3d3;
  --head-fill:    #f8f2e6;
  --row-line:     #e8e0d0;
  --row-line-soft:#eee7d9;
  --input-line:   #cdc3af;
  --brand-tint:   #e3e7f0;
  --brand-line:   #a7b6d4;
  --gold-tint:    #f1e3c3;
  --moss-tint:    #e3e8d8;
  --moss-ink:     #2f5d3a;
  --danger-tint:  #f3ded6;
  --danger-ink:   #8a2b22;
  /* restyle 2026-10-10 (board C1): the refusal text on --danger-tint. */
  --danger-deep:  #6e1d16;
  --on-fill:      #fff;
  /* restyle 2026-10-10 (contributor shell): the boards' top/tab bar hairline. */
  --hair-soft:    #e2d9c7;
  /* The boards' Arabic faces: headings Noto Kufi, body IBM Plex Sans Arabic. */
  --font-ar-head: "Noto Kufi Arabic", "IBM Plex Sans Arabic", sans-serif;
  --font-ar-body: "IBM Plex Sans Arabic", "Geist", sans-serif;
  /* restyle 2026-10-10 (board M7): the transcript editor's waveform bars.
     A canvas value -- public/transcribe.js hands it to wavesurfer and names
     this token beside it; change the two together. */
  --wave-bar:     #b9c3d9;
  /* restyle 2026-10-10 (board C4, the recording room): the rule and progress
     track, the disabled primary's fill and ink, the invite link's field. */
  --track:        #e5ddcc;
  --off-fill:     #ddd4c3;
  --off-ink:      #7a7262;
  --invite-fill:  #f3ede0;
  /* restyle 2026-10-10 (board C3, guided recording): the consent box's wash
     and the stay chip's ink on --gold-tint. */
  --brand-wash:   #eeeff3;
  --gold-ink:     #5e430a;

  /* THE DISPLAY STACK CARRIES THE ARABIC FACE TOO, and that is a MEASURED
     correction rather than a tidy-up. views/projects.html's own note says an
     Arabic heading "falls through to the generic serif" and that size and
     weight carry the hierarchy instead, "in the body stack" -- but .intro is
     NOT in the body stack, it is this one. Measured in Chrome 153 at 360px:
     «بياناتي.» in .intro rendered 50.1px wide, which is the Georgia fallback
     serif (50.5), not the body stack (64.1) and not IBM Plex (51.8). So the one
     Arabic HEADING on every page was set in whatever naskh serif the phone
     happens to ship, above a page set in something else -- a second face per
     page, arrived at by accident, which is what naming one Arabic family is
     meant to stop.

     Fraunces and Source Serif Pro stay AHEAD of it, so no Latin heading moves:
     both cover Latin, neither covers Arabic, and a browser falls through per
     character. Verified by measuring an English heading before and after, not
     by reading this rule. */
  --font-display: "Fraunces", "Source Serif Pro", "IBM Plex Sans Arabic", Georgia, serif;
  /* THE ARABIC FACE IS IN THE STACK, NOT INSTEAD OF IT (msa spec, D3). Geist
     has NO Arabic glyphs at all, so a browser falls THROUGH it per character:
     Latin still renders in Geist, and every Arabic character lands on IBM Plex
     Sans Arabic, which is the whole point of naming it AFTER both Geist
     entries rather than before them. IBM Plex Sans Arabic ships a full Latin
     set of its own, so the order is load-bearing -- put first, it would
     silently replace Geist on every English page in the product.

     IT IS SAFE ON THE PAGES THAT DO NOT LOAD IT. style.css is served to all
     twenty views and only the five user views carry the <link>; the rest skip
     a family they do not have and land on -apple-system exactly as before. An
     Arabic project NAME on /manager/projects is data, and it renders in the
     system fallback there as it always did.

     --font-display IS DELIBERATELY UNTOUCHED. Fraunces has no Arabic either,
     and views/projects.html already carries the note: an Arabic heading set in
     it falls to the generic serif, so size and weight carry that hierarchy
     instead. Putting the Arabic face on the display token would make an Arabic
     h1 a DIFFERENT family from its English one, which is the fork this
     stylesheet keeps paying for. */
  --font-body:    "Geist", "Geist Sans", "IBM Plex Sans Arabic", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono:    "JetBrains Mono", "SF Mono", "Cascadia Code", Consolas, monospace;
}

/* ------------------------------------------------------------------
   Reset + base
------------------------------------------------------------------ */
* { box-sizing: border-box; }

/* Make sure the HTML `hidden` attribute always wins, even when an
   element has a `display: <something>` rule via id or class. */
[hidden] { display: none !important; }

/* THE WRONG WORDS, HIDDEN UNTIL TRANSLATED (polish 2026-10-19). /lang-boot.js
   sets the stored English direction from <head> and adds .i18n-pending; the
   served Arabic words keep their boxes but are not seen until
   I18nApply.apply() paints them and drops the class. THE FAILSAFE: after
   1.5 s the words show anyway, so a page whose scripts fail never stays blank. */
html.i18n-pending [data-i18n] { visibility: hidden; animation: i18n-failsafe 0s linear 1.5s forwards; }
/* AND EVERY OTHER SERVED WORD (hotfix 2026-10-20): a [data-i18n] rule alone left
   the Arabic placeholders and other data-i18n-attr values, option labels and the
   text the page scripts write on screen while /i18n.js was still loading. So
   while pending, the attribute carriers and every select are hidden too, and so
   is the content of the contributor pages (everything in <main> but the top bar
   and the nav, and the wordless aria-hidden skeletons) and of the sign-in pages
   (the stage under the app bar). The bars stay, so the page is never blank. SAME FAILSAFE: shown after 1.5 s whatever
   happens. */
html.i18n-pending :is([data-i18n-attr], select),
html.i18n-pending body[data-user-page] main > :not(.site-header):not(.user-nav):not([aria-hidden="true"]),
html.i18n-pending .auth-stage { visibility: hidden; animation: i18n-failsafe 0s linear 1.5s forwards; }
@keyframes i18n-failsafe { to { visibility: visible; } }

html, body { margin: 0; padding: 0; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "cv11";
}

main {
  max-width: 560px;
  margin: 0 auto;
  padding: 22px 20px 80px;
  position: relative;
}

a {
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
a:hover { color: var(--brand-deep); }

button { font-family: inherit; }

/* ------------------------------------------------------------------
   Browser surfaces — the parts nobody draws
------------------------------------------------------------------ */

/* These four shipped as OS defaults on eight of the fifteen pages: an
   operating-system BLUE selection dragged across a cream page, a black caret
   in every field, a grey scrollbar, and iOS inflating body text in landscape.

   The 2026-08-21 console pass wrote the first two -- and wrote them with
   GLOBAL selectors, '::selection' and ':root' -- but put them in admin.css,
   which only nine pages load. So the rule was product-wide in every respect
   except the file it lived in, and the room page, /manager, /my-recordings
   and all five auth pages never got it. Moved here, where every page looks.

   'text-size-adjust' is new and is not cosmetic: without it mobile Safari
   inflates text on rotation, which is how a 44px control quietly stops being
   44px. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
::selection { background: var(--brand-soft); color: var(--ink); }
:root {
  accent-color: var(--brand);
  /* Colour only. Deliberately NOT 'scrollbar-width: thin' at the document
     level -- narrowing the page's own scrollbar is a usability change, not a
     theme one. admin.css still asks for thin on .md-scroll, which is a small
     inner column where it earns it. */
  scrollbar-color: var(--hair-strong) transparent;
}
input, select, textarea { caret-color: var(--brand); }

/* ------------------------------------------------------------------
   Focus, for everything that does not style its own
------------------------------------------------------------------ */

/* DELIBERATELY the lowest specificity a focus rule can have (0,1,0), so it
   FILLS GAPS and overrides nothing. Every designed ring already outranks it:
   'button.action:focus-visible' is (0,2,1), '.meta-form input:focus' (0,1,1),
   and admin.css's eleven rules are all more specific AND load second.

   It exists because a static sweep of the stylesheets against every rendered
   page found the gaps are not incidental. /admin and /manager are fully
   covered -- the 2026-08-21 console pass did that -- but the pages nobody
   swept were not: the room page had ten unstyled controls including its whole
   header nav and both landing selects, /transcribe had EIGHTEEN, which is
   every control in a keyboard-driven editor, and the auth pages had their
   sign-in / sign-up links.

   ':focus-visible', never ':focus': a mouse click on a button should not ring
   it. The browser decides which is which, and it is right more often than a
   rule guessing per element. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------
   Site header (logo + room id) — appears on both pages
------------------------------------------------------------------ */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 14px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--hair);
}

.brand-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
}
.brand-mark:hover { text-decoration: none; }

.brand-mark img {
  height: 26px;
  width: auto;
  display: block;
}

.brand-mark .wordmark {
  font-family: var(--font-display);
  font-size: 12px;
  font-style: italic;
  color: var(--warm-1);
  font-weight: 500;
  letter-spacing: 0.005em;
  /* LOGICAL, NOT PHYSICAL (msa onboarding spec, D3). This divider separates the
     wordmark from the logo, so it belongs on whichever side the logo is on —
     and <html dir="rtl"> swaps that the moment the page is Arabic. The logical
     properties compute to the old left values in LTR, so nothing moves on an
     English page, and there is no [dir="rtl"] override block to keep in sync. */
  border-inline-start: 1px solid var(--hair);
  padding-inline-start: 10px;
  margin-inline-start: 2px;
}

.site-header .room-id {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--warm-2);
  text-transform: uppercase;
}

/* ------------------------------------------------------------------
   Page intro (who you are)
------------------------------------------------------------------ */

.intro {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.015em;
  margin: 0 0 14px;
  font-variation-settings: "opsz" 36;
}
.intro .you-are {
  font-style: italic;
  font-weight: 400;
  font-size: 14px;
  color: var(--warm-1);
  margin-inline-end: 6px;
}
.intro .who {
  color: var(--ink);
}

.hint-row {
  font-size: 13px;
  color: var(--warm-1);
  margin: 0 0 18px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.hint-row #hintText { min-width: 0; }

/* The "Save these details to my profile" tick left the seat in phase 6 (D30):
   the room never writes a profile. */

/* ------------------------------------------------------------------
   Microphone banner — persistent, state-independent
   ------------------------------------------------------------------
   The one message on this page that is NOT keyed off the UI state. A mic
   failure used to be S.ERROR, and S.ERROR hides #slots — so the participant
   lost the form they had just filled in. This sits directly above the seats it
   blocks and stays until the microphone actually works. Uses --danger like
   .meta-err rather than a new colour.
------------------------------------------------------------------ */

.mic-banner {
  margin-bottom: 12px;
  padding: 10px 12px;
  border: 1px solid var(--danger);
  border-inline-start-width: 3px;
  border-radius: 6px;
  background: var(--danger-soft);
  color: var(--danger);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.45;
}

/* ------------------------------------------------------------------
   Slot picker (room page, initial state)
------------------------------------------------------------------ */

#slots {
  display: grid;
  /* Was a fixed `1fr 1fr` REGARDLESS of speaker count, so a 5-speaker room got
     five half-width cells stacked in two columns — and the .role line added in
     2026-08-15 wrapped inside them at 375px. auto-fit reflows to one column on a
     phone and packs up to the available width on a desktop. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}

/* THE EDITING SEAT SPANS THE ROW. See the class toggle in renderSlots for the
   arithmetic: auto-fit resolves to TWO 166px columns on a 375px phone, and the
   metadata form lives inside a seat card, so the entire form was rendering in
   half the screen. The grid stays right for a seat PICKER; only the seat being
   edited needs the width.
   The only other grid-column in this file is on .rec-card .actions and is
   unrelated. */
.slot.editing { grid-column: 1 / -1; }

.slot {
  position: relative;
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 6px;
  padding: 14px 14px 12px;
  cursor: pointer;
  text-align: start;
  color: var(--ink);
  font-family: inherit;
  transition: background 180ms ease, border-color 180ms ease, transform 100ms ease, box-shadow 180ms ease;
}
.slot:hover:not(:disabled):not(.taken) {
  background: var(--card-hover);
  border-color: var(--hair-strong);
}
.slot:active:not(:disabled):not(.taken) { transform: translateY(1px); }

/* scripts/check-views.js has reported this class as its ONE finding since
   2026-08-21, and it was deferred twice -- both times correctly, because it
   belonged to neither the console split nor unit 01a (docs/DEFERRED.md 12).
   The room page IS this sweep's scope, and the open question was "dead class
   or lost rule". It is a LOST RULE, and the evidence is that its absence
   silently voids two declarations one rule below this one:

     .ch and .state are <span>s, so with no rule here they compute to
     'display: inline' -- and a vertical margin on an inline box does
     NOTHING. '.ch { margin-bottom: 6px }' and '.state { margin-top: 8px }'
     were both being dropped by the layout engine. Measured on the rendered
     page: the real gaps were 3.65px and 6.80px, which is line-height
     leading, not the spacing either rule asks for. The seats only stacked
     at all because '.name' between them is display:block and forced the
     breaks.

   A flex column is what makes those two margins take effect, and the
   stacking stays identical. 'align-items' is left at its 'stretch' default
   deliberately: '.name' is a full-width block today and shrinking it to its
   content would be a real change, not the no-op this is meant to be. */
.slot-head {
  display: flex;
  flex-direction: column;
}

.slot .ch {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.16em;
  color: var(--warm-2);
  margin-bottom: 6px;
  text-transform: uppercase;
}

.slot .name {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.15;
  color: var(--ink);
  letter-spacing: -0.005em;
  font-variation-settings: "opsz" 36;
}

/* The derived seat role (Doctor / Patient / Companion 3). Its own line under
   .name, sitting ABOVE .state so the seat reads name -> who they are -> what
   they have done. Hidden outright when the room has no role vocabulary. */
.slot .role {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
}
.slot.mine .role { color: var(--brand); }

.slot .state {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-2);
  margin-top: 8px;
  font-weight: 500;
}

.slot.mine {
  background: linear-gradient(180deg, #f0f3fb 0%, var(--card) 100%);
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-soft);
}
.slot.mine .state { color: var(--brand); font-weight: 600; }

.slot.taken {
  opacity: 0.5;
  cursor: not-allowed;
}

.slot:disabled { cursor: not-allowed; }
.slot[disabled] { background: var(--card); }

/* slot 2 on landing — can't initiate, must arrive via invite link */
.slot.locked {
  cursor: not-allowed;
  background:
    repeating-linear-gradient(135deg,
      var(--card) 0 8px,
      var(--paper-elev) 8px 16px);
  border-style: dashed;
  opacity: 0.72;
}
.slot.locked:hover { background:
  repeating-linear-gradient(135deg,
    var(--card) 0 8px,
    var(--paper-elev) 8px 16px); }
.slot.locked .state { color: var(--warm-1); font-weight: 600; }

/* ---------- Metadata form (inside slot card) ---------- */

.meta-form {
  margin-top: 12px;
  padding: 12px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px dashed var(--brand);
  border-radius: 5px;
}
/* 'minmax(0, 1fr)' AND NOT '1fr', the trap public/admin.css records twice
   about `.master-detail`: '1fr' means 'minmax(auto, 1fr)', and 'auto' as a
   MINIMUM is the item's min-content width, so a track refuses to shrink below
   the widest unbreakable thing in it. Measured on /settings at 390px, which
   is this rule's only consumer since phase 6 took gender and age off the
   seat: the two columns resolved to 99px and 251px — a Gender select sized to
   the word "female" beside a Year of birth box two and a half times its width
   — while at 1280px both were 302px and the pair looked deliberate. Equal
   halves at every width now. */
.meta-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}
.meta-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Was 9px at 0.14em — below a readable size on a phone, and the tracking
     widened every string inside what used to be a 166px cell. */
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
}
.meta-form label.full {
  margin-top: 8px;
}

/* ------------------------------------------------------------------
   Landing setup pickers -- speaker count, session type, project
------------------------------------------------------------------ */

/* These three had NO CSS AT ALL. They rendered as raw browser selects with
   raw browser labels, on the landing page, which is the first thing a host
   ever touches on this product -- 19px tall against the 44px the seat form
   next to them already uses, and at the UA default font size, which on iOS
   Safari means focusing one zooms the viewport and does not zoom back.

   The values are not new: they are exactly what '.meta-form' settled on, and
   the reasoning for both numbers is written out at that rule. Copying them
   here rather than widening that selector keeps the seat form's cascade
   untouched -- these sections are not a metadata form and must not start
   inheriting its layout rules (order:, .lang-pair, the details fold). */
#speakerCountPicker label,
#landingPickers label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
}
#speakerCountPicker select,
#landingPickers select {
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--hair);
  border-radius: 3px;
  padding: 8px 10px;
  font-family: var(--font-body);
  font-size: 16px;
  min-height: 44px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
  width: 100%;
}
#landingPickers { margin-top: 12px; }
#landingPickers #projectPickerLabel { margin-top: 12px; }

/* ---- Conditional fields (2026-08-18) --------------------------------------
   THE FORM BECOMES A FLEX COLUMN so a COLLAPSED field can sink below the ones
   this seat actually owes (see `order` below). One deliberate side effect, and
   a welcome one on a phone: .meta-save was an inline-block that shrank to its
   own text and now stretches to the full width. */
.meta-form {
  display: flex;
  flex-direction: column;
}

/* A conditional field's disclosure. applyFieldState in app.js adds `plain` when
   the seat owes this field or is being offered it; without `plain` the field is
   tucked behind the summary. COLLAPSED IS NOT REMOVED — the control is still in
   the DOM, still submitted, still saved if filled. */
.meta-form .meta-more > summary {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--brand);
  cursor: pointer;
  padding: 12px 0;
  min-height: 44px;
  display: flex;
  align-items: center;
  list-style: none;
}
.meta-form .meta-more > summary::-webkit-details-marker { display: none; }
.meta-form .meta-more > summary::before {
  content: '+';
  display: inline-block;
  width: 16px;
  font-size: 15px;
  line-height: 1;
}
.meta-form .meta-more[open] > summary::before { content: '\2212'; }
.meta-form .meta-more.plain > summary { display: none; }

/* COLLAPSED FIELDS SINK. Everything this seat owes stays directly under the
   language pair in every combination, without the markup being reordered per
   case:
     medical host      SPECIALITY plain; topic + second language sink
     code-switch host  TOPIC and SWITCHES INTO plain; speciality sinks
     any joiner        both classifiers sink
   The trailing controls are pinned below whatever ends up there. */
.meta-form > .meta-more:not(.plain) { order: 5; }
.meta-form > .seat-details { order: 10; }
.meta-form > .meta-save { order: 11; min-height: 44px; }
.meta-form > .meta-err { order: 12; }

/* THE SEAT'S DETAILS LINE (phase 6, spec §6.3): one sentence and at most one
   control -- Try again or Open My details. Plain on purpose; the visual pass is
   phase 8's. [hidden] still wins over display:flex (global rule). */
.seat-details {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--warm-1);
}
.seat-details .seat-details-text { flex: 1 1 200px; min-width: 0; }
.seat-details .seat-details-retry,
.seat-details .seat-details-link { margin: 0; }

/* NUMBER OF SPEAKERS, AS BUTTONS (phase 6, PAR-4). The checked one is filled;
   44px so a phone can hit it. */
#speakerCountPicker .lab {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
  margin-bottom: 6px;
}
.count-row { display: flex; flex-wrap: wrap; gap: 8px; }
.count-row .action { min-width: 44px; min-height: 44px; }
.count-row .action[aria-checked="true"] { background: var(--brand); color: var(--card); }

/* LANGUAGE + DIALECT on one row. Two stacked full-width selects cost two
   labelled rows each, and the two pairs are half of what a code-switching
   joiner has left once fieldPlan has hidden what it does not owe. */
.meta-form .lang-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 8px;
}
.meta-form input,
.meta-form select {
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--hair);
  border-radius: 3px;
  padding: 8px 10px;
  font-family: var(--font-body);
  /* 16px IS THE iOS THRESHOLD, not a taste choice. Mobile Safari auto-zooms the
     viewport on focusing any input below 16px and does NOT zoom back out, so at
     13px every single tap into this form zoomed the page in and left it there.
     Recordings are made on phones by host and joiners both (owner ruling
     2026-08-18), so this was hit on every field of every recording. */
  font-size: 16px;
  /* A finger is not a mouse pointer. */
  min-height: 44px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.meta-form input:focus,
.meta-form select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-soft);
}
.meta-form input:invalid:not(:placeholder-shown) {
  border-color: var(--danger);
}
.meta-form select:disabled {
  background: repeating-linear-gradient(135deg,
    var(--paper-elev) 0 6px,
    var(--card) 6px 12px);
  border-style: dashed;
  color: var(--warm-2);
  cursor: not-allowed;
}
.meta-form .meta-hint {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--warm-1);
  margin-top: 7px;
  letter-spacing: 0.02em;
}
.meta-form .meta-save {
  margin-top: 10px;
  background: var(--brand);
  color: var(--card);
  border: 0;
  padding: 8px 16px;
  border-radius: 3px;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 160ms;
}
.meta-form .meta-save:hover:not(:disabled) { background: var(--brand-deep); }
.meta-form .meta-save:disabled { opacity: 0.5; cursor: not-allowed; }
.meta-form .meta-err {
  margin-top: 7px;
  font-size: 11px;
  color: var(--danger);
  font-weight: 500;
}

/* Saved metadata summary — replaces the form once submitted */
.meta-summary {
  margin-top: 10px;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--hair);
  border-radius: 4px;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.meta-summary .meta-line {
  font-family: var(--font-mono);
  color: var(--ink-soft);
  letter-spacing: 0.01em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meta-summary .meta-edit {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 11px;
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 3px;
  flex: none;
}

/* ---------- Start-recording hint ---------- */

.start-hint {
  margin: -8px 0 14px;
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--warm-2);
  text-align: center;
}

/* ------------------------------------------------------------------
   Peer line (after both claimed — compact)
------------------------------------------------------------------ */

#peerLine {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 14px;
  font-size: 13px;
}

#peerLine .seg {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1;
  min-width: 0;
}
#peerLine .seg.me { color: var(--ink); font-weight: 600; }
#peerLine .seg.them { color: var(--warm-1); }

#peerLine .dot {
  width: 7px; height: 7px; border-radius: 50%;
  flex: none;
}
#peerLine .seg.me .dot { background: var(--brand); }
#peerLine .seg.them .dot { background: var(--moss); }
#peerLine .seg.them[data-state="waiting"] .dot,
#peerLine .seg.them[data-state="connecting"] .dot {
  background: var(--warm-2);
  animation: blink 1.2s ease-in-out infinite;
}
#peerLine .seg.them[data-state="recording"] .dot {
  background: var(--gold);
  animation: pulse-gold 1.4s ease-out infinite;
}

#peerLine .seg .name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#peerLine .seg .state-tag {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--warm-2);
  text-transform: lowercase;
}

#peerLine .divider {
  width: 1px;
  align-self: stretch;
  background: var(--hair);
  margin: -10px 0;
}

/* ------------------------------------------------------------------
   Invite card
------------------------------------------------------------------ */

#inviteCard {
  background: var(--card);
  border: 1px solid var(--hair);
  border-inline-start: 3px solid var(--brand);
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 14px;
}

#inviteCard .lab {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-2);
  margin-bottom: 8px;
  font-weight: 600;
}

.invite-row {
  display: flex;
  gap: 6px;
}

.invite-row input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  border: 1px solid var(--hair);
  background: #fff;
  padding: 9px 11px;
  border-radius: 4px;
  color: var(--ink-soft);
}
.invite-row input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}

.copy-feedback {
  margin-top: 8px;
  font-size: 11px;
  color: var(--moss);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* ------------------------------------------------------------------
   Status component (state-driven)
------------------------------------------------------------------ */

/* Board C4's status card (restyle 2026-10-10): 14px radius, 12/14 padding,
   14px medium text, a 10px dot. */
#statusRow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 14px;
  margin-bottom: 12px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.45;
}

#statusRow .status-dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  flex: none;
  background: var(--warm-2);
}
#statusRow .status-text { flex: 1; min-width: 0; }
#statusRow .status-timer {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--warm-1);
  font-feature-settings: "tnum";
  letter-spacing: 0.02em;
  flex: none;
}

/* State variants -- board C4's dots: cobalt while someone is joining, gold
   while the room waits for a speaker, moss once connected, red while it
   records. */
#statusRow[data-state="idle"] .status-dot { background: var(--brand); }

#statusRow[data-state="waiting"] .status-dot {
  background: var(--gold);
  animation: blink 1.6s ease-in-out infinite;
}

#statusRow[data-state="connecting"] .status-dot {
  background: var(--brand);
  animation: blink 1.0s ease-in-out infinite;
}

#statusRow[data-state="connected"] .status-dot {
  background: var(--moss);
}

#statusRow[data-state="recording"] .status-dot {
  background: var(--danger);
  animation: blink 1.4s ease-in-out infinite;
}
#statusRow[data-state="recording"] {
  background: var(--card);
  border-color: var(--hair);
}

#statusRow[data-state="finalizing"] .status-dot {
  background: var(--brand);
  animation: blink 0.8s ease-in-out infinite;
}

#statusRow[data-state="done"] .status-dot { background: var(--moss); }

#statusRow[data-state="ended"] .status-dot { background: var(--warm-2); }

#statusRow[data-state="error"] .status-dot { background: var(--danger); }
#statusRow[data-state="error"] {
  background: var(--danger-soft);
  border-color: rgba(169, 50, 38, 0.3);
  color: var(--danger);
}

@keyframes blink {
  50% { opacity: 0.28; }
}
@keyframes pulse-gold {
  0%   { box-shadow: 0 0 0 0 rgba(200, 148, 20, 0.55); }
  70%  { box-shadow: 0 0 0 9px rgba(200, 148, 20, 0); }
  100% { box-shadow: 0 0 0 0 rgba(200, 148, 20, 0); }
}

/* Sub-line below status (e.g., connection hint, peers) */
.status-hint {
  margin: -6px 0 12px;
  padding: 9px 12px;
  border-radius: 6px;
  background: var(--gold-soft);
  border-inline-start: 3px solid var(--gold);
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.45;
}

/* ------------------------------------------------------------------
   Primary action button (only one rendered at a time)
------------------------------------------------------------------ */

button.action {
  width: 100%;
  background: var(--brand);
  color: var(--card);
  border: 0;
  padding: 13px 18px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: background 160ms ease, transform 90ms ease, box-shadow 160ms ease;
  margin-bottom: 12px;
}
button.action:hover:not(:disabled) {
  background: var(--brand-deep);
}
button.action:active:not(:disabled) { transform: translateY(1px); }
button.action:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--brand-soft);
}
button.action:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

button.action.stop {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--ink);
}
button.action.stop:hover {
  background: var(--ink);
  color: var(--card);
}

button.action.compact,
a.action.compact {
  display: inline-block;
  width: auto;
  padding: 9px 16px;
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-bottom: 0;
  text-decoration: none;
}

button.action.ghost,
a.action.ghost {
  background: transparent;
  color: var(--brand);
  border: 1px solid var(--brand);
}
button.action.ghost:hover:not(:disabled),
a.action.ghost:hover { background: var(--brand-soft); color: var(--brand-deep); }

/* THE WAY OUT, AND THE WAY TO END IT (phase 3, D57/D58).
   Leave and End session are destructive in the ordinary sense -- they take you
   out of a room other people are in -- so they read as destructive. Built on the
   --danger pair that already exists for #micBanner and the error status dot, so
   no new colour enters the palette. */
button.action.danger,
a.action.danger {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
}
button.action.danger:hover:not(:disabled),
a.action.danger:hover { background: var(--danger-soft); }
button.action.danger:focus-visible { box-shadow: 0 0 0 3px var(--danger-soft); }

/* ------------------------------------------------------------------
   The action bar (2026-08-18)
------------------------------------------------------------------ */
/* See the markup comment for why STICKY and not FIXED. Sticky brings a second
   benefit the plan did not anticipate: it occupies its own space in the flow,
   so unlike a fixed bar it can never cover the last form field and no reserved
   padding is needed anywhere. */
#actionBar {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 16px;
  padding: 12px 0 max(12px, env(safe-area-inset-bottom));
  /* Board C4's dock: the card surface under a soft hairline (restyle
     2026-10-10; it was --paper and --hair). The room's own section at the end
     of this sheet gives it the board's full-bleed padding. */
  background: var(--card);
  border-top: 1px solid var(--hair-soft);
}
/* The buttons carry a 12px margin from the old stacked layout; the bar spaces
   its children with gap instead, so the two would otherwise double up. */
#actionBar button.action { margin-bottom: 0; }

/* AN EMPTY BAR MUST NOT DRAW ITSELF. Every child is `hidden` on the landing
   page before a seat is claimed, and [hidden]{display:none!important} means
   they take no space — but the bar would still paint its border and padding as
   a stray line across the bottom of the page.
   This is DERIVED from state rather than written by renderUi, so the
   one-render-owns-visibility rule is untouched. If :has() is ever unavailable
   the bar simply shows empty: cosmetic, not broken. */
#actionBar:not(:has(> *:not([hidden]))) { display: none; }

/* ------------------------------------------------------------------
   Final / download card
------------------------------------------------------------------ */

#resultBox {
  background: var(--moss-soft);
  border: 1px solid rgba(74, 107, 58, 0.35);
  border-radius: 6px;
  padding: 13px 14px;
  margin-bottom: 14px;
}
#resultBox .lab {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  margin-bottom: 6px;
  font-weight: 700;
}
#resultBox a {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 16px;
  color: var(--brand);
  font-weight: 500;
}
#resultBox .share {
  margin-top: 6px;
  font-size: 11px;
  color: var(--warm-1);
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}
#resultBox button {
  margin-top: 14px;
}

/* ------------------------------------------------------------------
   Log (folded)
------------------------------------------------------------------ */

.log-fold {
  margin-top: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--hair);
}
.log-fold summary {
  cursor: pointer;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
  list-style: none;
  user-select: none;
}
.log-fold summary::-webkit-details-marker { display: none; }
.log-fold summary::before {
  content: "▸  ";
  display: inline-block;
  transition: transform 120ms ease;
}
.log-fold[open] summary::before {
  content: "▾  ";
}
.log-fold pre {
  margin: 10px 0 0;
  background: #1a1815;
  color: #c8bda3;
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 10px 12px;
  max-height: 200px;
  overflow: auto;
  border-radius: 4px;
  line-height: 1.5;
}

/* ------------------------------------------------------------------
   Footer
------------------------------------------------------------------ */

.site-footer {
  margin-top: 32px;
  padding-top: 16px;
  border-top: 1px solid var(--hair);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-2);
  text-align: center;
  font-weight: 500;
}
.site-footer .dot { margin: 0 6px; color: var(--hair-strong); }
.site-footer .ink {
  font-family: var(--font-display);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.005em;
  font-size: 11px;
  color: var(--warm-1);
}

/* ------------------------------------------------------------------
   Admin page — seat names editor
------------------------------------------------------------------ */

#seatNamesPanel {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 8px;
  padding: 16px 16px 14px;
  margin-bottom: 18px;
}

#seatNamesPanel h2 {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
#seatNamesPanel .hint {
  margin: 0 0 12px;
  font-size: 12px;
  color: var(--warm-1);
  line-height: 1.5;
}

.seat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 12px;
}

.seat-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
}

.seat-grid input {
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: 9px 11px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.seat-grid input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}

/* The iOS 16px floor, for the two shared controls outside .meta-form. Same
   selector shape as the base rule so it wins on source order rather than on
   a specificity trick, and scoped to the breakpoint so the desktop type
   scale of the console and the manager queue is untouched. */
@media (max-width: 640px), (pointer: coarse) {
  .seat-grid input,
  .invite-row input {
    font-size: 16px;
    min-height: 44px;
  }
}

.row-flex {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

#seatNamesStatus { font-size: 11px; letter-spacing: 0.02em; }
#seatNamesStatus.ok  { color: var(--moss); }
#seatNamesStatus.err { color: var(--danger); }

/* ------------------------------------------------------------------
   Admin: section heads + active rooms
------------------------------------------------------------------ */

.admin-section { margin-bottom: 20px; }
.admin-section .section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.admin-section .section-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.admin-section .section-head .row-flex { gap: 8px; }

/* The projects panel (2026-08-18). An inline note beside a field label, for the
   two inputs whose FORMAT is not guessable from their name (comma-separated
   lists). Deliberately quiet: it is a hint, not a second label. */
.hint-inline {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--warm-1);
}
/* The save diff. Shown only when the server refuses on rulesetVersion, so it
   appears exactly when an operator needs to see what they changed. */
#pfDiff {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--warm-1);
  margin: 8px 0;
}
#projectForm { margin-top: 12px; }
#projectsList { margin-top: 10px; }

.small-status {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
  margin-bottom: 10px;
}

#activeRooms {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.active-card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 6px;
  padding: 12px 14px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
}
.active-card .active-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.active-card .active-id {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.02em;
}
.active-card .active-age {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--warm-2);
  letter-spacing: 0.02em;
  flex: 1;
}
.active-card .active-tag {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 2px;
  letter-spacing: 0.12em;
}
.active-card .active-tag.idle {
  background: var(--brand-soft);
  color: var(--brand);
}
.active-card .active-tag.rec {
  background: var(--gold-soft);
  color: var(--gold-deep);
  animation: pulse-gold 1.4s ease-out infinite;
}
.active-card .active-slot {
  font-size: 12px;
  color: var(--ink-soft);
  display: flex;
  gap: 10px;
  align-items: baseline;
}
.active-card .active-slot .ch {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--warm-2);
  letter-spacing: 0.16em;
  flex: none;
  min-width: 56px;
}
.active-card .active-slot .who {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink);
  letter-spacing: -0.005em;
}

/* ------------------------------------------------------------------
   Admin: recordings list
------------------------------------------------------------------ */

#listToolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 10px;
}

#listStatus {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--warm-2);
  font-weight: 600;
}

#list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rec-card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 6px;
  padding: 12px 14px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  transition: border-color 140ms ease, background 140ms ease;
}
.rec-card:hover {
  border-color: var(--hair-strong);
  background: var(--card-hover);
}

.rec-card .rec-num {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  color: var(--brand);
  min-width: 24px;
  text-align: center;
  font-weight: 500;
}

.rec-card .rec-body { min-width: 0; }

.rec-card .seat-line {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.005em;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rec-card .seat-line .sep {
  color: var(--warm-2);
  font-style: italic;
  font-weight: 400;
  margin: 0 4px;
}

.rec-card .meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--warm-2);
  letter-spacing: 0.02em;
  margin-top: 3px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}

/* WHAT KIND OF RECORDING THIS IS (B8, 2026-08-18). /admin could previously draw
   only the speciality, so a code-switching session was indistinguishable from a
   medical one at a glance — and the owner read it as the session type having
   failed to take. Two muted tints rather than two loud ones: this is a fact
   about every row, not an alert on some of them. */
.rec-card .type-badge {
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--hair);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.rec-card .type-medical { color: var(--warm-1); }
.rec-card .type-code_switching {
  color: var(--brand);
  border-color: var(--brand-soft);
  background: var(--brand-soft);
}

.rec-card .meta-detail {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--warm-1);
}
.rec-card .meta-detail .label {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  color: var(--brand);
}
.rec-card .meta-detail .value {
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}

.rec-card .actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
  font-size: 12px;
}
.rec-card .actions a {
  color: var(--brand);
  font-family: var(--font-display);
  font-style: italic;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ------------------------------------------------------------------
   Help: hint-row link + "how it works" modal
------------------------------------------------------------------ */

.help-link {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 600;
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.help-link:hover { color: var(--brand-deep); }
.help-link .q {
  width: 15px; height: 15px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  text-decoration: none;
}

#helpBackdrop {
  position: fixed;
  inset: 0;
  background: rgba(26, 26, 26, 0.45);
  opacity: 0;
  transition: opacity 280ms ease;
  z-index: 40;
}
#helpBackdrop.show { opacity: 1; }

#helpModal {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, calc(-50% + 12px));
  width: min(440px, calc(100vw - 32px));
  background: var(--card);
  border: 1px solid var(--hair-strong);
  border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.3);
  padding: 20px 22px 22px;
  z-index: 41;
  opacity: 0;
  transition: opacity 280ms ease, transform 280ms ease;
}
#helpModal.show {
  opacity: 1;
  transform: translate(-50%, -50%);
}

.help-close {
  position: absolute;
  inset-inline-end: 12px; top: 10px;
  background: transparent;
  border: 0;
  color: var(--warm-2);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.help-close:hover { color: var(--ink); }

/* THE GUIDE BECOMES A BOTTOM SHEET ON A PHONE (2026-08-18). It auto-opens once
   per browser, so for most participants it is the very first thing they see and
   touch — and recordings are made on phones by host and joiners both.

   MUST STAY BELOW THE #helpModal.show RULE ABOVE. Both selectors are a single
   id, so specificity ties and SOURCE ORDER decides; moved above, this block
   loses and nothing happens.

   BOTH transforms are overridden, not just the base one. The desktop rule
   centres the dialog with translate(-50%, calc(-50% + 12px)) and `.show`
   animates that same property to translate(-50%, -50%). Override only the base
   and the sheet slides half off the screen the instant it opens. */
@media (max-width: 480px) {
  #helpModal {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: auto;
    max-height: 88vh;
    overflow-y: auto;
    border-radius: 14px 14px 0 0;
    transform: translateY(16px);
    padding: 20px 20px max(20px, env(safe-area-inset-bottom));
  }
  #helpModal.show { transform: translateY(0); }
  /* 44px, because this is the first control a participant ever touches and a
     guide you cannot dismiss is worse than no guide. */
  .help-close {
    inset-inline-end: 8px; top: 8px;
    width: 44px; height: 44px;
    font-size: 18px;
  }
}

.help-eyebrow {
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brand);
  font-weight: 700;
  margin-bottom: 3px;
}
.help-title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 14px;
}
.help-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: help;
}
.help-steps li {
  counter-increment: help;
  position: relative;
  padding-inline-start: 32px;
  margin-bottom: 11px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.help-steps li::before {
  content: counter(help);
  position: absolute;
  inset-inline-start: 0; top: 0;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--brand);
  color: var(--card);
  font-weight: 700;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-steps li b { color: var(--ink); font-weight: 600; }
/* .help-foot left with the cap sentence (phase 6, D32). */

@media (prefers-reduced-motion: reduce) {
  #helpBackdrop, #helpModal { transition: none; }

  /* This file had SEVEN infinite animations and eight transitions, and the
     rule above covered the help modal alone -- so a reader who asks the OS
     for less motion still got an indefinitely blinking dot on the page they
     sit in front of for twenty minutes.

     Killing the duration is safe HERE, and that was checked rather than
     assumed, because "reduce motion" must never cost a recording indicator
     its meaning:
       * 'blink' declares only a 50% keyframe (opacity 0.28), so 0% and 100%
         are the default 1 -- it settles VISIBLE, not faded out.
       * 'pulse-gold' animates box-shadow only; the dot keeps its --gold fill.
       * every #statusRow state already carries its own colour AND its own
         text, so nothing here was ever conveyed by movement alone.
     A keyframe set that ended dimmed would need pausing, not shortening. */
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------------
   Mobile tweaks
------------------------------------------------------------------ */

@media (max-width: 480px) {
  main { padding: 18px 16px 80px; }
  .brand-mark .wordmark { display: none; }
  .rec-card {
    grid-template-columns: auto 1fr;
    gap: 10px 12px;
  }
  .rec-card .actions {
    grid-column: 1 / -1;
    flex-direction: row;
    justify-content: flex-end;
    align-items: center;
    gap: 14px;
  }
}

/* ------------------------------------------------------------------
   Logged-in header chip (room + admin) — signed-in name + log out
------------------------------------------------------------------ */
.header-right { display: flex; align-items: center; gap: 12px; }
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--warm-1);
}
.user-chip a {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 11px;
  color: var(--brand);
}

/* A 44px TAP TARGET WITHOUT A 44px BOX. Measured, these links render 17px
   tall -- 'My recordings', 'Admin', 'Dashboard', 'Log out', 'New room' and
   the help link, on every page that has a header, which is all of them.

   Growing them for real would push the header down ~18px site-wide to fix
   something that is only ever a problem for a finger, so the hit area is
   extended past the visual bounds instead -- the documented remedy, and the
   one that moves no pixel. Vertical only: these sit in a single row, so
   there is no neighbour above or below to collide with, whereas widening
   them horizontally would eat into the 8px that separates them from each
   other. Verified afterwards with elementFromPoint that nothing below the
   header lost a click to one of these, on six pages.

   THE WIDTH IS LEFT ALONE, AND THAT IS A LIMIT WORTH STATING rather than
   implying. 'Admin' stays 34px wide, not 44. Reaching 44 needs ~5px a side,
   which is more than the 8px gap between these links, so the targets would
   overlap; widening the gap instead pushes the header past a 375px viewport
   once an admin sees all four links (146px of brand + 192px of links + the
   larger gaps). 34x44 is a large improvement on 34x17 and 44x44 would be a
   new defect, so the vertical half is taken and the horizontal half is not. */
.user-chip a,
.help-link,
.site-header .brand-mark { position: relative; }
.user-chip a::after,
.help-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
@media (max-width: 480px) {
  .user-chip #userChipName { display: none; }
}

/* Who is on the page without a seat (stage 4). Room-level and deliberately
   quiet: it is context for the host, not an action. Per-seat is NOT knowable —
   a seat is claimed atomically WITH its metadata, so before a claim nobody is
   associated with any particular seat. */
.onlooker-line {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--warm-1);
  line-height: 1.45;
}

/* ---- the user home's nav (unit 06) --------------------------------------
   IN style.css, NOT admin.css. Seventeen pages load this file and seven load
   that one; the 2026-08-27 sweep found ::selection, caret-color and
   accent-color stranded in admin.css, which is why most of the product had the
   OS blue selection on a cream page. The same mistake was available here.

   Tokens only, no hex — the palette is at the top of this file. */
.user-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: 0 0 18px;
  border-bottom: 1px solid var(--hair);
}
.user-nav a {
  /* 44px is the touch floor the 2026-08-27 sweep set across the product. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 10px 14px;
  font-size: 0.92rem;
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  /* Sits the active underline ON the container's hairline rather than under it. */
  margin-bottom: -1px;
}
.user-nav a:hover { color: var(--ink); }
.user-nav a.is-current {
  color: var(--brand);
  font-weight: 600;
  border-bottom-color: var(--brand);
}
/* No :focus-visible rule here on purpose: style.css already carries a global
   one from the 2026-08-27 sweep, and a second would fight it. */

/* THE LANGUAGE TOGGLE (msa onboarding spec, D1/D2). Painted into #userNav by
   public/user-chrome.js, so it appears once on every user page that ships the
   string table, and the rule lives HERE rather than in a view's <style> for
   the same reason .user-nav does: five pages render it.

   A PILL, NOT A TAB. The tabs beside it say where you are; this says what the
   page is written in and it does something when you press it. Reading it as a
   sixth destination would be a lie, so it takes the outlined-control shape the
   product already uses for a secondary action rather than the tab's underline.

   IT SITS AFTER THE LAST TAB AND IS NOT PUSHED TO THE END OF THE ROW, and that
   is measured rather than preferred. `margin-inline-start: auto` was written
   here first, to separate it from the tabs without a physical property that
   dir="rtl" would put on the wrong side. IN CHROME 153 IT DOES NOTHING: inside
   a wrapping flex row getComputedStyle reports a used value of 145px for it and
   the element moves ZERO pixels, while `margin-left: auto` on the same element
   moves it. So the choice was a dead declaration or a physical margin that pins
   this control to the wrong end in exactly the language it exists to serve —
   the fork species public/nav-common.js:194-200 names two files for. Neither.
   It reads as a control rather than as a fifth tab because it is a PILL, and a
   shape does not depend on a direction.

   letter-spacing AND text-transform ARE EXPLICIT AT ZERO/NONE. Tracking
   applied to Arabic pulls apart letters that are meant to join, and «العربية»
   has no uppercase to transform to; both are stated rather than inherited so a
   later sweep that adds tracking to nav text cannot reach this one label.

   44px HIGH, and the padding carries the width past 44 for both labels
   («English» and «العربية» are 6-7 glyphs) — the touch floor the 2026-08-27
   sweep set across the product. */
.user-nav .nav-lang {
  margin-inline-start: 6px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  align-self: center;
  padding: 8px 14px;
  border: 1px solid var(--hair-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--brand);
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.2;
  cursor: pointer;
}
.user-nav .nav-lang:hover { background: var(--brand-soft); border-color: var(--brand); }

/* ---------------------------------------------------------------------------
   THE PEOPLE EDITOR (2026-08-31). Who is on a project and what they may do.

   IN style.css AND NOT IN admin.css OR manager.css, because it is mounted by
   BOTH families: /admin/projects (admin.css) and /manager/projects
   (manager.css) render the identical component from public/people-ui.js. A
   copy in each sheet is two places for one look to drift, and scripts/
   check-views.js would report every class as unstyled on whichever page
   loaded the sheet that lacked it.
   --------------------------------------------------------------------------- */
.ppl-panel { margin-top: 1rem; }
.ppl-notice {
  margin: 0 0 0.9rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--gold);
  border-left-width: 3px;
  border-radius: 6px;
  background: var(--gold-soft);
  color: var(--ink);
  font-size: 0.88rem;
  line-height: 1.5;
}
.ppl-list { list-style: none; margin: 0 0 0.9rem; padding: 0; }
.ppl-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--hair);
}
/* THE ROW'S GEOMETRY IS SPREAD OVER THREE RULES NOW, and it used to be one.
   Until the Team panel, `.ppl-email` was a direct child of `.ppl-row` and
   carried the whole job: a 14rem flex basis so every row's controls lined up
   in a column, a 24rem cap so they were not flung to the far edge of a 1440px
   row, and `overflow-wrap: anywhere` so one long address could not push them
   off a 375px viewport. The address now sits inside `.ppl-who` beside a name,
   a badge and up to two tags, so the job splits:

     .ppl-who    the flex child of .ppl-row -- it carries the 14rem BASIS and
                 `min-width: 0`, which is what stops a long unbroken address
                 forcing the whole box wider than the viewport. `.ppl-email`
                 RETAINS a 14rem basis of its own INSIDE it: the outer one
                 lines the rows up against the actions column, the inner one
                 lines the addresses up against the badges beside them.
     .ppl-name   } the two elements that can hold an ADDRESS, so each carries
     .ppl-email  } the 24rem cap and the wrap itself. `.ppl-name` needs them
                 because a row for an address with NO ACCOUNT has no name to
                 show and renders the address there instead -- it had only
                 `font-weight: 600` for one commit, which left exactly that
                 row unprotected.

   `.ppl-who` gets a GENEROUS cap rather than none. It holds five kinds of
   thing -- name, email, role badge, up to two tags, and a note -- and a tight
   cap would wrap the badge and tags onto their own lines. 48rem is wide enough
   that they do not, and narrow enough that on a 1440px row the actions stay
   beside the person they act on instead of at the far edge. (This comment used
   to say the box deliberately had no max-width, which overstated the case: the
   thing that must not be tightly capped is the box, not the page.) The 24rem
   cap still belongs to the address, which is the part that runs long. */
.ppl-email {
  flex: 1 1 14rem;
  max-width: 24rem;
  min-width: 0;
  /* An address is data, not prose: it must not be re-wrapped mid-domain. */
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: 0.86rem;
}
.ppl-empty { padding: 0.7rem 0; color: var(--ink-soft); font-size: 0.88rem; }
.ppl-add { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0.4rem 0 0.9rem; }
.ppl-add input { flex: 1 1 16rem; min-width: 0; }
.ppl-foot { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.ppl-msg { font-size: 0.86rem; color: var(--ink-soft); }
.ppl-msg.is-bad { color: var(--danger); }
.ppl-msg.is-good { color: var(--moss); }
/* ---- the Team panel's own heading, badges, tags and notes (phase 2 plan B,
   spec 3.4). `.ppl-note` uses --gold-deep, NOT --gold: style.css:33-49 rules
   that --gold is a pill/border token (2.6:1 on --card) and --gold-deep is the
   status-TEXT colour. The violet is the one hex value this panel adds -- there
   is no violet token, and the three role badges have to be told apart. ---- */
.ppl-heading { margin: 0 0 0.6rem; font-size: 1rem; }
/* The basis and the wrap: see the note above `.ppl-email`. */
.ppl-who { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem; flex: 1 1 14rem; min-width: 0; max-width: 48rem; }
.ppl-name { font-weight: 600; max-width: 24rem; overflow-wrap: anywhere; }
.ppl-badge { font-size: 0.72rem; font-weight: 600; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--hair); color: var(--ink-soft); }
.ppl-badge.is-manager { color: var(--brand); border-color: var(--brand); }
.ppl-badge.is-transcriptionist { color: #6b4fbb; border-color: #6b4fbb; }
.ppl-tag { font-size: 0.74rem; color: var(--ink-soft); font-style: italic; }
/* flex-basis: 100% puts the note on its own line; the ch cap keeps it a
   readable measure on a wide row instead of one 48rem line of prose. */
.ppl-note { font-size: 0.78rem; color: var(--gold-deep); flex-basis: 100%; max-width: 68ch; }
.ppl-actions { display: flex; gap: 0.5rem; align-items: center; }
.ppl-role { font: inherit; font-size: 0.86rem; }
.ppl-result { margin-top: 0.6rem; }

/* ---- the ? affordance (public/tips.js). Moved from admin.css in phase 2 plan B so manager pages can carry one. Order preserved: the contrast override must still win over the base rule. ---- */
/* ---- The `?` affordance --------------------------------------------
   Every explanatory paragraph on this console is now one of these
   (spec §4). It must be HOVERABLE AND FOCUSABLE: :focus-within covers
   both the keyboard (Tab) and the mouse (click), and :hover covers the
   pointer that never clicks. A tooltip reachable only by mouse is
   invisible to a keyboard user, and this repo has already had to fix
   /manager for being keyboard-invisible once. */
.tip { position: relative; display: inline-flex; vertical-align: middle; margin-left: 6px; }
.tip-btn {
  width: 15px; height: 15px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-body); font-size: 9.5px; font-weight: 700;
  line-height: 1;
  color: var(--warm-2); background: var(--paper);
  border: 1px solid var(--hair-strong); border-radius: 999px;
  cursor: help;
  transition: color 160ms ease, border-color 160ms ease, background-color 160ms ease;
}
.tip-btn:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
.tip-btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  box-shadow: none;
}
.tip-body {
  position: absolute; top: calc(100% + 7px); left: -6px;
  z-index: 20;                 /* above .console-nav's sticky 5 */
  width: max-content; max-width: 320px;
  font-family: var(--font-body); font-size: 11.5px; font-weight: 400;
  line-height: 1.5; letter-spacing: 0; text-transform: none;
  color: var(--ink-soft); background: var(--card);
  border: 1px solid var(--hair-strong); border-radius: 6px;
  padding: 8px 11px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.10);
  /* visibility, not display: a display:none element is not animatable and,
     more importantly, is removed from the accessibility tree even while
     aria-describedby points at it. */
  visibility: hidden; opacity: 0;
  transition: opacity 140ms ease, visibility 140ms ease;
}
.tip:hover .tip-body, .tip:focus-within .tip-body { visibility: visible; opacity: 1; }
/* Anchored LEFT, not centred. Every `?` on this console sits immediately after
   a heading or a label at the left edge of a card, so a centred bubble would
   hang off the left of the page and a right-anchored one would hang off the
   right of the narrow settings column. */

/* The `?` glyph is a button. Same rule. */
.tip-btn { color: var(--warm-1); }

/* ...and being a button, it is 15x15, which is a third of a finger. Same
   remedy as the header nav in style.css: extend the hit area, move no pixel.
   Nothing sits beside a tip, and elementFromPoint confirms it takes no
   click from anything else. */
.tip-btn { position: relative; }
.tip-btn::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}

/* THE TOOLTIP SCROLLED THE PAGE SIDEWAYS WHILE INVISIBLE. Moved here from
   admin.css's numbered "Narrow viewports" list with the rest of the `?`
   affordance, and deliberately UNNUMBERED now: every page with a tip needs
   it, not just the console. .tip-body is
   'visibility: hidden', which still occupies layout and still counts toward
   the scrollable overflow area -- it is 'display' that would remove it, and
   display:none was deliberately rejected right where the rule is written,
   because it drops the element out of the accessibility tree that
   aria-describedby points into. So a 320px popup anchored near the right of
   a 371px screen took /admin/settings to a scrollWidth of 592px and
   /admin/recordings to 450px, with nothing on screen to explain why the page
   slid. Clamping the width fixes the scroll; flipping the anchor is what
   makes the thing readable once it opens, since a tooltip that renders past
   the right edge is not a tooltip. */
@media (max-width: 640px) {
  .tip-body {
    left: auto;
    right: 0;
    max-width: min(320px, calc(100vw - 32px));
  }
}

/* ---- Filter chips (moved from public/manager.css in phase 7, B11) ---------
   SCOPED UNDER .chips (I2). views/transcribe.html loads this file and gives
   every inline tag in the editor's preview class="chip" with no .chips
   ancestor; a bare .chip rule here would put a 34px min-height and a border on
   every tag. Every chip that should look like one sits inside a .chips. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips .chip { font: inherit; font-size: 0.8rem; font-weight: 600; padding: 6px 12px; min-height: 34px; cursor: pointer;
  border: 1.5px solid var(--hair); border-radius: 999px; background: var(--card); color: var(--ink-soft); }
.chips .chip:hover { background: var(--card-hover); }
.chips .chip[aria-pressed="true"] { border-color: var(--brand); background: var(--brand-soft); color: var(--brand-deep); }
.chips .chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.chips .chip .chip-count { font-family: var(--font-mono); font-weight: 500; opacity: 0.8; margin-left: 5px; }

/* ---- Numbered pages (phase 7, R4) ---------------------------------------
   Painted from NavCommon.pagerModel by Users, Recordings and Transcriptions. */
.pager { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin: 12px 0 2px; }
.pager .pager-item { font: inherit; font-size: 0.82rem; min-width: 34px; min-height: 34px; padding: 4px 10px; cursor: pointer;
  border: 1px solid var(--hair); border-radius: 8px; background: var(--card); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.pager .pager-item:hover:not(:disabled) { background: var(--card-hover); }
.pager .pager-item[aria-current="page"] { border-color: var(--brand); background: var(--brand-soft); color: var(--brand-deep); }
.pager .pager-item:disabled { cursor: default; opacity: 0.55; }
.pager .pager-item:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.pager .pager-gap { padding: 0 4px; color: var(--ink-soft); }

/* ---- GUIDED MODE (task 10) -----------------------------------------------
   The instructions modal, the question card, its anchors and its two Arabic
   buttons. Spec: docs/superpowers/specs/2026-09-24-msa-prompt-collection-design.md
   D2 (the card is inline and survives the recording). D8 (right-to-left
   scoped to these blocks) is superseded by the contributor restyle's ruling
   C-R3: the page follows the reader's language; the card and the sheet keep a
   static dir="rtl" because they carry the project's Arabic.

   BOARD C3 (2026-10-10) IS THE DRAWING NOW: the question card, the rules
   sheet, the big microphone, the ruler, the live clock, the stop ring and the
   saved screen. Every pinned rule below is edited where it lives (restyle spec
   §3); nothing overrides it from the contributor section.

   dir="rtl" IS ON THE ELEMENTS, NOT IN HERE. `direction` in a stylesheet moves
   the text but NOT the DOM-order semantics the browser needs for bidi
   reordering of mixed Arabic/Latin runs, and a question that names a Latin
   brand is not hypothetical. The attribute does both; these rules only have to
   avoid assuming a side -- so padding and margins below use logical properties
   (inline-start / inline-end), which flip with the attribute for free.

   EVERY MEASUREMENT HERE IS FOR A 360px PHONE HELD IN ONE HAND. Nearly every
   recruit is on one, this screen is the entire product to them, and the tap
   targets are 48px because 44 is the floor rather than the target. */

/* BOARD C3: ONE CARD (.q). The 2026-09-24 board stacked a cobalt question block
   on a paper anchor block ("two materials"); board C3 draws one card -- the
   card surface, a hairline, 18px corners, 18px in, 12px between -- with the
   caption «السؤال» over the question, the ideas as chips under their own
   caption, and «سؤال آخر» as a link. The question still cannot be mistaken
   for the ideas: it is the largest, boldest thing on the screen, and the
   ideas are small chips on a fill. */
.prompt-card {
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 18px;
  padding: 18px;
  margin: 0 0 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* THE QUESTION'S BOX: no surface of its own any more (board C3), only the
   caption and the question. Kept, with no id, because the tour spotlights it. */
.prompt-qblock {
  background: none;
  border-radius: 10px;
  padding: 0;
}

/* THE TWO CAPTIONS (owner, 2026-09-24). A question and an anchor stacked with
   nothing between them read as TWO QUESTIONS, and a recruit cannot tell which
   one is the task. These say which is which -- and they are CAPTIONS, not
   headings: 11px, uppercase-weight without uppercase (Arabic has no case), and
   in --warm-2 so they sit UNDER the text they label in the reading order of the
   eye as well as of the page. A caption that competed with the question would
   be the same defect with more text on the screen. */
/* Board C3's .lab: 12.5px, medium, --warm-1. */
.prompt-eyebrow {
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--warm-1);
  margin: 0 0 6px;
  letter-spacing: normal;
}
.prompt-eyebrow-soft { color: var(--warm-1); margin-bottom: 8px; }
/* The question's caption, on the card like every other caption now (it was
   white on the cobalt block). (0,2,0), kept so the two captions stay one. */
.prompt-qblock .prompt-eyebrow { color: var(--warm-1); }

/* THE QUESTION. The largest thing on the screen, because it is the one thing
   that has to be read from arm's length while the speaker is talking, and
   because a person who loses the question mid-recording stops. Board C3's h2
   is 21px bold; this stays at 22px, the floor test/prompt-guided-ui.test.js
   holds for arm's length, and at the board's 1.45 leading plus room for the
   diacritics (1.6). Ink on the card -- it was white on cobalt. */
.prompt-question {
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1.6;
  font-weight: 700;
  color: var(--ink);
  margin: 0;
}
.prompt-question:lang(ar) { font-family: var(--font-ar-head); letter-spacing: 0; }

/* THE ANCHORS, ON A SURFACE OF THEIR OWN. They exist for minute four, when the
   speaker dries up -- they are a rescue, not a second task, and the owner's
   report is that nobody could tell. The tint and the inset are what carry that
   in a two-second glance; the caption above only names it. Quieter than the
   question and still comfortably readable: this is what somebody reads while
   talking, so it is not a footnote either.

   THE WHOLE BLOCK IS HIDDEN when a question has no anchors -- usableQuestion
   says such a question is still a question -- so there is never a labelled
   empty box. public/app.js owns that one write.

   BOARD C3: THE IDEAS ARE CHIPS (.anc) -- 13px words on the --fill tint, 8px
   corners, 6px apart -- under their caption, inside the one card. The block
   is the caption plus the chip row; it has no surface of its own. */
.prompt-anchors-block {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}
/* A flex row of chips; no list marker at all, so nothing can land on the
   wrong side of an rtl list. */
.prompt-anchors {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.prompt-anchors li {
  margin: 0;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--fill);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
}

/* WHY THE PRESS IS BUSY OR REFUSED. Same job as #startHint, in the card, so a
   refusal is never off-screen from the control that caused it. */
.prompt-hint {
  font-size: 13px;
  line-height: 1.5;
  color: var(--warm-1);
  margin-top: 0;
}

/* «سؤال آخر» -- AND IT MAY NEVER LOOK LIKE THE RECORD BUTTON (owner ruling,
   2026-09-24). It was a full-width 48px bordered button directly under a
   full-width 52px bordered button, which is two primaries by any reading. It is
   an ESCAPE from a question somebody does not want, not a second offer, so it
   is now a quiet underlined line with a 16px icon, sized to its words and
   sitting with the card rather than under a thumb.

   STILL 44px TALL. It is a tap target on a phone whatever it looks like; the
   height comes from padding around a 14px line, so the target is bigger than
   the ink.

   `button.action.prompt-skip` IS (0,2,1) AND THAT IS LOAD-BEARING. `button.action`
   is (0,1,1) and would otherwise win on width, background, border and colour --
   a full-width blue bar. This block is near the END of the file, so where
   specificity ties with an earlier rule, source order decides for this one.
   letter-spacing is reset: tracking applied to Arabic pulls apart letters that
   are meant to join.

   BOARD C3 draws it as a link in the card: 14px, semibold, --brand, no
   underline. Still 44px tall and sized to its words, still not a primary. */
button.action.prompt-skip {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 6px;
  width: auto;
  min-height: 44px;
  margin: -10px 0 -12px;
  padding: 10px 2px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--brand);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: normal;
  text-transform: none;
  white-space: normal;
  text-decoration: none;
}
button.action.prompt-skip:hover:not(:disabled) {
  background: transparent;
  color: var(--brand-deep);
}
/* Board C3's link carries no glyph; the icon stays in the markup, undrawn. */
button.action.prompt-skip .ctl-icon { display: none; align-items: center; }
button.action.prompt-skip .ctl-icon svg { width: 16px; height: 16px; }

/* ---- The rules sheet ------------------------------------------------------
   BOARD'S ANSWER TO "WHERE DO INTERRUPTIVE THINGS GO": a bottom sheet over the
   screen you were on, which is what every phone app a recruit already uses
   does. Same overlay mechanics as #helpModal (a `.show` class over a `hidden`
   attribute, with a reflow between) and NO close control -- the one button
   dismisses it and the acknowledgement is stored against the person.

   IT IS A SHEET AT EVERY WIDTH NOW, not only under 480px. A centred dialog on a
   desktop and a sheet on a phone is two designs; this is one, docked to the
   bottom and capped at 460px so it reads as a panel rather than a stretched
   phone on a laptop.

   A COLUMN WITH ONE SCROLLING CHILD. Ten rules do not fit: the list scrolls and
   the foot -- consent, button, reason -- is `flex: none`, so the thing that
   unlocks the sheet can never be the thing below the fold. That was a real
   defect in the reference mockups, found by measuring. */
#promptBackdrop {
  position: fixed;
  inset: 0;
  background: rgba(26, 26, 26, 0.42);
  opacity: 0;
  transition: opacity 240ms ease;
  z-index: 42;
}
#promptBackdrop.show { opacity: 1; }

.prompt-modal {
  position: fixed;
  left: 50%;
  top: auto;
  bottom: 0;
  transform: translate(-50%, 18px);
  width: min(460px, 100vw);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  /* Board C3's .sheet: the card surface, 26px top corners. */
  background: var(--card);
  border: 1px solid var(--hair);
  border-bottom: 0;
  border-radius: 26px 26px 0 0;
  box-shadow: 0 -14px 44px rgba(0, 0, 0, 0.3);
  padding: 0;
  z-index: 43;
  opacity: 0;
  transition: opacity 240ms ease, transform 240ms ease;
}
.prompt-modal.show {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* The grab handle. It is decoration and it is honest decoration: it says "this
   is a sheet" in the vocabulary of every phone, which is the whole of Board's
   argument. aria-hidden in the markup, so it is not announced. */
.prompt-grab {
  flex: none;
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: var(--input-line);
  margin: 20px auto 6px;
}
.prompt-modal-title {
  flex: none;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 700;
  line-height: 1.45;
  margin: 8px 18px 0;
  color: var(--ink);
}
.prompt-modal-title:lang(ar) { font-family: var(--font-ar-head); letter-spacing: 0; }
.prompt-sheet-sub {
  flex: none;
  margin: 0 18px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--warm-1);
}

/* THE CHIP ROW -- board C3's segmented control (.seg): one --fill track, 12px
   corners, 4px in, and the group you are on lifted onto the card with a
   hairline ring. 44px is the tap floor and each segment is 44. A single-group
   sheet hides this row entirely: one chip is not a choice. */
.prompt-chips {
  flex: none;
  display: flex;
  gap: 6px;
  margin: 14px 18px 4px;
  padding: 4px;
  border-radius: 12px;
  background: var(--fill);
}
.prompt-chip {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  padding: 7px 4px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--warm-1);
  font: inherit;
  font-size: 13px;
  line-height: 1.3;
  cursor: pointer;
}
.prompt-chip[aria-pressed="true"] {
  background: var(--card);
  color: var(--ink);
  font-weight: 600;
  box-shadow: 0 0 0 1px var(--hair);
}
.prompt-chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* THE LIST IS THE ONE SCROLLING CHILD, so the consent and the button below it
   can never fall under the fold. */
.prompt-modal-list {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  list-style: none;
  margin: 0;
  padding: 10px 18px 4px;
}
/* A RULE IS A TITLE AND A REASON, not one long sentence. Ten equal sentences at
   360px is a wall; a three-word title with its reason under it is scannable,
   which is the only way ten rules get read at all. Board C3's .rl: the number
   tile, then the title over its reason; no rules between rows. */
.prompt-modal-list li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 7px 0;
  margin: 0;
  border-bottom: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-soft);
}
.prompt-modal-list li:last-child { border-bottom: 0; }
.prompt-modal-list li b {
  display: block;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--ink);
}
.prompt-modal-list li span {
  display: block;
  font-size: 13px;
  line-height: 1.55;
  color: var(--warm-1);
}
/* The number in its cobalt square. A drawn glyph per rule would be ten inline
   SVGs built in JavaScript; the number says the same thing -- there are ten of
   these and this is which one -- and it is a text node, which is the discipline
   this page holds everywhere. Isolated so a Western digit inside an rtl
   paragraph cannot be reordered with anything beside it.

   `.prompt-modal-list li .prompt-rule-n` AND NOT `.prompt-rule-n`, AND THAT IS
   A DEFECT THE OWNER MET IN PRODUCTION: "dark numbers inside a dark blue
   square, it looks horrible". The badge is a <span>, so
   `.prompt-modal-list li span` -- the rule just above this note -- MATCHES IT
   at (0,1,2) and beat the bare class at (0,1,0). Source order never came into
   it -- specificity decided first -- and every rule number was painted
   --warm-1 on --brand.
   MEASURED off a rendered sheet at 1.10:1, against a 4.5 floor; white on
   cobalt is 7.91. Raising this selector to (0,2,1) is what puts the
   declaration back in front, without !important and without touching the rule
   that dresses the reason line under each title.

   `.prompt-modal-list li.prompt-strong .prompt-rule-n` below is (0,3,1) and
   still outranks this one, so the tenth rule keeps its red square.

   BOARD C3's tile (.rl em): 26px, 8px corners, --brand-deep on --brand-tint --
   a LIGHT square with a dark number, so "dark numbers inside a dark blue
   square" cannot come back (about 9:1). */
.prompt-modal-list li .prompt-rule-n {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--brand-tint);
  color: var(--brand-deep);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  line-height: 26px;
  text-align: center;
  direction: ltr;
  unicode-bidi: isolate;
  margin-top: 1px;
}
/* THE STAY-ON-THIS-PAGE LINE, AND IT IS NOT DECORATION (spec §3.1, senior
   review S12). A phone that backgrounds the browser suspends the recorder and
   the session truncates SILENTLY -- the speaker believes they talked for twenty
   minutes and the file holds four. Nothing in this platform detects that and
   nothing tells them, so this sentence is the only defence there is. */
.prompt-modal-list li.prompt-strong b { color: var(--danger); }
.prompt-modal-list li.prompt-strong .prompt-rule-n { background: var(--danger); }
/* Board C3 draws rule 10 like the others; it keeps its red (S12, above), so
   its number is light on that square. */
.prompt-modal-list li.prompt-strong .prompt-rule-n { color: var(--on-fill); }

/* THE FOOT. `flex: none`, so the control that unlocks this sheet is never the
   thing below the fold -- which is exactly the defect measuring found in the
   reference mockups, where an unticked consent box could only be reached by
   scrolling a list somebody had no reason to scroll. */
.prompt-sheet-foot {
  flex: none;
  padding: 10px 18px max(18px, env(safe-area-inset-bottom));
  border-top: 0;
  background: var(--card);
}

/* «المجموعة التالية» -- the only control on groups one and two, and it is a
   primary, because moving on IS the one action there. The consent and the start
   button are not on the screen yet. */
button.action.prompt-group-next {
  display: block;
  width: 100%;
  min-height: 50px;
  padding: 13px 20px;
  border-radius: 14px;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
  margin: 0;
}

/* THE CONSENT. The LABEL is the tap target (50px, past the 44 floor) and the
   native checkbox is visually hidden behind a drawn box -- hidden, never
   `display: none` and never removed, because it is what carries the keyboard,
   the screen reader and the form semantics.
   Board C3's .ck: a boxed row -- --brand-line hairline, 12px corners, the
   --brand-wash fill, 14px medium words, a 20px box. */
.prompt-consent {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 50px;
  padding: 12px 14px;
  margin: 0 0 14px;
  border: 1px solid var(--brand-line);
  border-radius: 12px;
  background: var(--brand-wash);
  cursor: pointer;
}
.prompt-consent-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.prompt-consent-box {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 2px solid var(--hair-strong);
  border-radius: 5px;
  background: var(--card);
  color: transparent;
}
.prompt-consent-input:checked + .prompt-consent-box {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-fill);
}
.prompt-consent-input:focus-visible + .prompt-consent-box {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.prompt-consent-word {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
}

/* WHY THE BUTTON IS LOCKED. Never a dead control that ignores a press: this is
   the sentence RoomLogic.consentGate names, and it is on screen for as long as
   the button is shut. */
.prompt-gate-hint {
  margin: 9px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--warm-1);
  text-align: center;
}
button.action.prompt-ack {
  display: block;
  width: 100%;
  min-height: 50px;
  padding: 13px 20px;
  border-radius: 14px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
  margin: 0;
}
/* VISIBLY LOCKED, NOT MERELY INERT. The platform's `:disabled` is an opacity
   drop, which on a filled blue button still reads as a button somebody should
   be able to press. Board C3 draws the shut button as a solid grey slab with
   white words (.b[disabled]) -- no blue left in it, no shadow -- and
   #promptGateHint under it says why. `button.action` is (0,1,1);
   `button.action.prompt-ack:disabled` is (0,3,1) and wins. */
button.action.prompt-ack:disabled {
  background: var(--input-line);
  color: var(--on-fill);
  border: 0;
  box-shadow: none;
  opacity: 1;
}

/* AN ICON *AND* ITS WORD (room redesign, 2026-09-24). The first pass made these
   controls icon-ONLY and the owner's verdict was "buttons should indicate
   something not a fuck ass text only". He is right: End session and Leave were
   two full-width red-outlined buttons distinguishable only by a power glyph and
   a door glyph, which is a puzzle rather than a screen.

   THREE CHILDREN PER BUTTON, ONE RULE EACH. The icon and the painted .ctl-name
   are display:none until the button is put in `ctl-guided` -- which renderUi
   does for GUIDED projects and for nothing else -- so the four live collections
   render byte for byte what they rendered yesterday: the English word, no icon,
   no new box. In guided mode the English word hides and the painted one shows,
   which is what keeps test/room-markup-wiring.test.js's English literals true
   while the screen reads Arabic.

   THE ACCESSIBLE NAME DOES NOT LIVE IN THE PIXELS EITHER. aria-label carries it
   as well, in the reader's language from public/i18n.js, because .ctl-name is
   painted and a control whose paint has not run must still have a name. */
.ctl-icon { display: none; }
.ctl-word { display: inline; }
.ctl-name { display: none; }
/* Board C3's .b: 50px, 14px corners, 16px semibold. */
button.action.ctl-guided {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 50px;
  padding: 12px 20px;
  border-radius: 14px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
}
button.action.ctl-guided .ctl-word { display: none; }
button.action.ctl-guided .ctl-name { display: inline; }
button.action.ctl-guided .ctl-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.ctl-icon svg { display: block; width: 22px; height: 22px; }

/* THE RED SQUARE, AND THE SLOT IT HAS TO FILL. While a recording runs THIS is
   the one primary on the screen -- RoomLogic.guidedPrimary hands it the slot.

   BOARD C3 DRAWS IT AS THE RING: a 96px circle on the card with a 3px red rim
   and a 30px red square inside (.stop), the same round control as the 112px
   microphone it replaces, so the slot keeps its shape when it changes job.
   «الإنتهاء من التسجيل» stays UNDER the ring -- the owner asked for the word
   beside the red button, and the board's aria-label alone would take it away.
   The button itself is a transparent column the width of the bar, so the
   whole of it is the tap target.

   `button.action.stop` is (0,2,1) and transparent-with-an-ink-border; (0,3,1)
   here outranks it, and the :hover pair below outranks button.action.stop:hover,
   which is (0,3,1) too and would otherwise tie and win on source order. */
button.action.stop.ctl-guided {
  flex-direction: column;
  background: transparent;
  color: var(--danger);
  border: 0;
  min-height: 96px;
  border-radius: 14px;
  padding: 4px 16px;
  font-size: 15px;
  font-weight: 600;
  gap: 8px;
  box-shadow: none;
}
button.action.stop.ctl-guided .ctl-icon {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 3px solid var(--danger);
  background: var(--card);
  color: var(--danger);
}
button.action.stop.ctl-guided .ctl-icon svg { width: 60px; height: 60px; }
button.action.stop.ctl-guided:hover:not(:disabled),
button.action.stop.ctl-guided:active:not(:disabled) {
  background: transparent;
  color: var(--danger-ink);
}
button.action.stop.ctl-guided:hover:not(:disabled) .ctl-icon { background: var(--danger-tint); }

/* THE WAYS OUT ARE NOT THE WAY ON. End session and Leave are real controls and
   they stay on the screen, but a recruit has ONE action and these are not it:
   44px, 13px, a 16px glyph. `button.action.ghost.ctl-guided` is (0,3,1) and
   outranks the (0,2,1) base above. #retryFinalizeBtn is deliberately NOT in
   here -- when a finalize has failed, retrying it IS the one action. */
button.action.ghost.ctl-guided,
button.action.danger.ctl-guided {
  min-height: 44px;
  padding: 10px 14px;
  border-radius: 12px;
  background: transparent;
  font-size: 14.5px;
  gap: 6px;
}
/* The board C4 dock's pair, so the two rooms read as one: Leave stays red and
   obvious (D57, from the base .danger); End session and Cancel sit on a
   hairline. */
button.action.danger.ctl-guided:not(.ghost) { color: var(--danger-ink); border: 1px solid var(--hair); }
button.action.ghost.ctl-guided:not(.danger) { color: var(--ink); border: 1px solid var(--hair); }
button.action.ghost.ctl-guided .ctl-icon svg,
button.action.danger.ctl-guided .ctl-icon svg { width: 16px; height: 16px; }

/* DO NOT CLOSE THIS PAGE (phone fix, problem 2). The only element in this
   product that is allowed to shout, because it is the only one whose moment is
   unrecoverable: between the stop press and the merge, the audio exists only in
   this tab.

   RED ON A RED WASH WITH A RED RULE, on the --danger pair that already carries
   #micBanner and the error status dot, so no new colour enters the palette. The
   weight and the 15px size are deliberate: at 360px this sentence is three lines
   of Arabic, and it has to be readable at arm's length by somebody who is
   reaching for the home button.

   The inline start border rather than a full box: the same shape #micBanner
   uses, so the two read as one family of "stop and look at this". */
/* Board C4's banner shape (restyle 2026-10-10): 14px radius and padding. The
   board draws this one gold; it stays RED -- red is the one moment audio can
   be lost, and the pins above say so. */
.saving-warning {
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger);
  border-radius: 14px;
  padding: 14px;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 600;
}

/* HOW FAR THE SAVE HAS GOT. Sits under .saving-warning and is deliberately
   quieter than it: the red bar is the instruction ("do not close this page"),
   this is the evidence that something is still happening. Cobalt, because a
   second red block would teach people to read neither.

   The counts are Latin digits inside an RTL sentence, so the label isolates
   them -- without it "180 من 203" reorders on exactly the phones this is for. */
.upload-progress {
  margin-block-start: 8px;
}
.upload-progress-track {
  height: 6px;
  border-radius: 999px;
  background: var(--hair);
  overflow: hidden;
}
.upload-progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: var(--brand);
  transition: width 200ms linear;
}
.upload-progress-label {
  margin: 6px 0 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--warm-1);
  unicode-bidi: isolate;
}
.upload-progress-warn {
  margin: 4px 0 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--danger);
  unicode-bidi: isolate;
}
/* A STALLED SAVE MUST STOP LOOKING LIKE A RUNNING ONE. A bar frozen at 89%
   under the word "saving" is the same lie in a nicer typeface. */
.upload-progress.is-stalled .upload-progress-fill {
  background: var(--danger);
  transition: none;
}
/* THE HEADER CHUNK IS THE WHOLE FILE. Losing it does not cost three seconds, it
   costs the recording — so when it is at risk the warning stops being a footnote
   and takes the weight of the block. This is the one message a person must read
   while they can still act on it. */
.upload-progress.is-head-risk .upload-progress-warn {
  font-size: 15px;
  line-height: 1.55;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--danger);
  border-inline-start: 4px solid var(--danger);
  background: var(--danger-soft);
}
.upload-progress.is-head-risk .upload-progress-fill {
  background: var(--danger);
}

/* THE ONE PRIMARY (room redesign, 2026-09-24), drawn to BOARD C3 in its two
   faces. SAVED («سؤال جديد», data-icon="next"): the board's .b -- full width,
   50px, 14px corners, 16px semibold, cobalt. READY (data-icon="mic"): the
   board's big round microphone -- see the [data-icon="mic"] rules below.

   TWO ICONS IN THE MARKUP AND ONE DRAWN. `data-icon` is written by paintGuided
   from RoomLogic.guidedPrimary; swapping an <svg> in JavaScript would be
   innerHTML on a control, and nothing in public/app.js does that.

   `direction: rtl` HERE RATHER THAN A dir ATTRIBUTE ON THE MARKUP:
   test/prompt-guided-ui.test.js pins the markup's dir="rtl" count at two. Its
   words are the PROJECT'S and arrive in Arabic whatever language the reader
   chose, which is why this one is static while the five controls above are
   stamped at paint time.

   `box-shadow: none` IS DECLARED, NOT OMITTED: the board's .b has no lip, and
   this declaration at (0,2,1) is still what the tour's win-back rule below
   has to outrank -- see the note there.

   `button.action.prompt-primary`, not `.prompt-primary`: (0,2,1) beats
   `button.action` (0,1,1), which otherwise wins on font-size, padding and
   margin. The rule that caught that one on the card's own button. */
button.action.prompt-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 50px;
  padding: 12px 20px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
  direction: rtl;
  text-align: center;
  border-radius: 14px;
  margin: 0;
  box-shadow: none;
}
button.action.prompt-primary .ctl-icon { display: none; flex: none; }
button.action.prompt-primary[data-icon="mic"] .ctl-mic,
button.action.prompt-primary[data-icon="next"] .ctl-next {
  display: inline-flex;
  align-items: center;
}
button.action.prompt-primary .ctl-icon svg { width: 22px; height: 22px; }
button.action.prompt-primary:disabled { box-shadow: none; }
/* READY: THE BOARD'S MICROPHONE. The button is a transparent column the width
   of the bar -- the whole of it is the tap target -- holding a 112px cobalt
   circle with a 10px --brand-tint halo, and the project's words under it at
   15px medium («اضغط عندما تكون جاهزاً للتسجيل»). */
button.action.prompt-primary[data-icon="mic"] {
  flex-direction: column;
  gap: 16px;
  padding: 14px 16px 4px;
  background: transparent;
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
}
button.action.prompt-primary[data-icon="mic"]:hover:not(:disabled) { background: transparent; }
button.action.prompt-primary[data-icon="mic"] .ctl-mic {
  justify-content: center;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  background: var(--brand);
  color: var(--on-fill);
  box-shadow: 0 0 0 10px var(--brand-tint);
}
button.action.prompt-primary[data-icon="mic"] .ctl-mic svg { width: 44px; height: 44px; }
button.action.prompt-primary[data-icon="mic"]:hover:not(:disabled) .ctl-mic { background: var(--brand-deep); }
/* Locked while its reads answer (one round trip): the circle goes the board's
   disabled grey, the words stay. */
button.action.prompt-primary[data-icon="mic"]:disabled { opacity: 1; }
button.action.prompt-primary[data-icon="mic"]:disabled .ctl-mic { background: var(--input-line); box-shadow: 0 0 0 10px var(--fill); }

/* THE PRODUCT'S NAME, THREE TIMES, TO SOMEBODY WHO CAME HERE TO TALK. The owner,
   reading his own recording screen on a phone: «ROOM · 314033ae», "NEW ROOM",
   the four nav tabs and "Aqlama Tech · stereo recording prototype" are noise to
   a recruit mid-task. One statement of where they are is orientation, three is
   clutter -- and since board C3 that one statement is the PROJECT'S name in the
   bar (#roomProjectLabel), beside the back chevron, not the brand mark.

   IN CSS, OFF ONE CLASS ON <body>, AND NOT WITH `.hidden = true` WRITES.
   #userNav is painted by /user-chrome.js, which public/app.js does not own and
   which would put it straight back on its own schedule. A rule cannot be
   out-raced. */
body.guided-screen #userNav,
body.guided-screen .site-footer,
body.guided-screen #roomIdLabel,
/* AND THE "?" THAT OPENS AN ENGLISH POP-UP ABOUT INVITE LINKS AND "Speaker 1"
   (found by measuring, 2026-09-24). public/app.js hides the pop-up itself in
   the guided block; a link left on the screen that a paint 500 ms later undoes
   is a control that does nothing, which is worse than one that is not there.
   The rules sheet is this screen's how-it-works. */
body.guided-screen #helpLink { display: none; }

/* THE HEADER CHIP'S Log out IS A 17px TAP TARGET (measured at 360px). It is
   inline text in a chip on every page in this product; on the guided screen it
   is one of only two things in the header, in front of somebody on a phone, so
   it gets the floor here -- SCOPED to this screen, because the same rule applied
   globally would re-space six console pages nobody asked me to touch. */
body.guided-screen #logoutLink {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
}
/* THE BAR (board C3 .bar): the back chevron and the project's name, 15px
   semibold. The wordmark link navigated AWAY from somebody's recording from
   the one spot a thumb reaches the header; board C3 has no brand mark. The
   room id is already gone (above); the project line under it becomes the
   title (the room section of the contributor block sizes it). */
body.guided-screen .brand-mark { display: none; }

/* THE CLOCK, WHERE THE ACTION IS (board C3, "Recording"). #statusRow is written
   for an operator reading a room; while a guided recording runs it has one job
   -- how long have I been talking -- so the row becomes the board's live pill
   («يسجّل», the dot inside it) over a 52px clock with «من 30:00» under it, on
   no surface of its own. `#statusRow.guided-live` is (1,1,0), the same weight
   as `#statusRow[data-state="recording"]` above, so this block MUST stay below
   it: they tie and source order decides. */
#statusRow.guided-live {
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  padding: 4px 0 0;
  background: transparent;
  border-color: transparent;
  text-align: center;
}
/* The pill is the sentence; the dot sits over its start padding. */
#statusRow.guided-live .status-text {
  flex: none;
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding-block: 0;
  padding-inline: 26px 12px;
  border-radius: 999px;
  background: var(--danger);
  color: var(--on-fill);
  font-size: 13px;
  font-weight: 600;
}
#statusRow.guided-live .status-dot {
  position: relative;
  z-index: 1;
  width: 8px;
  height: 8px;
  margin-inline-end: -18px;
  background: var(--card);
}
#statusRow.guided-live .status-timer {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin: 12px 0 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}
#statusRow.guided-live .timer-now {
  font-family: var(--font-mono);
  font-size: 52px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  direction: ltr;
}
#statusRow.guided-live .timer-cap {
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--warm-1);
}

/* THE SAVED SCREEN'S HEADING (board C3, "Saved"): the status card on a guided
   page in S.DONE is drawn as the board's moss tick over «تم حفظ التسجيل.» at
   25px, with #promptSavedNext under it. Read off data-state, which renderUi
   already writes; the tick is the status dot, grown and given a drawn check. */
body.guided-screen #statusRow[data-state="done"] {
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  padding: 6px 0 0;
  background: transparent;
  border-color: transparent;
}
body.guided-screen #statusRow[data-state="done"] .status-dot {
  position: relative;
  width: 64px;
  height: 64px;
  background: var(--moss-tint);
}
body.guided-screen #statusRow[data-state="done"] .status-dot::after {
  content: '';
  position: absolute;
  inset-inline-start: 24px;
  top: 17px;
  width: 13px;
  height: 24px;
  border: solid var(--moss);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}
body.guided-screen #statusRow[data-state="done"] .status-text {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 700;
  line-height: 1.3;
}
body.guided-screen #statusRow[data-state="done"] .status-text:lang(ar) { font-family: var(--font-ar-head); }
.prompt-saved-next {
  margin: 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--warm-1);
}

/* WHILE RECORDING, THE CARD TIGHTENS AND STAYS (board C3, "Recording"). The
   clock, the ruler, the stay chip and the stop ring take the dock's height, and
   D2's whole point is that the question and its ideas are still on screen at
   minute four. So the card does what the board's does: 14/16 in, 6px apart,
   the question at 17px, the ideas' caption dropped -- the chips stay. Keyed
   off #statusRow.guided-live, which paintGuided writes for exactly the
   recording; the card is back to full size the moment it ends. */
body.guided-screen:has(#statusRow.guided-live) .prompt-card { padding: 14px 16px; gap: 6px; }
body.guided-screen:has(#statusRow.guided-live) .prompt-question { font-size: 17px; line-height: 1.5; }
body.guided-screen:has(#statusRow.guided-live) .prompt-eyebrow-soft { display: none; }
/* A SHORT PHONE (640px and under 700 tall) gets a tighter dock while
   recording, so the card is not pushed under it: a 40px clock, a 76px ring. */
@media (max-height: 700px) {
  #statusRow.guided-live .status-timer { margin-top: 8px; gap: 4px; }
  #statusRow.guided-live .timer-now { font-size: 40px; }
  button.action.stop.ctl-guided { min-height: 76px; gap: 4px; }
  button.action.stop.ctl-guided .ctl-icon { width: 76px; height: 76px; }
  button.action.stop.ctl-guided .ctl-icon svg { width: 48px; height: 48px; }
  body.guided-screen #actionBar:has(#statusRow.guided-live) { gap: 8px; padding-top: 10px; padding-bottom: max(12px, env(safe-area-inset-bottom)); }
  body.guided-screen #actionBar:has(> #shortStopBar:not([hidden])) .short-stop { padding: 10px 14px; font-size: 15px; }
}
/* AND WHILE THE TOO-SHORT ASK IS UP (review M1), at every height: the ask
   itself says what the coaching line, the stay chip and the Leave hint say less
   urgently, so they step aside for it and the question stays above the dock
   (measured: 360x640 and 360x760). Each comes back the moment the ask closes;
   the stop ring and «واصل التسجيل» are untouched. */
body.guided-screen #actionBar:has(> #shortStopBar:not([hidden])) :is(#promptStayBar, #lengthLine, #leaveHint) { display: none; }

/* THE SHEET. MUST STAY BELOW .prompt-modal.show above: both selectors are one
   class, so specificity ties and SOURCE ORDER decides -- moved up, this block
   loses and the sheet slides half off the screen. Both transforms are
   overridden, not only the base one, for the same reason #helpModal's copy of
   this note gives. */
/* ---- BOARD: THE TRANSPORT BAR ---------------------------------------------
   "A transport bar at the bottom owns the controls." The bar already exists and
   is already sticky; what Board adds is that it reads as a SURFACE -- the card
   tone, a hairline above it, and a lift -- rather than as the bottom of the
   page. Everything a recruit can do is on it, and the thing they are doing is
   above it.

   GUIDED PROJECTS ONLY, off the one class on <body>. The four live collections
   get the bar exactly as it is today.

   NOTHING HERE MOVES A CONTROL OR CHANGES WHAT IS IN THE BAR. The stop path is
   the one code path on this platform where a mistake destroys audio; this is
   paint on the box around it. */
body.guided-screen #actionBar {
  background: var(--card);
  border-top: 1px solid var(--hair-soft);
  box-shadow: 0 -8px 22px rgba(26, 26, 26, 0.09);
  padding: 14px 18px max(18px, env(safe-area-inset-bottom));
  margin-inline: -16px; /* full bleed: the room's main is 16px in (board C3 dock 14/18/18) */
  gap: 10px;
}
/* BOARD C3'S ORDER, OFF `order` AND NOT OFF THE DOM. The markup order is pinned
   (the saving warning first, the primary above the ways out) and is also the
   focus order; every focusable control below keeps its DOM order. What moves is
   what the eye reads: the clock or the saved heading, then the big button, then
   the ruler, then the stay chip and the confirm, then the stop ring -- and the
   ways out (End session, Leave, their hints) last, in DOM order. */
body.guided-screen #actionBar > * { order: 20; }
body.guided-screen #actionBar > #savingWarning { order: 1; }
body.guided-screen #actionBar > #uploadProgress { order: 2; }
body.guided-screen #actionBar > #statusRow { order: 3; }
body.guided-screen #actionBar > #promptSavedNext { order: 4; }
body.guided-screen #actionBar > #promptBarBtn { order: 5; }
body.guided-screen #actionBar > #lengthCoach { order: 6; }
body.guided-screen #actionBar > #statusHint { order: 7; }
body.guided-screen #actionBar > #promptStayBar { order: 8; }
body.guided-screen #actionBar > #shortStopBar { order: 9; }
body.guided-screen #actionBar > #keepGoingBtn { order: 10; }
body.guided-screen #actionBar > #stopBtn { order: 11; }

/* «ابقَ في هذه الصفحة أثناء التسجيل» -- the tenth rule, on screen for the whole
   recording rather than once in a sheet four minutes earlier. Board C3 draws it
   as a gold chip under the coaching line (.stay): --gold-ink on --gold-tint,
   12px corners, 13.5px semibold, an info glyph. Not red: red belongs to the
   saving warning, and two reds teach somebody to ignore both. Its lang and dir
   are stamped at paint time. */
.prompt-stay {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  background: var(--gold-tint);
  color: var(--gold-ink);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.5;
  text-align: start;
  padding: 10px 12px;
  margin: 0;
  border-radius: 12px;
}
.prompt-stay-icon { flex: none; display: inline-flex; align-items: center; }
.prompt-stay-icon svg { display: block; width: 18px; height: 18px; }
.prompt-stay-word { min-width: 0; }

/* ---- BOARD: THE FIRST-RUN TOUR -------------------------------------------
   THE SPOTLIGHT IS A BOX-SHADOW ON THE TARGET ITSELF, so the cut-out is
   recomputed by LAYOUT: when the Arabic rewraps to another line, when the phone
   rotates, when a keyboard opens. A coordinate-placed overlay needs a resize
   listener to stay honest and drifts off its target the first time one is
   missed. 9999px is larger than any phone in any orientation.

   NO TETHERED ARROW. At 360px there is nowhere to put one, and an arrow that
   must flip for right-to-left is a defect waiting to happen. The explanation
   goes in a sheet at the bottom, where the thumb already is. */
.prompt-spot {
  /* ONE VALUE, READ BY TWO RULES -- see the note under this block. A second
     literal copy of the dim is how the two come to disagree. */
  --spot-dim: 0 0 0 9999px rgba(26, 26, 26, 0.74);
  position: relative;
  z-index: 6;
  border-radius: 14px;
  outline: 2px solid var(--gold);
  outline-offset: 5px;
  box-shadow: var(--spot-dim);
}
/* AND THE THIRD STEP GETS THE SAME DIM, which it did not have (owner,
   2026-09-24: "the last step at the demo, it shows the button but its not like
   the first 2 steps, nothing is shiny, nothing is black, just a pointer").

   IT WAS NOT THE STACKING CONTEXT AND IT WAS NOT A CLIP. Both were suspected
   and both were measured innocent: #actionBar carries no overflow, no
   transform and no filter, nothing on the chain clips, and with the bar at
   z-index 3 the dim has nothing above it to hide behind. It was the CASCADE.
   Step three's target is #promptBarBtn, and `button.action.prompt-primary`
   (0,2,1) carries `box-shadow: 0 2px 0 var(--brand-deep)` -- the button's own
   lip -- which outranks `.prompt-spot` (0,1,0). The dim was never applied at
   all: the computed box-shadow on the spotlit button read
   `rgb(22, 61, 128) 0px 2px 0px 0px`, so the gold outline was the only thing
   on the screen that changed. The same class of defect as the rule number in
   the sheet above, found in the same round.

   (0,2,1) here ties `button.action.prompt-primary` and wins on SOURCE ORDER,
   which is why this rule must stay below it. The `:disabled` half is (0,3,1)
   and outranks `button.action.prompt-primary:disabled`, which sets
   `box-shadow: none` -- the state a record button is in for the one round trip
   its reads are still owed.

   NOTHING ELSE ABOUT THE BUTTON CHANGES. Its radius, its padding and its lip
   in every other state are untouched: this rule carries the dim and nothing
   but the dim, so the cut-out follows the button's own 10px corner exactly as
   the other two steps follow their targets'. */
button.action.prompt-spot,
button.action.prompt-spot:disabled { box-shadow: var(--spot-dim); }
/* Since board C3 the question and the ideas have no surface of their own
   inside the one card, so a spotlit one gets the card's surface and a little
   air -- padding taken back by an equal negative margin, so nothing reflows. */
.prompt-qblock.prompt-spot,
.prompt-anchors-block.prompt-spot { background: var(--card); padding: 6px 8px; margin: -6px -8px; }
/* AND THE STICKY BAR STOPS OUTRANKING THE CUT-OUT. #actionBar is z-index 20 and
   creates a stacking context, so while the question is spotlit the bar would
   otherwise be the one bright thing on a dimmed page. Only during the tour --
   the class comes off with it. */
body.prompt-touring #actionBar { z-index: 3; }
.prompt-tour {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: min(460px, 100vw);
  z-index: 60;
  /* Board C3 draws no tour; the panel takes its sheet's surface and corners
     (.sheet) so the two overlays read as one family. */
  background: var(--card);
  border: 1px solid var(--hair);
  border-bottom: 0;
  border-radius: 26px 26px 0 0;
  box-shadow: 0 -14px 34px rgba(26, 26, 26, 0.34);
  padding: 8px 18px max(18px, env(safe-area-inset-bottom));
}
.prompt-tour-text {
  margin: 4px 0 14px;
  font-size: 16px;
  line-height: 1.8;
  color: var(--ink);
  text-align: start;
}
/* RTL MIRRORING PER ADOBE SPECTRUM, and the two halves go OPPOSITE WAYS: the
   explanation is start-aligned (the right, in Arabic) while the dots and the
   button group sit at the end (the left). The indicator travels WITH the
   buttons, leading at the group's start edge, which is the exact mirror of the
   left-to-right order. */
.prompt-tour-nav {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}
.prompt-tour-dots { display: flex; align-items: center; gap: 7px; }
.prompt-tour-dots i {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--hair-strong);
}
.prompt-tour-dots i.on { width: 22px; border-radius: 5px; background: var(--brand); }
/* THE ONLY CONTROL IN THE PANEL. There is no skip (owner, 2026-09-24): three
   taps is not a thing people need an escape from, and a skip beside this would
   be a second primary on a screen whose whole rule is that there is one.
   `button.action.prompt-tour-next` is (0,3,1) and outranks `button.action`. */
button.action.prompt-tour-next {
  display: flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-width: 112px;
  min-height: 50px;
  padding: 12px 20px;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
  border-radius: 14px;
}

/* ---- THE LENGTH METER (2026-09-24) ----------------------------------------
   THE DEFECT IT CLOSES IS NOT A BUG. 60 production recordings, 16 people, 53
   minutes between them, average 54 seconds, none over five minutes; the
   project's validity floor is 60 seconds, so forty of the sixty count for
   nothing. Every one of them saved correctly. People answer the question and
   stop, because nothing on the screen ever said how long was wanted.

   DIRECTLY UNDER THE CLOCK, IN THE SAME SLOT. #statusRow.guided-live is the red
   band carrying `02:41 / 30:00` at 17px; this is the MEANING of that number,
   and a person glancing at one sees the other without moving their eye.

   THE TWO TICKS ARE STATIC AND SIT EXACTLY ON THE STOPS RoomLogic's COACH_STOPS
   produces: 33% is the minute that makes it count at all, 66% is five minutes.
   `inset-inline-start` rather than `left`, so they stay correct beside a
   sentence that runs the other way.

   THREE TONES, OFF `data-phase`, WRITTEN BY paintLengthCoach:
     short              gold -- the one phase carrying bad news, and it carries
                        a distance with it so the bad news has an end.
     counting           cobalt -- it counts now.
     good / great       moss -- the owner's five minutes, and past fifteen.

   GOLD RATHER THAN RED FOR `short`, and it is the token this product already
   uses for "pending / unconfirmed / TOO SHORT" on the recordings lists -- the
   same fact, the same colour, in the one place it can still be acted on. Red on
   this page belongs to #savingWarning (the one moment audio can actually be
   destroyed) and to the live clock beside it; a third red teaches somebody to
   read none of the three. This is a target not yet reached, not a danger. */
/* BOARD C3's .rule: a 10px --track bar with 5px corners and no border, the
   ticks standing 6px proud of it on both sides, the coaching line under it at
   14.5px. (Board C3 draws the ticks at 3.3% and 16.6% of a LINEAR 30-minute
   bar; they stay on RoomLogic's piecewise stops at 33% and 66% -- the reason is
   in the note above, and test/prompt-guided-ui.test.js holds them there.) */
.length-coach {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.length-track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  background: var(--track);
  border: 0;
  overflow: visible;
}
.length-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 5px;
  background: var(--brand);
}
/* DARK INK AT 45%, NOT A PALETTE TOKEN. The tick has to be legible against the
   PALE EMPTY TRACK and against the SATURATED FILL that passes over it -- gold,
   cobalt and moss in turn -- and --hair-strong disappears on the first of
   those. Measured against all four beds rather than chosen by eye. */
.length-mark {
  position: absolute;
  top: -6px;
  bottom: -6px;
  width: 2px;
  background: rgba(26, 26, 26, 0.5);
}
.length-mark-count { inset-inline-start: 33%; }
.length-mark-good  { inset-inline-start: 66%; }
.length-line {
  margin: 0;
  font-size: 14.5px;
  font-weight: 500;
  line-height: 1.5;
  text-align: start;
  color: var(--ink);
}
/* THE THREE MARKS UNDER THE READY RULER (board C3 .marks, 12px --warm-1). Three
   columns cut where the ticks are, each label ending at its own tick -- so
   «يُحتسب عند 1:00» sits under the 1:00 tick rather than wherever a
   space-between row would put it. */
.length-marks {
  display: grid;
  grid-template-columns: 33% 33% 34%;
  margin: 2px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--warm-1);
}
.length-marks > span { text-align: end; padding-inline-end: 4px; }
.length-marks > span:last-child { padding-inline-end: 0; }
/* THE THREE TONES. `.length-coach[data-phase=…] .length-fill` is (0,2,0) and
   beats the base `.length-fill` (0,1,0), so it may sit anywhere after it --
   which is where it is. NOT inside a media query and NOT inside a
   prefers-reduced-motion block: a rule that only applies on some devices is a
   rule a test can pin while the screen shows something else. */
.length-coach[data-phase="short"] .length-fill { background: var(--gold); }
.length-coach[data-phase="short"] .length-line { color: var(--gold-deep); }
.length-coach[data-phase="counting"] .length-fill { background: var(--brand); }
.length-coach[data-phase="counting"] .length-line { color: var(--brand-deep); }
.length-coach[data-phase="good"] .length-fill,
.length-coach[data-phase="great"] .length-fill { background: var(--moss); }
.length-coach[data-phase="good"] .length-line,
.length-coach[data-phase="great"] .length-line { color: var(--moss); }

/* ---- STOPPING UNDER THE MINUTE -------------------------------------------
   The sentence and the way on. The way OUT is the red button underneath, which
   is never hidden, never disabled and never relabelled while this is open: a
   confirm that cannot be dismissed is a worse defect than the one it fixes, and
   the escape has to be the control the thumb is already on.

   GOLD, THE SAME FAMILY AS `short` ABOVE, because it is the same fact said at
   the moment it matters most. The inline-start border is #micBanner's and
   #savingWarning's shape, so the three read as one family of "stop and look at
   this" rather than three unrelated boxes. */
/* Board C3 ("Too short") sets the sentence as the sheet's heading -- bold, a
   size up -- over «واصل التسجيل». It keeps the gold family here (the meter's
   `short` tone, pinned) on 14px corners, without the old inline-start rule. */
.short-stop {
  color: var(--gold-deep);
  background: var(--gold-soft);
  border: 1px solid var(--gold);
  border-radius: 14px;
  padding: 14px 16px;
  font-size: 16px;
  line-height: 1.55;
  font-weight: 700;
}
/* «واصل التسجيل» -- board C3's .b: the same 50px, 14px-cornered cobalt
   button as «سؤال جديد» and the sheet's buttons.
   `button.action.short-keep` is (0,2,1) and beats `button.action` (0,1,1) on
   font-size, padding and margin -- the rule that caught the card's own button. */
button.action.short-keep {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 50px;
  padding: 12px 20px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  white-space: normal;
  text-align: center;
  border-radius: 14px;
  margin: 0;
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-fill);
  box-shadow: none;
}
.short-keep-icon { flex: none; display: inline-flex; align-items: center; }
.short-keep-icon svg { display: block; width: 22px; height: 22px; }
.short-keep-word { min-width: 0; }

/* THE SHEET. MUST STAY BELOW .prompt-modal.show above: both selectors are one
   class, so specificity ties and SOURCE ORDER decides -- moved up, this block
   loses and the sheet slides half off the screen. Both transforms are
   overridden, not only the base one, for the same reason #helpModal's copy of
   this note gives. */
@media (max-width: 480px) {
  .prompt-modal {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: auto;
    border-radius: 26px 26px 0 0;
    transform: translateY(16px);
  }
  .prompt-modal.show { transform: translateY(0); }
  .prompt-tour {
    left: 0;
    right: 0;
    width: auto;
    transform: none;
    border-radius: 26px 26px 0 0;
  }
  /* A PHONE GETS MORE OF THE QUESTION, NOT LESS. This used to drop it to 19px,
     which is the wrong direction: 360px is the screen this card was designed
     for and every other screen is a bonus. Only the box shrinks. */
  .prompt-card { padding: 16px; }
  .prompt-question { font-size: 22px; }
}

/* 340px AND UNDER -- the iPhone SE (1st gen) and every cheap Android still in
   circulation. He will have people on every size of phone within hours, so the
   floor is measured rather than assumed: the chips are the first thing that
   stops fitting three-across, and the question is the last thing allowed to
   shrink. */
/* A LANDSCAPE PHONE IS 360px TALL, AND THE SHEET STILL HAS TO BE READABLE.
   Measured at 740 x 360: with the full furniture the rules list was an 18px
   window -- one line of a ten-rule list. The furniture goes, not the rules: the
   grab handle and the subtitle are decoration, the title and the chips shrink,
   and the foot tightens to its 44px floors. The consent and the button it
   gates never move out of the foot. */
@media (max-height: 430px) {
  .prompt-modal { max-height: 96vh; }
  .prompt-modal .prompt-grab { display: none; }
  .prompt-sheet-sub { display: none; }
  .prompt-modal-title { font-size: 18px; margin-top: 12px; }
  .prompt-chips { margin-top: 6px; }
  .prompt-chip { min-height: 44px; }
  .prompt-modal-list li { padding: 8px 0; }
  .prompt-consent { min-height: 44px; margin-bottom: 8px; }
  button.action.prompt-ack { min-height: 48px; }
  button.action.prompt-group-next { min-height: 48px; }
  .prompt-sheet-foot { padding-top: 9px; padding-bottom: max(9px, env(safe-area-inset-bottom)); }
  .prompt-gate-hint { margin-top: 6px; }
  /* The tour panel gets the same treatment: it is fixed over a 360px-tall
     screen and its own height is what the spotlight has to clear. */
  .prompt-tour { padding-top: 6px; }
  .prompt-tour .prompt-grab { display: none; }
  .prompt-tour-text { margin: 6px 0 10px; font-size: 15px; line-height: 1.65; }
  button.action.prompt-tour-next { min-height: 44px; padding: 10px 18px; }
}

@media (max-width: 345px) {
  .prompt-chips { gap: 5px; margin-inline: 12px; }
  .prompt-chip { font-size: 12.5px; padding: 6px 2px; }
  .prompt-modal-title { font-size: 19px; margin-inline: 12px; }
  .prompt-sheet-sub { margin-inline: 12px; }
  .prompt-modal-list { padding-inline: 12px; }
  .prompt-sheet-foot { padding-inline: 12px; }
  .prompt-card { padding: 14px; }
  .prompt-question { font-size: 21px; }
  .prompt-tour { padding-inline: 14px; }
  .prompt-tour-text { font-size: 15.5px; }
  button.action.prompt-tour-next { min-width: 100px; padding: 12px 14px; }
  .prompt-stay { font-size: 12.5px; gap: 7px; }
}

/* =============================================================================
   CONTRIBUTOR SHELL (restyle phase 3, task 1; boards C2/C5/C6/C7)
   The phone screens' top bar, bottom tab bar, base type and the collection-
   closed page. ONE marked section at the end of the sheet, and every rule in it
   is scoped to the contributor bodies: `body[data-user-page]` (not the room,
   which keeps its own chrome until its board lands) or body.contrib-closed
   (the closing page, which shares the room's data-user-page="/"). No staff page
   (body.console) and no auth page carries either, so none of them moves.
   Colours are tokens from :root; nothing here is hex. RTL by logical
   properties only.
   ============================================================================= */
/* The real <main> max-width of each page, so the bar lines up with its column. */
body[data-user-page="/projects"], body[data-user-page="/my-recordings"], body[data-user-page="/work"] { --shell-main: 820px; }
body[data-user-page="/settings"] { --shell-main: 680px; }
/* The closing page's main is 640px (task 8 visual pass: the bar was drawn on the 560 default, 40px inside the text). */
body.contrib-closed { --shell-main: 640px; }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) { --shell-gutter: 18px; padding-top: 58px; padding-bottom: calc(66px + env(safe-area-inset-bottom, 0px)); }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed)[data-nav-held] { padding-bottom: 0; }

/* THE TOP BAR (board .top): 58px, logo at the start, language toggle at the
   end. The header is fixed so the toggle (a child of the fixed tab bar in the
   DOM, pulled up here) and the logo share one bar. The wordmark, the chip and
   the room id are not on the board: hidden by CSS, never removed. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .site-header {
  position: fixed; top: 0; inset-inline: 0; z-index: 30;
  height: 58px; margin: 0; padding: 0 var(--shell-gutter);
  background: var(--card); border-bottom: 1px solid var(--hair-soft);
}
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .brand-mark img { height: 24px; width: auto; }
/* The logo is a link home: a 44px hit area around the 36x24 mark (task 8
   visual pass; the bar is 58px, so it never reaches past it). */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .brand-mark::after { content: ''; position: absolute; inset: -10px -4px; }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .brand-mark .wordmark, body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .room-id, body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userChipName, body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #logoutLink { display: none; }
/* Staff keep a way back (ruling C-R5): #adminLink and #dashboardLink show
   whenever their painter un-hides them, as pills beside the language toggle. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .header-right { margin-inline-end: 96px; }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .user-chip { gap: 6px; }
/* The body face, not the chip's: .user-chip sets JetBrains Mono at 0.02em,
   which `inherit` carried into the pills (task 8 visual pass); the board's
   .lang pill is Geist. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .user-chip a {
  position: relative; padding: 6px 10px; border: 1px solid var(--hair); border-radius: 8px; background: var(--card);
  color: var(--brand); font-family: var(--font-body); font-style: normal; font-size: 13px; font-weight: 500;
  letter-spacing: 0; line-height: 1.55; text-decoration: none;
}
/* A 46px hit area without a 46px box, as the language pill has (they were 34px). */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) .user-chip a::after { content: ''; position: absolute; inset: -6px -3px; height: auto; transform: none; }

/* The toggle is painted INSIDE #userNav (user-chrome.js), so it is lifted out
   of the bar with position: fixed, which positions against the viewport.
   `direction: inherit` (task 8 visual pass): i18n-apply stamps the pill with
   the language it switches TO (dir="ltr" on an Arabic page), and a logical
   inset resolves against the element's own direction, so on Arabic pages the
   pill sat at the right, on top of the logo. Inheriting the page's direction
   puts it at the page's end; its one word still reads in its own script and
   its lang attribute is untouched. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav .nav-lang {
  direction: inherit; unicode-bidi: isolate;
  position: fixed; top: 12px; inset-inline-end: var(--shell-gutter); z-index: 31;
  margin: 0; min-height: 0; padding: 6px 10px;
  border: 1px solid var(--hair); border-radius: 8px; background: var(--card);
  color: var(--brand); font-size: 13px; font-weight: 500; line-height: 1.55;
}
/* 44px hit area without a 44px box: the board's pill is ~34px tall. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav .nav-lang::after { content: ''; position: absolute; inset: -6px -4px; }

/* THE TAB BAR (board .tabs): fixed, 66px, icon over label, current in brand. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 30;
  height: calc(66px + env(safe-area-inset-bottom, 0px)); margin: 0;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: flex; flex-wrap: nowrap; gap: 0;
  background: var(--card); border-top: 1px solid var(--hair-soft); border-bottom: 0;
}
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav a {
  flex: 1; min-width: 0; min-height: 44px; margin: 0; padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  font-size: 11.5px; font-weight: 400; color: var(--warm-1);
  border: 0; text-decoration: none;
}
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav a.is-current { color: var(--brand); font-weight: 600; border: 0; }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav .tab-icon { display: inline-flex; line-height: 0; }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav .tab-icon svg { display: block; }

/* >= 900px: the same links as a top row under the bar. */
@media (min-width: 900px) {
  body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) { --shell-gutter: calc((100% - var(--shell-main, 560px)) / 2 + 20px); padding-bottom: 0; }
  body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav {
    position: static; height: auto; margin: 0 0 18px; padding: 0;
    border-top: 0; border-bottom: 1px solid var(--hair-soft); background: transparent;
    justify-content: flex-start;
  }
  body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav a {
    flex: none; flex-direction: row; gap: 8px; padding: 10px 16px;
    font-size: 14px; margin-bottom: -1px; border-bottom: 2px solid transparent;
  }
  body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) #userNav a.is-current { border-bottom: 2px solid var(--brand); }
}

/* The tab bar stays out of a recording: the room is not in this scope, and the
   guided screen's own hide rule above still wins for anything that is. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed).guided-screen #userNav { display: none; }

/* BASE TYPE. Arabic headings Noto Kufi, Arabic body IBM Plex Sans Arabic. */
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed):lang(ar) { font-family: var(--font-ar-body); }
body:is([data-user-page]:not([data-user-page="/"]), .contrib-closed) :is(h1, h2, h3):lang(ar) { font-family: var(--font-ar-head); font-weight: 700; }

/* THE COLLECTION-CLOSED PAGE (board C2, third phone). */
body.contrib-closed main { max-width: 640px; padding: 0 22px; }
body.contrib-closed .site-footer { display: none; }
body.contrib-closed .closing-card {
  min-height: calc(100vh - 58px - 66px); min-height: calc(100dvh - 58px - 66px); margin: 0; padding: 24px 0;
  display: flex; flex-direction: column; justify-content: center; gap: 16px;
  border: 0; background: none; text-align: start;
}
@media (min-width: 900px) {
  body.contrib-closed .closing-card { min-height: 60vh; }
}
body.contrib-closed .closing-marks { display: flex; gap: 8px; }
body.contrib-closed .closing-diamond {
  width: 16px; height: 16px; transform: rotate(45deg); border-radius: 3px; background: var(--brand);
}
body.contrib-closed .closing-diamond-gold { background: var(--gold); }
body.contrib-closed .closing-title {
  margin: 0; font-family: var(--font-display); font-size: 28px; font-weight: 600; line-height: 1.3;
}
body.contrib-closed .closing-title:lang(ar) { font-family: var(--font-ar-head); font-weight: 700; }
body.contrib-closed .closing-body { margin: 0; font-size: 16px; line-height: 1.55; color: var(--ink); }
body.contrib-closed .closing-payment { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; color: var(--warm-1); }
body.contrib-closed .closing-link {
  margin-top: 8px; min-height: 44px; display: inline-flex; align-items: center; align-self: flex-start;
  font-weight: 600; text-decoration: none;
}

/* PROJECTS and MY RECORDINGS (boards C2 and C5) moved to public/ui.css in the
   polish round (2026-10-19, 3b-ii item 2), rewritten on the --ui-* tokens. */

/* =============================================================================
   MY DETAILS (restyle phase 3, task 5; board C6)
   First time, the details form, the CV block, password by emailed link and Sign
   out. Every rule is scoped to the My details page. Colours are tokens; RTL by
   logical properties only. What used to be this view's inline <style> lives here
   now, except the one bidi rule a test pins in the view itself.
   ============================================================================= */
body[data-user-page="/settings"] main { max-width: 680px; padding: 20px 16px 24px; }
body[data-user-page="/settings"] .site-footer { display: none; }
body[data-user-page="/settings"] :is(input, select, button, textarea) { font-family: inherit; }

/* The heading (board .h1) and the one line under it (first time only). */
body[data-user-page="/settings"] h1.intro { margin: 0 0 4px; padding: 0 2px; font-size: 24px; font-weight: 700; line-height: 1.3; letter-spacing: 0; }
body[data-user-page="/settings"] .set-note { margin: 0 0 12px; padding: 0 2px; font-size: 13.5px; line-height: 1.55; color: var(--warm-1); }
body[data-user-page="/settings"] .set-msg { font-size: 13px; line-height: 1.5; color: var(--ink-soft); }
body[data-user-page="/settings"] .set-msg:empty { display: none; }
body[data-user-page="/settings"] .set-msg.is-error { color: var(--danger); font-weight: 500; }
body[data-user-page="/settings"] #profileError { margin: 0 0 8px; padding: 0 2px; }
body[data-user-page="/settings"] .admin-section { margin-bottom: 12px; }

/* THE FORM has no frame of its own (the seat's dashed one is not wanted here): its groups are the cards. */
body[data-user-page="/settings"] #profileForm { margin: 0; padding: 0; border: 0; background: transparent; gap: 0; }

/* A GROUP (board .grp): 16px radius, 14px padding, 11px between its parts. */
body[data-user-page="/settings"] .set-grp {
  margin: 0 0 12px; padding: 14px; display: flex; flex-direction: column; gap: 11px; min-width: 0;
  background: var(--card); border: 1px solid var(--hair); border-radius: 16px;
}
body[data-user-page="/settings"] .set-grp-title { margin: 0; font-family: var(--font-display); font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: 0; }
body[data-user-page="/settings"] .set-grp-title:lang(ar) { font-family: var(--font-ar-head); font-weight: 700; }

/* Labels (board .f label): 12.5px, sentence case, the field 5px below. */
body[data-user-page="/settings"] .meta-form label {
  gap: 5px; margin: 0; font-size: 12.5px; font-weight: 500; line-height: 1.55; letter-spacing: 0; text-transform: none; color: var(--ink-soft);
}
body[data-user-page="/settings"] .meta-grid { gap: 11px 10px; }
body[data-user-page="/settings"] .meta-grid .set-wide { grid-column: 1 / -1; }

/* Fields (board .in): 44px, 11px radius, 16px text (the iOS zoom floor). */
body[data-user-page="/settings"] .meta-form :is(input:not([type="radio"]), select, textarea) {
  width: 100%; min-height: 44px; padding: 0 12px; font-size: 16px; font-weight: 400;
  border: 1px solid var(--input-line); border-radius: 11px; background: var(--card); color: var(--ink);
}
body[data-user-page="/settings"] .meta-form textarea { padding: 10px 12px; resize: vertical; }
body[data-user-page="/settings"] .meta-form :is(input:not([type="radio"]), select):focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand); box-shadow: none; }

/* Language rows (board .lr): language wide, level 92px, remove at the end. */
body[data-user-page="/settings"] .set-lang-row { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
body[data-user-page="/settings"] .set-lang-row select { flex: 1 1 0; min-width: 0; }
body[data-user-page="/settings"] .set-lang-row select[data-lang="level"] { flex: 0 0 92px; }
body[data-user-page="/settings"] .set-lang-x {
  flex: none; width: 44px; height: 44px; margin: 0; padding: 0; border: 0; border-radius: 9px; background: none;
  color: var(--warm-2); font-size: 22px; line-height: 1; cursor: pointer;
}
body[data-user-page="/settings"] .set-lang-x:hover { color: var(--danger); }
/* Add a language (board: bold brand text), a 44px target. */
body[data-user-page="/settings"] .set-grp #pfAddLang {
  width: auto; align-self: flex-start; min-height: 44px; margin: 0; padding: 0; border: 0; border-radius: 0; background: none;
  color: var(--brand); font-size: 14px; font-weight: 600; cursor: pointer;
}
body[data-user-page="/settings"] .set-grp #pfAddLang:hover:not(:disabled) { background: none; color: var(--brand-deep); text-decoration: underline; text-underline-offset: 3px; }

/* The payment pair (board .radio): two tiles side by side, the chosen one in brand. */
body[data-user-page="/settings"] .pay-methods { margin: 0; padding: 0; border: 0; min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
body[data-user-page="/settings"] .pay-methods legend { margin: 0 0 11px; padding: 0; font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: 0; text-transform: none; color: var(--ink); }
body[data-user-page="/settings"] .pay-hint { margin: 0; font-size: 13px; line-height: 1.55; color: var(--warm-1); }
body[data-user-page="/settings"] .pay-methods .pay-hint { grid-column: 1 / -1; }
body[data-user-page="/settings"] .meta-form label.pay-choice {
  min-height: 46px; padding: 0 12px; flex-direction: row; align-items: center; gap: 10px; cursor: pointer;
  border: 1px solid var(--input-line); border-radius: 11px; font-size: 15px; font-weight: 500; color: var(--ink);
}
body[data-user-page="/settings"] .meta-form label.pay-choice:has(input:checked) { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); background: var(--brand-tint); }
body[data-user-page="/settings"] .meta-form .pay-choice input { width: 20px; height: 20px; min-height: 0; margin: 0; padding: 0; flex: none; accent-color: var(--brand); }

/* THE DOCK (board .dock): Save stays above the tab bar; in first-time mode there is no tab bar. */
body[data-user-page="/settings"] .set-actions { margin: 0; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
body[data-user-page="/settings"] .set-dock {
  position: sticky; bottom: calc(66px + env(safe-area-inset-bottom, 0px)); z-index: 20;
  margin: 0 -16px 12px; padding: 12px 16px 14px; flex-direction: column; align-items: stretch; gap: 6px;
  background: var(--card); border-top: 1px solid var(--hair-soft);
}
body[data-user-page="/settings"][data-nav-held] .set-dock { bottom: 0; }
body[data-user-page="/settings"] .set-dock .set-msg { order: -1; }
body[data-user-page="/settings"] #profileSave {
  min-height: 48px; margin: 0; padding: 0 20px; border-radius: 14px; font-size: 15.5px; font-weight: 600; letter-spacing: 0;
}
@media (min-width: 900px) {
  body[data-user-page="/settings"] .set-dock { bottom: 0; }
}

/* CV, password, account: each its own card (board .grp). */
body[data-user-page="/settings"] :is(#cvSection, #passwordSection) {
  margin: 0 0 12px; padding: 14px; display: flex; flex-direction: column; gap: 11px;
  background: var(--card); border: 1px solid var(--hair); border-radius: 16px;
}
body[data-user-page="/settings"] .admin-section.acct-row {
  margin: 0 0 12px; padding: 14px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--card); border: 1px solid var(--hair); border-radius: 16px;
}
body[data-user-page="/settings"] :is(#cvSection, #passwordSection) .section-head { margin: 0; }
body[data-user-page="/settings"] :is(#cvSection, #passwordSection) .section-head h2 { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: 0; }
body[data-user-page="/settings"] :is(#cvSection, #passwordSection) .section-head h2:lang(ar) { font-family: var(--font-ar-head); font-weight: 700; }
body[data-user-page="/settings"] :is(#cvSection, #passwordSection) .set-actions { flex-direction: column; align-items: stretch; gap: 10px; }
body[data-user-page="/settings"] #cvFile {
  width: 100%; padding: 10px 12px; font-size: 14px; border-radius: 12px; background: var(--fill); color: var(--ink);
}
body[data-user-page="/settings"] #cvFile::file-selector-button { margin-inline-end: 10px; }
body[data-user-page="/settings"] :is(#cvUploadBtn, #passwordLinkBtn) {
  width: 100%; min-height: 44px; margin: 0; padding: 0 18px; border: 1px solid var(--brand-line); border-radius: 14px;
  background: var(--card); color: var(--brand); font-size: 15px; font-weight: 600; letter-spacing: 0;
}
body[data-user-page="/settings"] :is(#cvUploadBtn, #passwordLinkBtn):hover:not(:disabled) { background: var(--brand-soft); color: var(--brand-deep); }
/* A sent or saved line is a quiet confirmation box (board .ok-line); an error stays red text. */
body[data-user-page="/settings"] :is(#passwordLinkMsg, #cvMsg):not(:empty):not(.is-error) { padding: 9px 12px; border-radius: 10px; background: var(--moss-tint); color: var(--moss-ink); }

/* The account row: who is signed in, and Sign out. The shell hides #logoutLink on contributor pages;
   this page is where it lives, so the rule below is one specificity step above that one. */
body[data-user-page="/settings"] .acct-who { min-width: 0; display: flex; flex-direction: column; line-height: 1.35; }
body[data-user-page="/settings"] .acct-who strong { font-size: 14.5px; font-weight: 600; overflow-wrap: anywhere; unicode-bidi: plaintext; }
body[data-user-page="/settings"] .acct-who bdi { font-size: 12.5px; color: var(--warm-1); overflow-wrap: anywhere; }
body[data-user-page="/settings"] .acct-row a#logoutLink {
  display: inline-flex; align-items: center; flex: none; min-height: 44px; font-size: 15px; font-weight: 600;
  color: var(--danger-ink); text-decoration: none;
}
body[data-user-page="/settings"] .acct-row a#logoutLink:hover { text-decoration: underline; text-underline-offset: 3px; }

/* =============================================================================
   TRANSCRIPTION WORK (restyle phase 3, task 5; board C7)
   The list grouped as "what you are working on" and "available to take", one
   action per row, and the empty state. Every rule is scoped to the work page.
   Colours are tokens; RTL by logical properties only.
   ============================================================================= */
body[data-user-page="/work"] main { max-width: 820px; padding: 20px 14px 24px; }
body[data-user-page="/work"] .site-footer { display: none; }
body[data-user-page="/work"] :is(button, a) { font-family: inherit; }

body[data-user-page="/work"] h1.intro { margin: 0 0 4px; padding: 0 2px; font-size: 24px; font-weight: 700; line-height: 1.3; letter-spacing: 0; }
body[data-user-page="/work"] #workIntro { display: block; margin: 0 0 12px; padding: 0 2px; font-size: 13.5px; line-height: 1.55; color: var(--warm-1); }
body[data-user-page="/work"] .work-msg { margin: 0 0 10px; padding: 0 2px; font-size: 13.5px; color: var(--danger); font-weight: 500; }
body[data-user-page="/work"] .work-msg:empty { display: none; }
body[data-user-page="/work"] .work-empty { margin: 0 0 10px; padding: 0 2px; font-size: 12.5px; line-height: 1.55; color: var(--warm-1); text-align: center; }
body[data-user-page="/work"] #workTruncated { text-align: start; }

/* The group headings (board .sec). */
body[data-user-page="/work"] .admin-section { margin: 0 0 12px; }
body[data-user-page="/work"] .admin-section .section-head { margin: 0 0 8px; padding: 4px 2px 0; }
body[data-user-page="/work"] .admin-section .section-head h2 { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.4; letter-spacing: 0; color: var(--ink-soft); }
body[data-user-page="/work"] .admin-section .section-head h2:lang(ar) { font-family: var(--font-ar-head); font-weight: 700; }

/* THE CARD (board .job): 16px radius, 14px padding, 10px between its parts; a held one takes the brand hairline. */
body[data-user-page="/work"] #workMine, body[data-user-page="/work"] #workPool { list-style: none; padding: 0; margin: 0 0 10px; display: flex; flex-direction: column; gap: 12px; }
body[data-user-page="/work"] .work-card {
  min-width: 0; padding: 14px; display: flex; flex-direction: column; gap: 10px;
  background: var(--card); border: 1px solid var(--hair); border-radius: 16px;
}
body[data-user-page="/work"] .work-card.is-mine { border-color: var(--brand-line); box-shadow: inset 0 0 0 1px var(--brand-line); }
/* Name and chip share a row; a long name wraps and the chip drops under it; the facts take their own line. */
body[data-user-page="/work"] .work-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
body[data-user-page="/work"] .work-name { flex: 1 1 140px; min-width: 0; font-size: 15px; font-weight: 600; line-height: 1.4; overflow-wrap: anywhere; unicode-bidi: plaintext; text-align: start; }
body[data-user-page="/work"] .work-chip {
  flex: none; display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 500; line-height: 1; white-space: nowrap; background: var(--brand-tint); color: var(--brand-deep);
}
body[data-user-page="/work"] .work-chip::before { content: ''; width: 6px; height: 6px; transform: rotate(45deg); background: currentColor; border-radius: 1px; }
body[data-user-page="/work"] .work-facts { flex: 1 0 100%; display: flex; flex-wrap: wrap; gap: 6px; }
body[data-user-page="/work"] .work-fact { padding: 3px 9px; border-radius: 8px; background: var(--fill); font-size: 12.5px; line-height: 1.55; color: var(--ink); }
body[data-user-page="/work"] .work-fact.n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; unicode-bidi: isolate; }
body[data-user-page="/work"]:lang(ar) .work-fact.n { font-family: inherit; }

/* ONE ACTION: the primary is 48px; Hand back is bold brand text with a 44px target. */
body[data-user-page="/work"] .work-actions { display: flex; align-items: center; gap: 10px; }
body[data-user-page="/work"] .work-go {
  flex: 1 1 auto; min-height: 48px; padding: 0 20px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 12px; background: var(--brand); color: var(--on-fill); font-size: 15px; font-weight: 600; text-decoration: none; cursor: pointer;
}
body[data-user-page="/work"] .work-go:hover:not(:disabled) { background: var(--brand-deep); color: var(--on-fill); text-decoration: none; }
body[data-user-page="/work"] .work-go:disabled { background: var(--fill-strong); color: var(--warm-2); cursor: not-allowed; }
body[data-user-page="/work"] .work-link {
  flex: none; min-height: 44px; padding: 0 10px; border: 0; background: none; color: var(--brand); font-size: 15px; font-weight: 600; cursor: pointer;
}
body[data-user-page="/work"] .work-link:hover:not(:disabled) { color: var(--brand-deep); text-decoration: underline; text-underline-offset: 3px; }
body[data-user-page="/work"] .work-link:disabled { opacity: 0.4; cursor: default; }

/* NO WORK YET (board second phone): the intro line becomes the title, centred under the icon. */
body[data-user-page="/work"] .work-empty-art { display: none; }
body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])) main {
  min-height: calc(100vh - 58px - 66px - 44px); min-height: calc(100dvh - 58px - 66px - 44px);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; padding: 24px; text-align: center;
}
body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])) :is(h1.intro, #mineSection, #poolSection) { display: none; }
body[data-user-page="/work"] .work-empty-art:not([hidden]) {
  width: 72px; height: 72px; border-radius: 20px; display: flex; align-items: center; justify-content: center;
  background: var(--brand-tint); color: var(--brand);
}
body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])) #workIntro { order: 1; margin: 0; padding: 0; font-family: var(--font-display); font-size: 24px; font-weight: 700; line-height: 1.3; color: var(--ink); }
body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])) #workIntro:lang(ar) { font-family: var(--font-ar-head); }
/* NOT GRANTED (task 8 visual pass): the line is two sentences, not a title, and
   at 24px it filled three heavy lines at 360px. The board draws only "nothing
   today"; this state keeps the layout at a sentence's size. */
body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])):has(#workEmptyBody[hidden]) #workIntro { font-size: 18px; line-height: 1.5; }
body[data-user-page="/work"] .work-empty-body { order: 2; margin: 0; font-size: 14px; line-height: 1.55; color: var(--warm-1); }
@media (min-width: 900px) {
  body[data-user-page="/work"]:has(#workEmptyArt:not([hidden])) main { min-height: 60vh; }
}

/* THE RECORDING ROOM (board C4; restyle phase 3, task 6). `/` minus the
   collection-closed page, which shares its data-user-page. PAINTING ONLY: every
   state below is read off what public/app.js already writes -- #statusRow's
   data-state, the seat classes renderSlots toggles, [hidden], and
   body.guided-screen -- never off a class added for styling alone. Rules that
   the guided screen (board C3, the next task) draws differently carry
   :not(.guided-screen), so a guided project's page is untouched by them. The
   pinned rules (#actionBar, #statusRow and its states, .saving-warning) are
   edited where they live, above. Leave stays in the bar, red (D57); End session
   stays in the bar, disabled not hidden (D58): no overflow menu (C-D1). */
body[data-user-page="/"]:not(.contrib-closed) { padding-top: 58px; }
body[data-user-page="/"]:not(.contrib-closed):lang(ar) { font-family: var(--font-ar-body); }
body[data-user-page="/"]:not(.contrib-closed) :is(h1, h2, .intro):lang(ar) { font-family: var(--font-ar-head); font-weight: 700; letter-spacing: 0; }
body[data-user-page="/"]:not(.contrib-closed) main { padding: 16px 16px 0; }
/* THE DOCK SITS AT THE FOOT OF THE SCREEN (task 8 visual pass). Sticky only
   pins it while the page is taller than the screen; on a short page (invite and
   wait, done, ended) it floated half-way up with empty paper under it, where
   board C4 draws it on the bottom edge. A column that fills the screen and an
   auto top margin put it there without fixing it over anything. The body
   already pads 58px for the bar, so the column is the screen LESS the bar
   (final review m4): a plain 100dvh made every short page scroll by 58px. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) main {
  display: flex; flex-direction: column; min-height: calc(100vh - 58px); min-height: calc(100dvh - 58px);
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) main > * { flex: none; }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar { margin-top: auto; order: 1; }
/* Done (board C4 "Done"): the result card is drawn in the body ABOVE the dock.
   It follows #actionBar in the DOM (pinned), so the column draws it first; the
   admin-only log stays last. Paint order only -- the DOM, and so the keyboard
   order, is unchanged. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) main > .log-fold { order: 2; }
/* The room has no tab bar and no footer (spec 3); its toggle is in the bar. */
body[data-user-page="/"]:not(.contrib-closed) #userNav,
body[data-user-page="/"]:not(.contrib-closed) .site-footer { display: none; }

/* THE BAR (board .bar): 58px, the card, a soft hairline; back chevron, title
   with the project under it, staff pills, language pill. */
body[data-user-page="/"]:not(.contrib-closed) .site-header {
  position: fixed; top: 0; inset-inline: 0; z-index: 30;
  height: 58px; margin: 0; padding: 0 14px; gap: 10px;
  justify-content: flex-start;
  background: var(--card); border-bottom: 1px solid var(--hair-soft);
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) .brand-mark { display: none; }
body[data-user-page="/"]:not(.contrib-closed) .room-back {
  display: none; flex: none; width: 40px; height: 40px; border-radius: 10px;
  align-items: center; justify-content: center; color: var(--ink); text-decoration: none;
}
/* Only while no session is live: the back link leaves the page (D57). */
body[data-user-page="/"]:not(.contrib-closed):has(#statusRow[data-state="idle"]) .room-back,
body[data-user-page="/"]:not(.contrib-closed):has(#statusRow[data-state="ended"]) .room-back { display: inline-flex; }
body[data-user-page="/"]:not(.contrib-closed) .room-back:hover { background: var(--fill); color: var(--ink); }
/* 44px to a finger without a 44px box: the board draws a 40px tile. */
body[data-user-page="/"]:not(.contrib-closed) .room-back { position: relative; }
body[data-user-page="/"]:not(.contrib-closed) .room-back::after { content: ''; position: absolute; inset: -2px; }
body[data-user-page="/"]:not(.contrib-closed) .room-back svg:dir(rtl) { transform: scaleX(-1); }
body[data-user-page="/"]:not(.contrib-closed) .room-title { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
body[data-user-page="/"]:not(.contrib-closed) .site-header .room-id {
  font-family: inherit; font-size: 15px; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-user-page="/"]:not(.contrib-closed) .room-sub {
  font-size: 12px; font-weight: 400; color: var(--warm-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* THE GUIDED BAR (board C3): the room id is hidden there (the guided block)
   and the project line under it becomes the bar's title -- 15px semibold ink,
   the board's `.bar strong`. */
body[data-user-page="/"]:not(.contrib-closed).guided-screen .room-sub { font-size: 15px; font-weight: 600; color: var(--ink); }
body[data-user-page="/"]:not(.contrib-closed) .header-right { flex: none; margin-inline-start: auto; gap: 6px; }
body[data-user-page="/"]:not(.contrib-closed) #userChipName,
body[data-user-page="/"]:not(.contrib-closed) #logoutLink { display: none; }
body[data-user-page="/"]:not(.contrib-closed) .user-chip { gap: 6px; }
body[data-user-page="/"]:not(.contrib-closed) .user-chip a,
body[data-user-page="/"]:not(.contrib-closed) .room-lang {
  position: relative; padding: 6px 10px; border: 1px solid var(--hair); border-radius: 8px; background: var(--card);
  color: var(--brand); font-family: var(--font-body); letter-spacing: 0; font-style: normal; font-size: 13px; font-weight: 500;
  line-height: 1.55; text-decoration: none; cursor: pointer;
}
body[data-user-page="/"]:not(.contrib-closed) .room-lang::after { content: ''; position: absolute; inset: -6px -4px; }
/* The staff pills (Admin, Dashboard) get the same 46px hit area: they were 34px. */
body[data-user-page="/"]:not(.contrib-closed) .user-chip a::after { content: ''; position: absolute; inset: -6px -3px; height: auto; transform: none; }

/* THE PAGE ABOVE THE DOCK. */
body[data-user-page="/"]:not(.contrib-closed) .intro { font-size: 20px; margin: 0 0 8px; }
body[data-user-page="/"]:not(.contrib-closed) .hint-row { font-size: 13px; margin: 0 0 12px; }
body[data-user-page="/"]:not(.contrib-closed) .help-link { font-size: 13px; }

/* The fixed-count sentence is the board's first status card (cobalt dot). */
body[data-user-page="/"]:not(.contrib-closed) #speakerFixedLine {
  display: flex; align-items: center; gap: 10px; margin: 0 0 12px; padding: 12px 14px;
  border: 1px solid var(--hair); border-radius: 14px; background: var(--card);
  font-size: 14px; font-weight: 500; color: var(--ink);
}
body[data-user-page="/"]:not(.contrib-closed) #speakerFixedLine::before {
  content: ''; flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--brand);
}
body[data-user-page="/"]:not(.contrib-closed) #speakerCountPicker .lab,
body[data-user-page="/"]:not(.contrib-closed) #landingPickers label,
body[data-user-page="/"]:not(.contrib-closed) .meta-form label {
  font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--ink-soft); gap: 5px;
}
body[data-user-page="/"]:not(.contrib-closed) #landingPickers select,
body[data-user-page="/"]:not(.contrib-closed) .meta-form select,
body[data-user-page="/"]:not(.contrib-closed) .meta-form input {
  min-height: 44px; padding: 0 12px; border: 1px solid var(--input-line); border-radius: 11px;
  background: var(--card); font-size: 16px; font-weight: 400;
}
body[data-user-page="/"]:not(.contrib-closed) .count-row .action { border-radius: 11px; }

/* THE SEATS (board .seat): stacked full-width cards, a 38px number tile, the
   name over one quiet line. */
body[data-user-page="/"]:not(.contrib-closed) #slots { grid-template-columns: minmax(0, 1fr); gap: 12px; margin-bottom: 12px; }
body[data-user-page="/"]:not(.contrib-closed) .slot {
  padding: 12px 14px; border: 1px solid var(--hair); border-radius: 16px; background: var(--card);
  box-shadow: none; opacity: 1;
}
body[data-user-page="/"]:not(.contrib-closed) .slot.locked,
body[data-user-page="/"]:not(.contrib-closed) .slot.locked:hover { background: var(--card); border-style: solid; opacity: 1; }
body[data-user-page="/"]:not(.contrib-closed) .slot.taken { opacity: 1; cursor: default; }
body[data-user-page="/"]:not(.contrib-closed) .slot.mine { border-color: var(--brand-line); background: var(--card); box-shadow: none; padding: 14px; }
body[data-user-page="/"]:not(.contrib-closed) .slot-head {
  display: grid; grid-template-columns: 38px minmax(0, 1fr); grid-template-areas: "ch name" "ch state";
  column-gap: 12px; align-items: center;
}
body[data-user-page="/"]:not(.contrib-closed) .slot .ch {
  grid-area: ch; width: 38px; height: 38px; margin: 0; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-tint); color: var(--brand-deep);
  font-family: inherit; font-size: 14px; font-weight: 700; letter-spacing: 0; text-transform: none;
}
body[data-user-page="/"]:not(.contrib-closed) .slot.is-empty .ch { background: var(--fill); color: var(--warm-2); border: 1px dashed var(--input-line); }
body[data-user-page="/"]:not(.contrib-closed) .slot .name {
  grid-area: name; font-family: inherit; font-size: 15px; font-weight: 600; line-height: 1.35; letter-spacing: 0; overflow-wrap: anywhere;
}
body[data-user-page="/"]:not(.contrib-closed) .slot.is-empty .name { color: var(--warm-1); font-weight: 500; }
body[data-user-page="/"]:not(.contrib-closed) .slot .state {
  grid-area: state; margin: 0; font-size: 12.5px; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--warm-1);
}
body[data-user-page="/"]:not(.contrib-closed) .slot.mine .state { color: var(--warm-1); font-weight: 400; }
body[data-user-page="/"]:not(.contrib-closed) .slot.is-saved .state { color: var(--moss); }

/* THE SEAT FORM (board .form): fields 10px apart, the summaries as links. */
body[data-user-page="/"]:not(.contrib-closed) .meta-form { margin-top: 12px; padding: 0; gap: 10px; background: none; border: 0; border-radius: 0; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form .lang-pair { grid-template-columns: minmax(0, 1fr); gap: 10px; margin-top: 0; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form label.full { margin-top: 0; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form .meta-more > summary {
  padding: 0; font-size: 13.5px; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--brand);
}
body[data-user-page="/"]:not(.contrib-closed) .meta-form .meta-more > summary::before { display: none; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form > .seat-from-details { order: 10; margin: 0; font-size: 13px; color: var(--warm-1); text-align: center; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form .meta-save {
  min-height: 50px; margin-top: 2px; padding: 0 20px; border-radius: 14px;
  font-size: 16px; font-weight: 600; letter-spacing: 0; text-transform: none;
}
body[data-user-page="/"]:not(.contrib-closed) .meta-form .meta-save:disabled { background: var(--off-fill); color: var(--off-ink); opacity: 1; }
body[data-user-page="/"]:not(.contrib-closed) .meta-form .meta-err { font-size: 13px; }
body[data-user-page="/"]:not(.contrib-closed) .seat-details { font-size: 13px; }
body[data-user-page="/"]:not(.contrib-closed) .meta-summary { margin-top: 6px; padding: 0; border: 0; background: none; font-size: 12.5px; }
body[data-user-page="/"]:not(.contrib-closed) .meta-summary .meta-line { font-family: inherit; color: var(--warm-1); letter-spacing: 0; }
body[data-user-page="/"]:not(.contrib-closed) .meta-summary .meta-edit {
  position: relative; font-family: inherit; font-style: normal; font-size: 13px; font-weight: 600; text-decoration: none;
}
body[data-user-page="/"]:not(.contrib-closed) .meta-summary .meta-edit::after { content: ''; position: absolute; inset: -12px -8px; }

/* THE INVITE CARD (board): a title, the link in a quiet field, Copy. */
body[data-user-page="/"]:not(.contrib-closed) #inviteCard {
  display: flex; flex-direction: column; gap: 10px; padding: 14px;
  border: 1px solid var(--hair); border-radius: 16px; background: var(--card);
}
body[data-user-page="/"]:not(.contrib-closed) #inviteCard .lab { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--ink); }
body[data-user-page="/"]:not(.contrib-closed) .invite-row { gap: 8px; }
body[data-user-page="/"]:not(.contrib-closed) .invite-row input {
  height: 44px; padding: 0 12px; border: 1px solid var(--input-line); border-radius: 11px;
  background: var(--invite-fill); color: var(--ink-soft); font-size: 13px; direction: ltr;
}
body[data-user-page="/"]:not(.contrib-closed) #copyBtn {
  display: inline-flex; align-items: center; gap: 6px; flex: none; height: 44px; padding: 0 14px;
  border: 1px solid var(--input-line); border-radius: 11px; background: var(--card); color: var(--brand);
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
}
body[data-user-page="/"]:not(.contrib-closed) #copyBtn:hover { background: var(--fill); }
body[data-user-page="/"]:not(.contrib-closed) .copy-feedback { margin: 0; font-size: 13px; letter-spacing: 0; }
body[data-user-page="/"]:not(.contrib-closed) #peerLine { border-radius: 14px; padding: 12px 14px; font-size: 14px; }

/* THE MICROPHONE BANNER (board): the tint, the title, the sentence, reload. */
body[data-user-page="/"]:not(.contrib-closed) .mic-banner {
  display: flex; flex-direction: column; gap: 6px; padding: 14px;
  border: 0; border-radius: 14px; background: var(--danger-tint); color: var(--danger-deep);
  font-size: 14px; font-weight: 400; line-height: 1.55;
}
body[data-user-page="/"]:not(.contrib-closed) .mic-banner-title { font-size: 15px; font-weight: 600; }
body[data-user-page="/"]:not(.contrib-closed) .mic-banner-text { margin: 0; }
body[data-user-page="/"]:not(.contrib-closed) .mic-banner-reload {
  align-self: flex-start; min-height: 44px; display: inline-flex; align-items: center;
  color: var(--danger-ink); font-weight: 600; text-decoration: none;
}

/* THE DOCK (board .dock): full-bleed, 12/16/16 padding, 8px apart. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar {
  margin-inline: -16px; padding: 12px 16px max(16px, env(safe-area-inset-bottom)); gap: 8px;
}
body[data-user-page="/"]:not(.contrib-closed) #actionBar #statusRow { margin-bottom: 0; }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar button.action {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 50px; padding: 0 20px; border-radius: 14px;
  font-size: 16px; font-weight: 600; letter-spacing: 0; text-transform: none;
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar button.action:not(.danger):not(.ghost):not(.stop):disabled {
  background: var(--off-fill); color: var(--off-ink); opacity: 1;
}
body[data-user-page="/"]:not(.contrib-closed) .start-icon { flex: none; }
body[data-user-page="/"]:not(.contrib-closed) .start-hint { margin: 0; font-size: 13px; letter-spacing: 0; color: var(--warm-1); text-align: center; }
/* Leave: red and obvious, greyed (never hidden) while recording -- D57. End
   session: the board's menu item, in the bar, disabled not hidden -- D58. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #leaveBtn,
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #endBtn,
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #endCancel {
  min-height: 44px; border-radius: 12px; font-size: 14.5px; background: transparent;
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #leaveBtn { color: var(--danger); border: 1px solid var(--danger); }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #endBtn { color: var(--danger-ink); border: 1px solid var(--hair); }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #leaveBtn:disabled,
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar #endBtn:disabled { opacity: 0.5; }
/* SESSION ENDED (task 8 visual pass): the same button now reads "Back to
   projects" -- there is nothing left to leave, so it is the board's plain
   secondary, not the red Leave. Keyed on the status row's own state. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar:has(#statusRow[data-state="ended"]) #leaveBtn {
  min-height: 50px; border-radius: 14px; font-size: 16px; font-weight: 600;
  color: var(--ink); background: var(--card); border: 1px solid var(--input-line);
}

/* RECORDING (board): the clock large and centred, "of 30:00" under it, and
   Stop as the board's 92px red ring with its square. The word stays in the
   button for a screen reader and is hidden from the eye. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow[data-state="recording"] {
  flex-wrap: wrap; justify-content: center; padding: 18px 14px 14px; text-align: center;
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow[data-state="recording"] .status-text { flex: 0 1 auto; color: var(--warm-1); }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow[data-state="recording"] .status-timer {
  flex-basis: 100%; margin: 6px 0 0; display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--ink);
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow .timer-now { font-family: var(--font-mono); font-size: 52px; font-weight: 600; letter-spacing: -0.02em; line-height: 1; }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow .timer-cap { font-family: var(--font-body); font-size: 14px; font-variant-numeric: tabular-nums; color: var(--warm-1); }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar button.action#stopBtn {
  align-self: center; width: 92px; height: 92px; min-height: 0; padding: 0; border-radius: 50%;
  border: 3px solid var(--danger); background: var(--card); color: var(--danger);
}
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #actionBar button.action#stopBtn:hover:not(:disabled) { background: var(--danger-tint); color: var(--danger); }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #stopBtn .ctl-icon { display: flex; }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #stopBtn .ctl-icon svg { width: 40px; height: 40px; }
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #stopBtn .ctl-word {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* SAVING, DONE, ENDED, SAVE FAILED: the board's sentence reads as the
   screen's heading. */
body[data-user-page="/"]:not(.contrib-closed):not(.guided-screen) #statusRow:is([data-state="finalizing"], [data-state="done"], [data-state="ended"], [data-state="error"]) .status-text {
  font-size: 17px; font-weight: 600; line-height: 1.45;
}

/* THE SAVE IN NUMBERS (board .prog): an 8px track, the count, and "then the
   server merges it" once Stop is pressed (the red warning is up exactly then). */
body[data-user-page="/"]:not(.contrib-closed) .upload-progress-track { height: 8px; border-radius: 4px; background: var(--track); }
body[data-user-page="/"]:not(.contrib-closed) .upload-progress-fill { border-radius: 4px; }
body[data-user-page="/"]:not(.contrib-closed) .upload-progress-row { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-top: 8px; }
body[data-user-page="/"]:not(.contrib-closed) .upload-progress-label { margin: 0; font-size: 13.5px; font-weight: 500; color: var(--ink); }
body[data-user-page="/"]:not(.contrib-closed) .upload-progress-note { display: none; font-size: 13.5px; color: var(--warm-1); }
body[data-user-page="/"]:not(.contrib-closed) #actionBar:has(> #savingWarning:not([hidden])) .upload-progress-note { display: inline; }

/* DONE (board): a moss tick, the heading, the card with the download. */
body[data-user-page="/"]:not(.contrib-closed) #resultBox {
  display: flex; flex-direction: column; gap: 10px; padding: 16px;
  border: 1px solid var(--hair); border-radius: 16px; background: var(--card);
}
body[data-user-page="/"]:not(.contrib-closed) .result-check {
  width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--moss-tint); color: var(--moss);
}
body[data-user-page="/"]:not(.contrib-closed) #resultBox .lab {
  margin: 0; font-family: var(--font-display); font-size: 25px; font-weight: 700; line-height: 1.3;
  letter-spacing: 0; text-transform: none; color: var(--ink);
}
body[data-user-page="/"]:not(.contrib-closed) #resultBox .lab:lang(ar) { font-family: var(--font-ar-head); }
body[data-user-page="/"]:not(.contrib-closed) #resultBox a {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; align-self: flex-start;
  font-family: inherit; font-style: normal; font-size: 15px; font-weight: 600; text-decoration: none;
}
body[data-user-page="/"]:not(.contrib-closed) #resultBox .share { margin: 0; font-family: inherit; font-size: 12.5px; letter-spacing: 0; }
body[data-user-page="/"]:not(.contrib-closed) #resultBox button.action {
  margin: 4px 0 0; min-height: 50px; border-radius: 14px; font-size: 16px;
}

/* THE HELP POP-UP: the board's radius. */
body[data-user-page="/"]:not(.contrib-closed) #helpModal { border-radius: 16px; }
@media (max-width: 480px) {
  body[data-user-page="/"]:not(.contrib-closed) #helpModal { border-radius: 16px 16px 0 0; }
}

/* THE FREELANCER ONBOARDING (/welcome) moved to public/ui.css in the polish round (2026-10-19). */
