/* ============================================================================
   QUALITY — the MVP audit's fixes, in one place
   ----------------------------------------------------------------------------
   Loaded LAST, after product.css. Every rule here answers a defect measured in
   the rendered product (Prompt 11), not a preference, and each one says what it
   fixes. Nothing here restyles a screen; it corrects contrast, target size and
   a few selected-state cues where the existing rules fell short.

   WHY A SEPARATE FILE. The same corrections spread across tokens.css, app.css,
   system.css and product.css would bury them in four files with four histories.
   Here they can be read, checked and — once the underlying rules are rebuilt —
   deleted together.
   ============================================================================ */

/* ── 1 · TEXT CONTRAST, LIGHT THEME ONLY ────────────────────────────────────
   Measured against the rendered page (#F6F7FB ground, #F1F2F7 / #F2F4F9 insets):

       --ink-3  #7A849C   3.5 : 1   secondary text, nav, crumbs, tags, helpers
       --ink-4  #AAB2C6   2.0 : 1   notes, provenance, demo disclaimers

   Both are used for body-size text across every MVP route, and WCAG AA asks
   4.5 : 1 for text that size. The two tokens are darkened just enough to pass
   on the darkest inset they sit on, and kept apart so the hierarchy survives:

       --ink-3  #5B647A   ≈ 5.5 : 1 on the ground, ≈ 5.3 : 1 on insets
       --ink-4  #636C82   ≈ 4.9 : 1 on the ground, ≈ 4.7 : 1 on insets

   Scoped away from an explicit dark theme, which has its own palette. The
   page stamps data-theme="light" by default (index.html), so this applies to
   the product as shipped.                                                    */
:root:not([data-theme="dark"]) {
  --ink-3: #5B647A;
  --ink-4: #636C82;
}

/* Coloured text that was too light on the light ground. */
:root:not([data-theme="dark"]) .repeat-v.is-yes     { color: #2F5FD0; }  /* was 3.3 : 1 */
:root:not([data-theme="dark"]) .repeat-v.is-no      { color: #6A3FD6; }  /* was 3.1 : 1 */
:root:not([data-theme="dark"]) .repeat-v.is-mixed   { color: #8A5A00; }
:root:not([data-theme="dark"]) .cthread-s .is-answered { color: #2F5FD0; }
:root:not([data-theme="dark"]) .helped-opt.is-yes   { color: #1F7A5A; }  /* was 3.0 : 1 */
:root:not([data-theme="dark"]) .counter-t           { color: #6D4AF0; }  /* was 3.1 : 1 */
:root:not([data-theme="dark"]) .cr3-organise        { color: #5B3BD9; }  /* was 4.4 : 1 */

/* ── 2 · A SELECTED PRIVACY CHOICE WAS UNREADABLE ───────────────────────────
   `.choice.is-on` (app.css) sets white text on a 14 % blue wash — written for
   the old dark theme. On the light product that is 1.3 : 1: the option a
   person chose was the one they could not read. Ink on the wash, and a check
   so the chosen option is not carried by colour alone.                       */
.choice { min-height: 44px; }
.choice.is-on { color: var(--ink); font-weight: 600; }
.choice.is-on::before { content: "✓"; margin-right: 8px; font-weight: 700; }

/* ── 3 · TOUCH TARGETS UNDER 44 × 44 ────────────────────────────────────────
   Measured on the rendered controls, clickable box not icon:
       home chips 38–40 px · logo button 28 px · language button 35 px ·
       creator "Edytuj" / "Zapisz wersję roboczą" 37 px · back links 21 px ·
       breadcrumb steps 21 px · legacy seed and facet buttons 33–37 px ·
       the conversation context toggle 34 px                               */
.home3-chip,
.cx-fold,
.mark,
.lang-btn,
.cr3-mini,
.back,
button.seed,
button.help { min-height: 44px; }

.mark, .back { display: inline-flex; align-items: center; }

/* Breadcrumbs grow their HIT AREA, not the row: the padding is taken back by
   an equal negative margin, so the crumb line keeps its height on screen. */
button.trail-step {
  display: inline-flex; align-items: center;
  min-height: 44px; margin-block: -12px;
}

/* ── 4 · MARKUP PROMPT 11 CHANGED ───────────────────────────────────────────
   A few labels became real headings, lists and definition lists so a screen
   reader can navigate them. These rules keep them looking as they did.      */
h2.counter-t, h2.notice-t, h2.dband-t, h3.pgroup-t, h3.myrow-k,
h3.twoax-k, h2.tag, h3.tag, h2.helped-t { font-weight: inherit; }
h2.notice-t, h2.dband-t, h3.pgroup-t, h2.helped-t { font-weight: 700; }
h3.myrow-k, h3.twoax-k, h2.tag, h3.tag { font-weight: 700; }

.cthreads > li { display: block; }
.cthreads > li > .cthread { width: 100%; text-align: left; }
.cthread-demo { color: var(--ink-3); }

.cstat dt { font-size: clamp(28px, 3vw, 42px); font-weight: 800; letter-spacing: -.04em; line-height: 1; }
.cstat dd { margin: 8px 0 0; font-size: var(--t-xs); color: var(--ink-3); }

/* The Story Reader's "nothing to show" heading keeps the empty-state size. */
h1.state-t { font-size: var(--t-lg); }

/* ── 5 · REDUCED MOTION ─────────────────────────────────────────────────────
   tokens.css already shortens every animation to ~0 under reduced motion, but
   not its DELAY: `.rise-*` content waited up to 450 ms, invisible, before the
   near-instant animation began. Nothing should wait to be readable.          */
@media (prefers-reduced-motion: reduce) {
  .rise, .rise-1, .rise-2, .rise-3, .rise-4, .rise-5 { animation-delay: 0s !important; }
}

/* ── 6 · THE LANGUAGE CONTROL ON A PHONE ────────────────────────────────────
   product.css hides the picker below 720px:

       .topbar.is-v3 .lang { display: none; }

   and the comment beside it gives the reason — "375px cannot hold the mark, a
   picker, the action and the account at a legible size". That was a judgement
   rather than a measurement, and measured it is wrong in one direction and
   right in another, so both halves are answered here.

   WRONG: the picker is not expensive. Below 720px product.css already hides
   the button's word, so it costs 39px — the same as swapping the globe for the
   two-letter code, which is what happens below.

   RIGHT: the bar really was full. At 320px the account button was ALREADY
   clipped before this change (its right edge fell at 337px in a 320px
   viewport), because the mark's wordmark takes 98px and the one action takes
   137px. So the wordmark stands down on the narrowest phones. The MARK stays —
   it is still the logo, still the button, still labelled — and the four
   controls then fit inside the viewport at 320px with room to spare.

   Nothing here introduces a second language state. The control is the same
   component, reading `locale()` and calling `setLocale()`; this file only
   decides how wide it is allowed to be.                                      */

/* Desktop keeps the globe and the spelled-out language. */
.lang-code { display: none; }

@media (max-width: 720px) {
  /* The control comes back. */
  .topbar.is-v3 .lang { display: inline-flex; }

  /* Two letters instead of a globe: same width, and it says which language is
     active without the reader having to open anything. The specificity has to
     beat product.css's `.lang-btn span { display: none }`, which would
     otherwise hide the code along with the full word. */
  .lang-btn > svg { display: none; }
  .lang-btn .lang-code { display: inline; }
  .lang-btn .lang-full { display: none; }
  .lang-btn {
    /* A 44px target, and wide enough that "EN" never wraps. */
    min-width: 44px; min-height: 44px; justify-content: center;
    padding: 7px 10px; font-size: var(--t-sm); letter-spacing: .02em;
  }

  /* THE MENU IS ANCHORED TO THE VIEWPORT, NOT TO THE BUTTON.
     Absolutely positioned with `right: 0` it hangs off the button, and the
     button sits 138px from the left on a 320px screen — a 250px menu then
     starts at -73px and half of it is unreachable. Fixed to the viewport's
     right edge it is always fully on screen, at every width. */
  .lang-menu {
    position: fixed; top: 60px; right: 12px; left: auto;
    min-width: 0; width: min(300px, calc(100vw - 24px));
  }
  /* Both options are full-width rows with a real touch target. */
  .lang-opt { min-height: 44px; align-items: center; }
}

@media (max-width: 400px) {
  /* THE 320px BUDGET, MEASURED RATHER THAN GUESSED.
     Before this change the bar asked for 347px inside a 320px viewport: a 20px
     gutter each side, a 34px logo, a 98px wordmark, a 20px gap, and a 253px
     run of utilities. The account button's right edge landed at 337px — it was
     ALREADY clipped, before the language control was ever put back. So this
     block buys back 28px, and it takes them from whitespace and from the one
     element in the bar that carries no action:

       wordmark stands down   −98px of demand   (the logo keeps link + label)
       gutter 20px → 12px     −16px
       bar gap 20px → 12px     −8px
       utilities gap 10px → 8px −4px

     That leaves 319px of content in a 320px viewport, with all four controls
     on screen — the language button, the action, the account, and the mark. */
  .topbar.is-v3 .mark-word { display: none; }
  .topbar.is-v3 { padding-left: 12px; padding-right: 12px; gap: 12px; }
  .topbar.is-v3 .utilities { gap: 8px; }
}

/* ── 7 · THE LANGUAGE MENU, NOW THAT IT HAS TWENTY-FIVE ENTRIES ─────────────
   Two locales fitted in a 250px box with no scroll. Twenty-five do not, and
   the second section is deliberately longer than the first — see Topbar.js.  */
.lang-menu {
  max-height: min(70vh, 460px); overflow-y: auto;
  overscroll-behavior: contain;
}

/* A section heading inside the menu, not a choice. */
.lang-head {
  margin: 6px 10px 4px; padding-top: 4px;
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.lang-head.is-soon { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; }

/* KNOWN, NOT SPOKEN. Dimmed and inert — and NOT hidden, which is the point.
   `cursor: default` rather than `not-allowed`: nothing here is forbidden, it
   simply does not exist yet, and the two read very differently. */
.lang-opt.is-soon { color: var(--ink-4); cursor: default; }
.lang-opt.is-soon:hover { background: transparent; }
.lang-opt.is-soon b { font-weight: 500; }

/* ── 8 · RIGHT-TO-LEFT ──────────────────────────────────────────────────────
   Arabic and Hebrew are in the registry with `dir: "rtl"`, and runtime.js now
   writes that onto <html> the moment such a locale becomes active. Neither
   has a dictionary yet, so nothing below is reachable in the product today —
   it exists so that the first RTL dictionary is a dictionary and not also a
   layout project.

   WHAT IS HANDLED HERE: the chrome, the few physical offsets that would place
   a panel on the wrong side, and the directional arrows. Flexbox and grid
   already mirror themselves under `dir`, which is why the Table, the Story
   Reader's columns and the card grids need nothing.

   WHAT IS NOT CLAIMED: this is not a full right-to-left pass over every
   screen. product.css still uses physical `left`/`right` in places that have
   never been rendered right-to-left, and auditing all of them against a
   language nobody can yet read would be checking a layout against copy that
   does not exist. The honest order is dictionary first, then audit. Recorded
   as a known limitation rather than quietly skipped.                         */

/* The menu hangs off the end of the bar, whichever end that is. */
.lang-menu { inset-inline-end: 0; inset-inline-start: auto; right: auto; left: auto; }

@media (max-width: 720px) {
  .lang-menu { inset-inline-end: 12px; inset-inline-start: auto; right: auto; left: auto; }
}

/* Arrows point the way the reader is going. `→` is a character, not an icon,
   so it is mirrored rather than swapped for a different glyph — one rule
   instead of a second string in every dictionary. */
[dir="rtl"] .tip,
[dir="rtl"] .trail-sep { display: inline-block; transform: scaleX(-1); }

/* The few offsets that are genuinely sided. */
[dir="rtl"] .back { flex-direction: row-reverse; }
[dir="rtl"] .home3-aside.is-left { inset-inline-start: auto; inset-inline-end: 0; }
[dir="rtl"] .home3-aside.is-right { inset-inline-end: auto; inset-inline-start: 0; }

/* Prose set right-to-left still reads from its own edge. */
[dir="rtl"] .read, [dir="rtl"] .xs, [dir="rtl"] .md { text-align: start; }

/* ── 9 · HISTORIE — what this file still owns ───────────────────────────────
   THE CARD ITSELF MOVED TO shell.css. This section used to carry the whole
   catalogue: a 760px column, a one-column list and a full set of
   `.story-card-*` rules. Phase 2 of the approved redesign rebuilt all of that
   — a grid that reflows, a serif title, the lavender kind-chip — and those
   rules now live in shell.css where the rest of the approved surface is.

   KEEPING BOTH COPIES WAS A LOADED GUN. Fourteen selectors were defined in
   both files, so which design a reader saw depended entirely on which
   stylesheet the page happened to load second. It worked only because
   index.html loaded shell.css last; putting quality.css last — which its own
   header says is where it belongs — silently reverted the catalogue to the
   pre-redesign look. The duplicates are deleted rather than reordered, so the
   question cannot come up again.

   `.stories { max-width: 760px }` WENT WITH THEM, and that one was visible.
   It capped the catalogue at 760px while every other screen in the shell runs
   to --shell-max, so the cards sat in a narrow column the approved layout does
   not have.

   WHAT STAYS HERE: the filter pills, the two count lines and the note — none
   of them is in shell.css, and all three are this file's own corrections.   */

.stories-tabs {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 28px;
}
.stories-tab {
  min-height: 44px; padding: 10px 16px;
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: var(--lift); color: var(--ink-2);
  font-size: var(--t-sm); font-weight: 600;
}
.stories-tab:hover { border-color: var(--line-3); color: var(--ink); }
/* The selected filter is carried by ink, weight AND a border, never by colour
   alone — the same rule the rest of the product's selected states follow. */
.stories-tab.is-on {
  border-color: var(--primary); color: var(--primary); background: var(--lift-2);
}

/* The second, separate number: how many stories the chosen category holds.
   It never replaces the global total standing beside it. */
.stories-filtered { color: var(--ink-4); }

.stories-note { margin-top: 40px; max-width: 58ch; }

/* ── AN AUTHORED ACCOUNT, INSIDE THE STORY READER ───────────────────────── */
.st3-account > .st3-read + .st3-read { margin-top: 16px; }
.st3-part > .st3-read + .st3-read { margin-top: 16px; }
.st3-part + .st3-part { margin-top: 26px; }

/* A SUB-HEADING THE AUTHOR WROTE, not a label Fllowly added. Quieter than the
   phase tab above it, because it sits inside that phase rather than competing
   with it — "Finding my place" is part of Po, not a fifth stage. */
.st3-part-k {
  margin: 0 0 10px; font-size: var(--t-sm); font-weight: 700;
  letter-spacing: -.005em; color: var(--ink);
}

/* HOW THIS PUBLICATION WAS MADE — anonymity, generalised details, and whether
   what is on screen is a translation. Deliberately quiet: a reader is being
   told how the text was prepared, not warned about it. */
.st3-pubnote {
  margin-top: 22px; font-size: var(--t-xs); color: var(--ink-3);
  line-height: 1.6; max-width: 58ch;
}

/* THE APPROVED LINE — prominent, and labelled as an approval rather than as
   speech. Visually distinct from `.st3-quote`, which is the verbatim path, so
   the two provenance states cannot be mistaken for one another at a glance. */
.st3-approved { margin: 30px 0 0; padding: 0 0 0 18px; border-inline-start: 3px solid var(--primary); }
.st3-approved-q {
  margin: 0; font-size: var(--t-md); font-weight: 600; line-height: 1.45;
  color: var(--ink);
}
.st3-approved-n { margin-top: 10px; font-size: var(--t-xs); color: var(--ink-3); max-width: 52ch; }

/* Their permission to be asked, said above the control it explains. */
.st3-open { margin-top: 34px; font-size: var(--t-sm); color: var(--ink-2); max-width: 56ch; }

@media (max-width: 720px) {
  .stories-tabs { gap: 6px; }
  .stories-tab { padding: 10px 13px; }
}


/* ============================================================================
   10 · THE DESKTOP HEADER, ACTUALLY CENTRED
   ----------------------------------------------------------------------------
   THE BUG, MEASURED. The bar was `display:flex; justify-content:space-between`
   over three children — mark, nav, utilities. Space-between distributes the
   LEFTOVER space, so where the three children are unequal the middle one does
   not land in the middle: at 1280px the mark was 98px wide and the utilities
   348px, which dragged the navigation group 112px left of the bar's centre.
   It read as an accident because it was one.

   Three unequal children cannot be centred by a flex rule. They can by a grid
   one: `1fr auto 1fr` gives the mark and the utilities an equal share of
   whatever is left, whatever they contain, so the middle column's centre IS
   the bar's centre — by construction rather than by coincidence.

       LOGO        |  centred nav group  |        account / actions

   WHY NOT `position:absolute` ON THE NAV. It would also centre it, and it
   would let a long translated label slide underneath the utilities at a narrow
   desktop width with no warning. The grid keeps all three in the same layout
   flow, so they push on each other and the bar is honest about running out of
   room instead of overlapping silently.

   LOGICAL PROPERTIES THROUGHOUT. `justify-self: start` / `end` and
   `margin-inline` follow the writing direction, so Arabic and Hebrew get the
   mark on the right and the utilities on the left with no second rule and no
   `[dir="rtl"]` override. The old `margin-left` on the nav was a physical
   property that would have shifted the group the wrong way in RTL; it is
   zeroed here rather than mirrored, because the grid already does the work.
   ========================================================================== */

@media (min-width: 901px) {
  .topbar.is-v3 {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    /* Grid fills the track, so space-between has nothing left to distribute;
       stated anyway so the flex rule it inherits cannot be read as active. */
    justify-content: normal;
  }

  /* Each column may shrink below its content rather than force the bar wider
     than the viewport — this is what stops an overflow at 1024 once a locale
     with longer words is selected. */
  .topbar.is-v3 > * { min-width: 0; }

  .topbar.is-v3 .mark { justify-self: start; }

  .topbar.is-v3 .nav {
    justify-self: center;
    justify-content: center;
    /* The old physical nudge. Zeroed: the column is already centred, and a
       left margin would have pushed the group off-centre again — the other
       way in RTL. */
    margin-inline: 0;
    flex-wrap: nowrap;
  }

  /* Vertical centring is stated on the item, not inherited from the bar, so a
     label that wraps to two lines still sits on the bar's mid-line. */
  .topbar.is-v3 .nav-item {
    align-items: center;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
  }

  .topbar.is-v3 .utilities { justify-self: end; }
}

/* The keyboard ring must survive the new stacking context and the 2px active
   rule beneath an item. Offset outward so it never sits under the indicator. */
.topbar.is-v3 .nav-item:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ══════════════════════════════════════════════════════════════════════════
   11 · THE GENERIC DECISION FORM
   --------------------------------------------------------------------------
   Prompt 20. The form stopped being about one decision, which added five
   things to the screen that had nowhere to live: an introduction, read-only
   examples under question one, an optional-step marker with a live count, two
   revealed "coś innego" fields, and a no-match state with two ways onward.

   NOTHING BELOW REDESIGNS ANYTHING. Every rule reuses the tokens, the radii,
   the surfaces and the focus treatment the product already has; `.quiz3-opt`,
   `.quiz3-ta`, `.quiz3-err` and the rest are untouched in product.css. This
   file loads last, so these are additions and not overrides.

   THE 44px RULE IS THE ONE WORTH NAMING. Every new interactive target — the
   skip button, the "coś innego" input, the two buttons under the no-match
   message — is at least 44px tall at every width, because a control somebody
   has to aim at on a phone while making a hard decision is not the place to
   save eight pixels.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── The introduction, above question one only ──────────────────────────── */
.quiz3-intro {
  margin: 14px 0 0;
  max-width: 58ch;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink-2);
}

/* ── The question row: heading, optional marker, live count ─────────────── */
.quiz3-qrow {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
}
.quiz3-qrow .quiz3-q { flex: 1 1 auto; min-width: 0; }

/* "Nieobowiązkowe". A statement, not a control — so it is never a chip shape
   and never looks pressable. */
.quiz3-opt-tag {
  flex: 0 0 auto;
  align-self: center;
  padding: 3px 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--lift);
  font-size: var(--t-xs, 13px);
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink-3);
  white-space: nowrap;
}

/* 2/3 · 1/2. Live, so somebody at the cap can see WHY the next tap did
   nothing before they make it. Tabular figures so the number does not jump
   the heading sideways as it changes. */
.quiz3-count {
  flex: 0 0 auto;
  align-self: center;
  font-size: var(--t-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* ── The three examples under question one ──────────────────────────────
   DELIBERATELY UNPRESSABLE. They are list items with no hover, no cursor
   change, no border and no background — because an example that looks
   tappable becomes, on one mis-tap, an answer somebody did not write. */
.quiz3-eg {
  margin: 16px 0 0;
  padding: 14px 16px;
  border-left: 2px solid var(--line-2);
  background: transparent;
}
.quiz3-eg-k {
  margin: 0;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink-3);
}
.quiz3-eg-list {
  margin: 8px 0 0;
  padding: 0 0 0 18px;
  list-style: disc;
}
.quiz3-eg-li {
  margin: 4px 0 0;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-3);
  cursor: default;
}

/* Said where the two free fields are, once each. */
.quiz3-privacy {
  margin: 14px 0 0;
  max-width: 62ch;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-3);
}

/* ── The revealed "coś innego" field ────────────────────────────────────
   It appears inside the same fieldset as the answer that reveals it, because
   it belongs to that answer: unchoosing the answer takes the field away and
   takes its words out of everything downstream. */
.quiz3-other {
  display: none;
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-card);
  background: var(--lift);
}
.quiz3-other.is-on { display: block; }

.quiz3-other-k {
  display: block;
  margin: 0 0 8px;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink-2);
}
.quiz3-otherin {
  display: block;
  width: 100%;
  min-height: 46px;
  padding: 11px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-input, 12px);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
}
.quiz3-otherin:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-color: var(--primary);
}
.quiz3-other-note {
  margin: 8px 0 0;
  font-size: var(--t-xs, 13px);
  color: var(--ink-3);
}

/* ── Forward actions: skip beside next ──────────────────────────────────
   Skip is quiet and it is on the left of the primary, so the eye reaches the
   thing most people want first. It is only ever rendered where skipping is
   actually allowed — the markup decides that, not a disabled state. */
.quiz3-forward {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.quiz3-skip { min-height: 44px; }

/* Back to the Decision Card, offered once the draft is complete. */
.quiz3-tosum {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-1, var(--line-2));
}
.quiz3-tosum-b { min-height: 44px; }

/* ── The Decision Card's "coś innego" line ──────────────────────────────── */
.dc3-otherline {
  margin: 10px 0 0;
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--ink-2);
}
.dc3-otherk { font-weight: 700; color: var(--ink-3); }

/* ── The no-match state's two ways onward ───────────────────────────────
   Buttons, because both of them go somewhere. Neither of them is a story. */
.tb3-none-a {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 18px;
}
.tb3-none-a > * { min-height: 44px; }

/* ── Phone ──────────────────────────────────────────────────────────────
   320 is the floor this form is built to. Nothing shrinks the type to fit:
   the nine-option steps go to one column and the row of actions stacks, which
   costs scrolling and costs nothing else. */
@media (max-width: 560px) {
  .quiz3-qrow { gap: 8px; }
  .quiz3-eg { padding: 12px 14px; }
  .quiz3-forward { width: 100%; justify-content: stretch; }
  .quiz3-forward > * { flex: 1 1 auto; }
  .tb3-none-a > * { flex: 1 1 100%; }
}

/* ════════════════════════════════════════════════════════════════════════
   9 · TEXT CONTRAST, DARK THEME
   ------------------------------------------------------------------------
   Section 1 above audited the LIGHT palette and said so in as many words:
   "Scoped away from an explicit dark theme, which has its own palette." The
   dark palette then never got the same pass. Measured across fourteen routes
   in both themes, with the page's entrance animations disabled, the product
   failed WCAG AA for text in fifty-nine places — four in light and
   fifty-five in dark. Almost all of them resolved to four values:

       --ink-3  #6D7690   4.41 : 1 ground · 4.04 lift · 3.76 inset
       --ink-4  #454D63   2.37 : 1 ground · 2.17 lift · 2.02 inset
       --primary as text  3.70 : 1 ground · 3.38 lift · 3.15 inset
       two hardcoded hexes in product.css (see 9b)

   FOUR AA-PASSING INK TIERS ARE NOT POSSIBLE IN DARK, and the light pass
   already discovered the same thing — it landed on 5.5 and 4.9, 0.6 ratio
   points apart, rather than a clean four-step scale. Dark does the same:
   ink-2 stays where it is, ink-3 and ink-4 are lifted until both clear 4.5
   on the darkest inset they sit on, and they keep enough distance to read as
   a hierarchy. A fourth distinct tier would have to fail, so there is not
   one.

   --ink-4 IS STILL NOT A TEXT COLOUR BY DESIGN. It is the faintest tier and
   its proper job is a hairline, a map dot, a rule. It is lifted here because
   ninety-seven rules across four stylesheets DO use it for text today, and
   an honest palette is better than a correct one nobody follows. Moving
   those ninety-seven to --ink-3 is the real fix and belongs with the rebuild
   this file's header already anticipates.
   ──────────────────────────────────────────────────────────────────────── */
:root[data-theme="dark"] {
  --ink-3: #8A93A9;   /* 6.49 : 1 ground · 5.94 lift · 5.53 inset */
  --ink-4: #828A9E;   /* 5.72 : 1 ground · 5.23 lift · 4.87 inset */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink-3: #8A93A9;
    --ink-4: #828A9E;
  }
}

/* ── 9a · THE PRIMARY, WHEN IT IS TEXT RATHER THAN A SURFACE ────────────
   #6D4AF0 is the brand violet. It is correct as a FILL in both themes and it
   is borderline as TEXT: 4.34 : 1 on its own lavender pill in light, 3.15 : 1
   on an inset in dark.

   WHY THE TOKEN IS NOT SIMPLY CHANGED. `--primary` is both the fill and the
   text colour, and the two want opposite corrections. Lightening it for dark
   text would lighten every violet button, and `--primary-ink` (white) on a
   lighter violet drops to about 2.6 : 1 — trading a text failure for a button
   failure. So the FILL keeps the brand violet in both themes and only the
   places where the violet IS the text are corrected.

   THE LIST IS DERIVED FROM MEASUREMENT, not from reading the stylesheets.
   Around thirty rules set `color: var(--primary)`; most of them sit on a
   ground where it passes. These are the ones that did not, across eleven
   routes in both themes, and the audit is re-run after the change rather
   than the list being trusted.

   SURFACES ARE LEFT ALONE. Nothing below changes a background, a border or
   a fill; a violet button keeps its violet. */
:root:not([data-theme="dark"]) {
  --violet-text: #5B36E8;   /* 6.25 : 1 ground · 6.69 lift · 5.3 on the lavender pill */
}
:root[data-theme="dark"] {
  --violet-text: var(--primary-lit);   /* #9A80F7 — 6.47 : 1 ground · 5.51 inset */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --violet-text: var(--primary-lit); }
}

/* Both themes, one list. Each entry failed the measured audit.

   `.cr3-organise` is also corrected by section 1 for light (#5B3BD9). This
   rule supersedes it — same hue, within a hundredth of the same ratio — and
   adds the dark case section 1 did not cover. The older line is left where
   it is so section 1 still reads as the record of what it measured. */
.sidenav-b.is-on,
.sidenav-b.is-on .sidenav-t,
.lang-opt.is-on,
.lang-opt.is-on span,
.cr3-organise { color: var(--violet-text); }

/* ── 9a-dark · the rest, which only failed on a dark ground ─────────────
   These pass in light and fail only against the dark grounds. */
:root[data-theme="dark"] .story-card-kind,
:root[data-theme="dark"] .stories-tab,
:root[data-theme="dark"] .st3-eyebrow,
:root[data-theme="dark"] .tb3-private,
:root[data-theme="dark"] .tb3-edit,
:root[data-theme="dark"] .cat-clear,
:root[data-theme="dark"] .st3-av { color: var(--primary-lit); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .story-card-kind,
  :root:not([data-theme="light"]) .stories-tab,
  :root:not([data-theme="light"]) .st3-eyebrow,
  :root:not([data-theme="light"]) .tb3-private,
  :root:not([data-theme="light"]) .tb3-edit,
  :root:not([data-theme="light"]) .cat-clear,
  :root:not([data-theme="light"]) .st3-av { color: var(--primary-lit); }
}

/* ── 9b · TWO HARDCODED HEXES ──────────────────────────────────────────
   `.home3-aside` and `.home3-chip` carry literal colours in product.css.
   A literal cannot follow a theme, which is why one of them measures
   2.82 : 1 in LIGHT — where it was written — and the other 2.36 : 1 in
   dark, where nobody looked. Both are replaced by tokens, so both follow
   the palette from here on. */
.home3-aside { color: var(--ink-3); }
.home3-chip { color: var(--ink); background: var(--lav-2); }

/* ── 9c · GREEN, WHEN IT IS TEXT ───────────────────────────────────────
   `--ok` is a state colour tuned to be legible as a FILL. As text on its own
   dim wash it is 2.77 : 1 in light — the approval badge on a story, and the
   "you can ask" note on a card. Dark is already fine (7.33 : 1) because the
   dark palette lightens --ok to #3FBF8F, so only light needs the darker
   green. */
:root:not([data-theme="dark"]) .st3-badge,
:root:not([data-theme="dark"]) .story-card-ask { color: #1F6E52; }  /* 5.60 : 1 on the wash */

/* ════════════════════════════════════════════════════════════════════════
   10 · ONE FOCUS INDICATOR, AND IT HAS TO SURVIVE DARK
   ------------------------------------------------------------------------
   Audited every field the product actually renders — the decision box, the
   arrival field, the search bar, the quiz textarea, the ask textarea, the
   feedback textarea, the creator's two, the reply box and the Table's note.
   Every one of them has SOME focus indicator, so there is no field a
   keyboard user can land on invisibly. Two things were wrong with them:

   THE RING WAS A LITERAL. Four rules carry `0 0 0 4px rgba(109,74,240,.14)`.
   Fourteen per cent violet reads as a soft halo on a near-white page and as
   nothing whatsoever on #08080F — so in dark mode those four fields showed a
   1px border change and no ring at all. `--ring` is the token the palette
   already defines per theme (.22 in light, .34 in dark) and is what they
   should have been using.

   ONE FIELD HAD NO RING AT ALL. `.reply-box` changed its border to --blue
   and nothing else: the weakest indicator in the product, and blue in a
   violet product.

   THE GLOBAL FALLBACK IS ALSO BLUE. base.css draws `:focus-visible` with
   --blue. It is perfectly visible, so this is consistency rather than
   access — but it is the ring most keyboard users see most often, and it is
   the only violet-product surface that is blue.
   ──────────────────────────────────────────────────────────────────────── */
:focus-visible { outline-color: var(--primary); }

.quiz3-ta:focus,
.ask3-ta:focus,
.fb3-ta:focus,
.cr3-opener:focus,
.cr3-ta:focus,
.reply-box:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--ring);
}

/* `.reply-box` has no border box of its own — it is a bottom rule — so the
   ring needs somewhere to sit. */
.reply-box:focus { border-bottom-color: var(--primary); }

/* ── 9d · THE VERDICT COLOURS, ON A DARK GROUND ────────────────────────
   Section 1 corrected these for light and said it was scoped away from
   dark. Dark then kept the raw data hues, and two of the three fail as
   text on #08080F:

       .repeat-v.is-yes   --blue   #2563FF   4.09 : 1 page · 3.49 inset
       .repeat-v.is-no    --violet #7C3AED   3.50 : 1 page · 2.98 inset
       .repeat-v.is-mixed --amber  #E0A458   9.15 : 1 — already fine

   THE HUE IS THE MEANING AND IT DOES NOT MOVE. Blue is "went and stayed",
   violet is "the other path"; a reader learns those on the fork diagram and
   on the lanes. So the correction uses the palette's own LIT variants of the
   same two hues — lighter, not different — exactly as section 1 darkened
   rather than re-hued them for light.

   `.is-no` is corrected although the audit did not catch it: it renders only
   where a record carries that verdict, and none of the eleven routes
   happened to show one. Leaving a known 2.98 : 1 in place because today's
   corpus hides it would be waiting for a record to arrive and break it. */
:root[data-theme="dark"] .repeat-v.is-yes,
:root[data-theme="dark"] .cthread-s .is-answered { color: var(--blue-lit); }   /* 5.75 : 1 page · 4.89 inset */
:root[data-theme="dark"] .repeat-v.is-no { color: var(--violet-lit); }         /* 6.08 : 1 page · 5.18 inset */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .repeat-v.is-yes,
  :root:not([data-theme="light"]) .cthread-s .is-answered { color: var(--blue-lit); }
  :root:not([data-theme="light"]) .repeat-v.is-no { color: var(--violet-lit); }
}

/* ── 9e · THE PATH COLOURS, WHEN THEY ARE TEXT ─────────────────────────
   `.repeat-v.is-yes` was corrected above and the page still measured
   4.09 : 1. The failing element had no class at all:

       el("span", { style: `color:${r.shape.color}` }, pathOf(r))

   `shape.color` is `var(--path-go)`, set as an INLINE STYLE from the record,
   so it outranked every rule in every stylesheet. Five places did this — the
   ask box, the experience card, two in the thread list and one in the atlas
   — and not one of them could be reached by a selector or caught by a test.

   THE COMPONENTS WERE CHANGED, NOT THE TOKENS, and that is the important
   part. Redefining `--path-go` per theme would also have repainted the fork
   diagram, the lane chips, the signature dots and the map — every FILL that
   uses the same token and is correct as it is. A fill wants the full-strength
   hue; text wants contrast. They are two jobs and they now have two
   mechanisms: `shape.color` still fills, and `.path-ink` colours text.

   THE HUE IS THE MEANING AND IT DOES NOT MOVE. Blue is "went", violet is
   "the other path", amber is "went and came back" — a reader learns them on
   the diagram. Each ink below is the same hue at the lightness its theme
   needs, which is what section 1 already did for `.repeat-v`. */
.path-ink.is-went     { color: var(--path-go-ink); }
.path-ink.is-stayed   { color: var(--path-stay-ink); }
.path-ink.is-returned { color: var(--path-return-ink); }

:root:not([data-theme="dark"]) {
  --path-go-ink:     #2F5FD0;   /* 5.35 : 1 ground · 5.20 inset */
  --path-stay-ink:   #6A3FD6;   /* the violet section 1 already uses */
  --path-return-ink: #8A5A00;   /* 5.54 : 1 ground · 5.39 inset — amber was 2.04 */
}
:root[data-theme="dark"] {
  --path-go-ink:     var(--blue-lit);     /* 5.75 : 1 page · 4.89 inset */
  --path-stay-ink:   var(--violet-lit);   /* 6.08 : 1 page · 5.18 inset */
  --path-return-ink: var(--amber);        /* 9.15 : 1 — already fine */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --path-go-ink:     var(--blue-lit);
    --path-stay-ink:   var(--violet-lit);
    --path-return-ink: var(--amber);
  }
}

/* ── 9f · ONE MORE LIT COLOUR ON THE WRONG GROUND ──────────────────────
   `.xcard-route .is-real` is `--blue-lit`, which is the DARK theme's blue
   used on the light page: 3.25 : 1. It gets the same blue section 1 chose. */
:root:not([data-theme="dark"]) .xcard-route .is-real { color: #2F5FD0; }

/* ── 10a · TWO TARGETS SECTION 3 DID NOT REACH ─────────────────────────
   Re-measured at 320px across fourteen routes. Section 3's list still
   holds; these two are new since it was written.

       #stories-q           27px tall inside a 49px field
       .qskip button        21px, and there are twenty of them

   THE SEARCH INPUT'S WRAPPER WAS ALREADY BIG ENOUGH, which is exactly why
   this is worth fixing rather than waving through: the field LOOKS like a
   44px target and 17px of it does nothing, because padding on a wrapper is
   not hit area for the input inside it.

   `.qskip` is "Nie wiem" / "Nie dotyczy" — the two controls beside every one
   of the creator's ten questions, and the ones somebody uses most if they
   are skipping. They grow their hit area and give the height back with a
   negative margin, so the row of questions keeps its rhythm. */
.cat-search input { min-height: 44px; }

.qskip button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-block: -11px;
}

/* ── 10b · THE PHASE HEADING KEEPS ITS SIZE ────────────────────────────
   `.st3-phase-s` became an `h2` so the story reader's heading outline stops
   skipping from h1 to h3. It is a caption, not a title, and it has to go on
   looking like one — the browser's h2 defaults would make it the largest
   thing in the panel. */
h2.st3-phase-s {
  font-size: var(--t-sm);
  font-weight: 400;
  font-family: inherit;
  letter-spacing: .01em;
}

/* -- 10c · KONTO I POWIADOMIENIA, ZMIERZONE PO ZBUDOWANIU --------------
   The two new screens introduced three failures of exactly the pattern 9a
   already names: a brand colour used as TEXT on its own tinted wash.

       .nav-account-i   the initial in the bar   4.04 : 1 light, 2.87 dark
       .nt-row-a        a row action, dark only  3.38 : 1
       .btn-danger      erase, on its own wash   3.94 : 1 light

   The first two take --violet-text, which is the token 9a introduced for
   this and which already carries a per-theme value. The third needs the same
   treatment for red, so it gets it the same way: the FILL keeps the full
   danger hue, only the text darkens, and dark already measures 4.63 so only
   light is corrected. */
.nav-account-i,
.nt-row-a { color: var(--violet-text); }   /* 5.33 : 1 light - 5.02 dark */

:root:not([data-theme="dark"]) .btn-danger { color: #B93D34; }   /* 4.86 : 1 on the wash */

/* The hover state fills with the full danger colour and puts white on it,
   which is a different pair and already passes. */

/* ══════════════════════════════════════════════════════════════════════════
   11 · THE PALETTE MOVED TO THE APPROVED ONE, AND TWO RULES HERE PRE-DATE IT
   --------------------------------------------------------------------------
   tokens.css now carries the measured mockup palette: a warm cream ground, a
   navy ink scale and a BLUE action colour where the product shipped violet.
   Two things in this file were written against the old one.
   ══════════════════════════════════════════════════════════════════════════ */

/* 11a · `--violet-text` IS NOW THE BLUE, AND THE NAME IS KEPT ON PURPOSE.
   Section 9 introduced it for one problem: `--primary` is correct as a FILL
   and too light as TEXT on its own tinted wash. That problem is unchanged —
   it is a property of a saturated accent on a pale ground, not of the hue —
   so the token stays and only its value moves. Renaming it would have meant
   editing a dozen selectors across two sections to no effect but the name,
   and leaving a violet here would have given the page two accent colours:
   blue buttons and violet links, on the same card.

   Measured against the new grounds: 5.54 : 1 on the cream page, 5.81 on a
   white card, 5.23 on `--lav-1`, 4.51 on `--lav-3`. */
:root:not([data-theme="dark"]) {
  --violet-text: var(--primary);
}

/* 11b · THE BAR HAS TWO CHILDREN IN THE SHELL, AND THE GRID ABOVE ASSUMES
   THREE. Section 8's `1fr auto 1fr` centres a middle column between a mark
   and a utilities group. Inside the shell the rail carries the navigation
   and the wordmark, so the bar renders only `.mark` (empty) and
   `.utilities` — which landed in the AUTO column, i.e. the middle of the
   content area, with an empty 1fr beside it. The account control sat halfway
   across the page instead of at its edge.

   Two children want a flex row, not a three-track grid. The grid is left
   exactly as it is for the pre-shell bar, which still has all three. */
@media (min-width: 901px) {
  .topbar.is-v3.is-shelled {
    display: flex;
    justify-content: flex-end;
  }
  /* The rail prints the wordmark. An empty box in the bar only ever
     contributed a gap. */
  .topbar.is-v3.is-shelled .mark { display: none; }
}

/* 11c · THE ACCENT AS TEXT, ON A DARK GROUND — THREE MORE
   --------------------------------------------------------------------------
   Section 9 corrected every place `--primary` was used as TEXT and measured
   under 4.5 : 1. These three were not in that list for a plain reason: the
   screens they live on were EMPTY when the audit ran. `.tb3-why` and
   `.tb3-open` only exist once a Table has seats in it, and `.dc3-eyebrow`
   only once a decision has been written. Re-running the audit against a
   document that actually holds a decision found them.

       .tb3-why   `Dlaczego ta historia?`  3.16 : 1 on the dark card
       .dc3-eyebrow `Twoja decyzja`        3.45 : 1 on the dark ground

   Same correction as section 9: the token, not a new colour, and the fill
   on these controls is unchanged because neither of them has one.

   `.tb3-open` WAS A THIRD ENTRY HERE and is not any more. The approved
   Table draws `Przeczytaj historię` as a filled button rather than a text
   link, so the pair being measured is white on `--primary` — 5.81 : 1 — and
   correcting its text colour would now be correcting the wrong thing. See
   the Table section in shell.css. */
.tb3-why,
.dc3-eyebrow { color: var(--violet-text); }
