/* css/tafwaq.css — TAFWAQ stylesheet.
   Two sections: "Broadsheet verbatim" (tokens + component classes
   transcribed from the design system) and "TAFWAQ additions" (the
   RTL/responsive/font-application rules layered on top for this app). */

/* ══════════════════════════════════════════════════════════════════════════
   Broadsheet verbatim
   Transcribed from:
   mockup/v3-tafwaq/_ds/broadsheet-5ff8c86e-72f4-4875-9ff6-06215a538909/styles.css
   Excluded: the Google Fonts @import (fonts are vendored — see css/fonts.css,
   loaded via its own <link>, not merged here) and all print-treatment blocks
   (.halftone, .cmyk*, .cmyk-num, .cmyk-head, the plate/press rules, and the
   --color-process-yellow token, which exists only to feed those blocks).
   ══════════════════════════════════════════════════════════════════════ */

:root {
  --color-bg: #f3f2f2;
  --color-surface: #eae9e9;
  --color-text: #201e1d;
  --color-accent: #0088b0;
  --color-accent-2: #d6006c;
  --color-divider: color-mix(in srgb, #201e1d 16%, transparent);

  /* Tonal ramps — generated in OKLCH on one shared lightness scale, so the
     same step of any role matches the others in visual value. */
  --color-neutral-100: #f8f4f4;
  --color-neutral-200: #eae7e7;
  --color-neutral-300: #d7d3d3;
  --color-neutral-400: #bab6b6;
  --color-neutral-500: #9b9797;
  --color-neutral-600: #7d7979;
  --color-neutral-700: #605d5d;
  --color-neutral-800: #444141;
  --color-neutral-900: #2d2b2b;

  --color-accent-100: #e9f8ff;
  --color-accent-200: #cbeeff;
  --color-accent-300: #99e0ff;
  --color-accent-400: #62c5ee;
  --color-accent-500: #38a6cf;
  --color-accent-600: #1186ac;
  --color-accent-700: #006786;
  --color-accent-800: #004961;
  --color-accent-900: #0a303e;

  --color-accent-2-100: #fff1f4;
  --color-accent-2-200: #ffdee6;
  --color-accent-2-300: #ffc0d0;
  --color-accent-2-400: #ff90b1;
  --color-accent-2-500: #ff458e;
  --color-accent-2-600: #d82071;
  --color-accent-2-700: #aa0b56;
  --color-accent-2-800: #790e3d;
  --color-accent-2-900: #4b1528;

  --font-heading: "Source Serif 4", system-ui, sans-serif;
  --font-heading-weight: 600;
  --font-body: "Source Serif 4", system-ui, sans-serif;

  --space-1: 5.0px;
  --space-2: 10.0px;
  --space-3: 15.0px;
  --space-4: 20.0px;
  --space-6: 30.0px;
  --space-8: 40.0px;
  /* The v3 desktop gutter. Not on the Broadsheet spacing scale, which stops at 40 — it is
     the page margin of that design's 1440px canvas, and every screen grid shares it. */
  --masthead-gutter: 64px;
  /* The third process ink. Excluded when this stylesheet was first transcribed, along with
     every .cmyk rule, on the grounds that they were "print treatment" — which was true of
     the photographic separations and wrong about the numeral, the single most recognisable
     thing in the design. Restored for .cmyk-num below. */
  --color-process-yellow: #edbb00;

  --radius-sm: 1px;
  --radius-md: 2px;
  --radius-lg: 4px;

  /* Elevation — derived from the ground: soft ink-tinted shadows on a
     light theme, a hairline edge + ambient darkness on a dark one. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, #2d2b2b 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2d2b2b 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2d2b2b 22%, transparent);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

/* ══════════════════════════════════════════════════════════════════════════
   Components — built with the tokens above. Plain CSS
   on plain HTML: no JavaScript, no build step. Each class is documented in
   readme.md and demonstrated in foundations/ and components/.
   ══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
  font-size: 11px; margin-top: var(--space-1);
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* — rules — */
.hr {
  height: 1px; border: 0; margin: var(--space-4) 0;
  background: var(--color-divider);
}

/* — buttons — */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px —
     the pair sits side by side in sign-up rows */
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); }

/* — forms — */
.field > label {
  display: block; font-size: 12px; margin-bottom: 5px;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input::placeholder { color: color-mix(in srgb, var(--color-text) 65%, transparent); opacity: 1; }
  /* explicit, measured: the browser default gray misses the 4.5:1 small-text bar on
     the surface fill; 65% ink measures 4.8:1 there, still a step quieter than typed
     text (opacity:1 overrides Firefox's default placeholder fade) */
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input, .seg-opt input {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
  border-color: var(--color-accent); background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.seg {
  display: inline-flex; overflow: hidden;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.seg-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: 13px; cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }

/* — cards — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
/* A label at the top of a card («تمرين», the session's ✓) is as wide as its word: a card is a flex
   column, and its children stretch across it unless they say otherwise (the final tour: the label
   was drawn as a full-width outlined box, like an empty field). flex-start is the right edge in RTL. */
.card > .tag { align-self: flex-start; }
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 17px; line-height: 1.2;
}
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }
.card-meta {
  display: flex; align-items: center; gap: 6px; font-size: 11px;
  color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag {
  display: inline-flex; align-items: center; font-size: 11px;
  letter-spacing: 0.02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — navigation — */
.nav {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: none;
}
.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 14px; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }

/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-2); border-bottom: 1px solid var(--color-divider);
}
.table td {
  padding: var(--space-2);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* — dialog — */
.dialog-backdrop {
  position: fixed; inset: 0; display: grid; place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
.dialog {
  width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px;
}
.dialog-body { font-size: 14px; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* ══════════════════════════════════════════════════════════════════════════
   TAFWAQ additions
   Arabic/RTL font application, RTL logical-property fixes, screen
   scaffolding, and the responsive layout primitives for the app shell.
   fonts.css (the @font-face rules for Source Serif 4 + Amiri) is loaded
   via its own <link> in index.html — it is NOT @import'd here.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  /* Latin characters inside Arabic text fall through Amiri to Source Serif 4 —
     keep this order. Amiri is range-limited in fonts.css (its file has "(" but
     not ")", so it must never draw a bracket). */
  --font-body-ar: "Amiri", "Source Serif 4", serif;
}

:lang(ar),
[dir="rtl"] {
  font-family: var(--font-body-ar);
}

/* ...but NOT inside a formula, and this was rendering mathematics that said the opposite of
   what the author wrote.

   `:lang(ar)` matches every element on an Arabic page, because language is inherited — the
   spans KaTeX generates included. katex.min.css sets its font on `.katex` alone and lets the
   descendants inherit, and a rule that matches an element directly always beats a value it
   would have inherited, so every KaTeX span without a font class of its own was being set in
   Amiri. Most of them survived it: Amiri has `(`, `=`, `0` and they merely looked out of
   place beside the KaTeX italics.

   `\neq` did not. KaTeX draws it by overlaying a slash held at U+E020, a PRIVATE USE
   codepoint that exists only in KaTeX_Main — Amiri has nothing there, so the slash vanished
   and `x ≠ 0` rendered as `x = 0`. Six of them on one lesson screen, each stating the
   negation of its own meaning. Nothing could see it: the DOM text is identical either way,
   KaTeX reported no error, and no font file 404'd.

   `font-family: inherit` rather than a restated stack, so descendants take whatever `.katex`
   itself resolves to and there is one place that decides. The classes KaTeX puts a font on
   are two selectors deep and keep winning. */
.katex * {
  font-family: inherit;
}

/* — RTL logical-property fixes — */
.table th {
  /* Broadsheet hardcodes text-align:left above; "start" flips correctly
     for RTL without needing a [dir="rtl"] override. */
  text-align: start;
}
.btn-ghost {
  /* Restated explicitly as a logical property (matches the Broadsheet
     value) so inline padding stays start/end-aware under RTL. */
  padding-inline: var(--space-1);
}

/* — an Arabic placeholder in a left-to-right maths field — The field stays ltr for what is typed;
   its placeholder is Arabic prose, so it reads right to left, in the Arabic font, from the right.
   It inherited the field's: "اكتب السطر التالي" in monospace, "قيمة لـ x" with the x first
   (preview tour, D8). */
:lang(ar) input[dir="ltr"]::placeholder {
  direction: rtl;
  text-align: right;
  unicode-bidi: plaintext;
  font-family: var(--font-body-ar);
}

/* — an inline formula does not break — js/inline-maths.js isolates each formula of Arabic prose
   (MATHS_ATTRS); it broke at its own spaces ("x·(1/x) − ln | x = 1 − ln x", preview tour, D9).
   Every run of the corpus fits a 390 px card on one line (tests/preview-tour-browser.test.js's
   gate). js/grade.js's KaTeX runs carry no dir attribute and keep KaTeX's own breaks. */
span[dir="ltr"][data-run-kind="math"] {
  white-space: nowrap;
}

/* — `hidden` hides, whatever display the element carries. The browser's own [hidden] rule loses
   to any inline or class display, so the barème's yes/no, every lesson step body and question
   "why" (a menu's "why" is its right answer) and the simulator's empty pill stayed drawn
   (preview tour, D3/D4). Covers the old .screen[hidden]. */
[hidden] {
  display: none !important;
}

/* .badge-img — the rendered achievement-card <img> inside js/badges.js's
   consent modal (fix wave finding 3: that modal now reuses the .dialog
   component below rather than the deleted css/app.css's .modal-card). Only
   this one rule is bespoke; sizing/max-width already come for free from
   this stylesheet's global `img { display:block; max-width:100% }` rule
   above and .dialog's own flex `gap`. */
.badge-img {
  border-radius: var(--radius-md);
}

/* — responsive layout primitives — */
.tfw-shell {
  max-width: 1440px;
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.tfw-cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
@media (min-width: 1100px) {
  .tfw-cols {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* — the reading column —
   `.tfw-shell` is 1440px, which is right for the top nav and the footer and wrong for
   everything a person reads. Every screen is a single stack of full-width cards, so on a
   laptop a bank row became 1230px wide and its state tag — held to the far end by
   `justify-content: space-between` — ended up eleven hundred pixels from the title it
   belongs to. Prose ran the same width, at roughly twice a comfortable measure.

   THIS IS NOT A DESKTOP COMPOSITION and does not pretend to be one. `.tfw-cols` has offered
   three columns above 1100px since the design system was transcribed and no screen has ever
   used it; laying the screens out for a wide viewport is a design decision, not a bug fix,
   and it waits for the design. What this does is stop the phone layout from being stretched
   to a width nothing in it was drawn for. */
main.tfw-shell > .screen {
  max-width: 46rem;
  margin-inline: auto;
}

/* Above the masthead's breakpoint the screens ARE the page, at its gutter — the reading
   column is what keeps a phone layout honest on a narrow laptop, not a cap on the design. */
@media (min-width: 768px) {
  main.tfw-shell > .screen {
    max-width: none;
    padding: 48px var(--masthead-gutter) 72px;
  }
}

/* ── THE BROADSHEET GRID ────────────────────────────────────────────────────────────
   The v3 desktop design lays a screen out in unequal columns — home is `7fr 3fr 3fr` at a
   60px gap — which is the newspaper grammar the whole design rests on: one wide column
   carrying the argument, narrow ones carrying the apparatus.

   TWO columns here and not three, and the reason is worth writing down. The reference's
   home carries a competency tree and a predicted average in its two narrow columns. This
   app has both — on the path screen and in the simulator — and moving them onto home is a
   product decision about what the first screen is for, not a layout one. Inventing a third
   column to fill a grid would be the design leading the product by the nose. So the grammar
   is here and the third column waits for that decision.

   Below the masthead's breakpoint this collapses to the single stack the phone has always
   had, and the reading column below keeps it honest in between. */
.tfw-broadsheet {
  /* The reference's own ratios, per screen: home 7/3/3 at 60, lesson and path 8/4 at 72,
     the bank 4/8 at 72 (narrow first — the list leads), the simulator 7/5 at 80. One
     primitive with two variables rather than five classes, so a screen states its own
     proportion where it uses it and nothing has to be looked up here. */
  --bs-cols: 8fr 4fr;
  --bs-gap: 72px;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

@media (min-width: 900px) {
  .tfw-broadsheet {
    grid-template-columns: var(--bs-cols);
    gap: var(--bs-gap);
    align-items: start;
  }
  .tfw-broadsheet-main,
  .tfw-broadsheet-aside { display: flex; flex-direction: column; gap: var(--space-4); }
}

/* Below the breakpoint the grid is one column, and DOM order decides what a student reads
   first. The aside is apparatus — a legend, a trace panel, a summary — so it follows the
   argument on a phone even where the design puts it alongside on a page.
   Except where the apparatus IS the headline: a student opens the simulator to see the
   predicted mark, and reading it after a table of eight coefficients is the wrong order.

   SCOPED TO THE NARROW WIDTH, not merely overridden at the wide one. Grid auto-placement
   honours `order`, and a media query adds no specificity — so an unscoped rule sitting later
   in the file beat the reset inside the desktop block and put each pane in the other's
   track, silently inverting every ratio in the design. */
@media (max-width: 899px) {
  .tfw-broadsheet-aside { order: 1; }
  .tfw-broadsheet--aside-first .tfw-broadsheet-aside { order: -1; }
}

/* THE BANK'S TWO PANES. Both exist at every level; below the grid's breakpoint only the one
   matching the current level is shown, which is the drill-down a phone has always had. The
   panes are never destroyed, so the list keeps its scroll position while a student moves
   between exercises on a page. */
@media (max-width: 899px) {
  .tfw-bank[data-level="list"] [data-pane="detail"] { display: none; }
  .tfw-bank[data-level="paper"] [data-pane="list"],
  .tfw-bank[data-level="item"] [data-pane="list"] { display: none; }
}
/* On a page the back buttons are noise: the list they go back to is on screen. */
@media (min-width: 900px) {
  .tfw-bank [data-back-list] { display: none; }
}

/* Content that exists ONLY because the masthead does not.
   The masthead carries the weekly target and XP, which is where the design puts them and
   the reason its home screen has no stats row. Below 768px there is no masthead, so home
   carries them instead; above it, home must not, or the same two numbers appear twice on
   one screen. One rule, at the masthead's own breakpoint, so the two can never disagree
   about which of them is showing. */
@media (min-width: 768px) {
  .tfw-masthead-carries { display: none; }
}

/* The screen title, in the reference's proportion: large, on the paper rather than in a
   card, with its one-line statement of purpose under it. */
.tfw-screen-head {
  background: none;
  border: 0;
  box-shadow: none;
  padding-inline: 0;
}
.tfw-screen-head .card-title { font-size: clamp(26px, 4vw, 44px); line-height: 1.05; }

/* The countdown card carries the design's own numeral, so it drops the card chrome the
   rest of the screen uses: a masthead numeral sits on the paper, not in a box. */
/* Numeral and words on ONE line, the words hanging off the numeral's foot — the
   arrangement the reference uses, and the reason the numeral can be this big without
   becoming a headline that has nothing to do with the sentence under it. Stacks below
   640px, where two columns of this would leave the words four characters wide. */
.tfw-countdown {
  background: none;
  border: 0;
  box-shadow: none;
  padding-inline: 0;
  display: flex;
  /* `.card` sets `flex-direction: column`, so the row has to be asked for by name. */
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2) var(--space-4);
}
@media (min-width: 640px) {
  .tfw-countdown {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
  }
  /* The provisional-dates note is a footnote to the pair, so it takes the whole row back. */
  .tfw-countdown > .text-muted { flex-basis: 100%; }
}
.tfw-countdown-num {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(72px, 13vw, 150px);
  margin: 0;
}
.tfw-countdown-words {
  font-size: clamp(16px, 2.4vw, 22px);
  line-height: 1.25;
  max-width: 14ch;
  margin: 0 0 .35em;
  color: var(--color-neutral-800);
}

/* ── THE MISREGISTERED NUMERAL ──────────────────────────────────────────────────────
   Transcribed from the Broadsheet design system's `.cmyk-num`, which this stylesheet's
   header records as deliberately excluded — "all print-treatment blocks". That call was
   right about the photographic separations, which need SVG filters and an image pipeline
   this app has neither of, and wrong about this one: the countdown numeral is the first
   thing on the first screen and the single most recognisable mark in the design. Dropping
   it is why the app could be a faithful transcription of the tokens and still not look
   like the design.

   THE CONSTRUCTION, because it is not obvious from the rules. Three plates — cyan, magenta,
   yellow, and no black — each a copy of the same digits, offset by a fraction of an em and
   multiplied together. Where all three overlap the inks product to near-black and you read
   the number; at the edges each plate escapes by its own offset and you get the colour
   fringe of a press slightly out of register. The `.paper` span underneath is the white of
   the sheet, built from two offset text-shadows so the plates have a ground wider than any
   of their offsets.

   The `.paper` span carries the text for assistive technology; the three plates are
   `aria-hidden` repeats, so a screen reader hears the number once.

   `--press-nx/--press-ny` in the design system are a pointer-lean driver that makes the
   registration drift as the mouse moves. Not transcribed: it is a mouse affordance on a
   product whose device is a cheap Android, and the rules below read the variables as 0 when
   nothing publishes them, which is exactly the still press this needs. */
.cmyk-num {
  --cmyk-num-ground: var(--color-bg);
  position: relative;
  width: fit-content;
  line-height: .9;
  font-feature-settings: "pnum" 1;
  background: var(--cmyk-num-ground);
  padding: .12em;
  margin: -.12em;
}
.cmyk-num .paper {
  color: var(--color-bg);
  text-shadow: .036em .025em 0 var(--color-bg), -.031em -.023em 0 var(--color-bg);
}
.cmyk-num .plate {
  position: absolute;
  inset: .12em;
  mix-blend-mode: multiply;
}
.cmyk-num .plate-c { color: var(--color-accent); }
.cmyk-num .plate-m {
  color: var(--color-accent-2);
  translate: calc(.036em + .018em * var(--press-nx, 0)) calc(.025em + .0125em * var(--press-ny, 0));
}
.cmyk-num .plate-y {
  color: var(--color-process-yellow);
  translate: calc(-.031em + .018em * var(--press-nx, 0)) calc(-.023em + .0125em * var(--press-ny, 0));
}

/* ── THE MASTHEAD ───────────────────────────────────────────────────────────────────
   The v3 desktop design's shell, transcribed from
   mockup/v3-tafwaq/TAFWAQ Desktop.dc.html. Every measurement below is that file's:
   30px 64px padding, a 52px wordmark at -0.015em, 11.5px uppercase tagline at 0.16em,
   a 3px rule over a 1px rule, a 12.5px byline, and nav at 16px with 34px gaps and a
   2px underline on the current screen.

   IT IS DESKTOP ONLY. Below 768px it is display:none and the .tabbar carries navigation
   exactly as it did before — the phone is the device this product is built for, every
   test in the suite runs at 390px, and none of that changes here.

   The rules are the whole device: a broadsheet masthead is a wordmark over a heavy rule,
   and everything below hangs off it. Getting their weights right (3px, then 1px, then
   hairlines at --color-divider) is most of what makes the page read as a newspaper rather
   than as an app with a big title. */
.masthead { display: none; }

@media (min-width: 768px) {
  .masthead {
    display: block;
    position: sticky;
    top: 0;
    z-index: 9;
    background: var(--color-bg);
    padding: 30px var(--masthead-gutter) 0;
  }

  .masthead-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 40px;
  }

  .masthead-brand { display: flex; align-items: baseline; gap: 20px; }

  .masthead-name {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 52px;
    line-height: .9;
    letter-spacing: -.015em;
  }

  .masthead-tagline {
    font-size: 11.5px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--color-neutral-700);
  }

  .masthead-stats { display: flex; align-items: center; gap: 26px; }

  .masthead-stat { display: flex; align-items: baseline; gap: 8px; }
  .masthead-stat b {
    font-family: var(--font-heading);
    font-size: 20px;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
  }
  .masthead-stat span { font-size: 11.5px; color: var(--color-neutral-700); }

  /* The ANTI-2 repair note, in a strip that has no room to argue. It is a whole sentence in
     French ("Un jour manqué a été compensé automatiquement"), so it gets its own line under
     the stat rather than pushing the lang switch off the end: the stat becomes a two-line
     block, which the baseline-aligned flex row above absorbs without moving anything else.
     Muted and un-alarmed on purpose — it is reassurance, not a warning. */
  .masthead-stat { flex-wrap: wrap; }
  .masthead-stat [data-stat-note] {
    flex-basis: 100%;
    max-width: 22ch;
    font-size: 10.5px;
    line-height: 1.3;
    color: var(--color-neutral-600, var(--color-neutral-700));
  }

  /* The double rule. `--color-text` and not a divider token: the mockup draws these in the
     ink colour, and a broadsheet masthead rule that is grey is not a masthead rule. */
  .masthead-rule-thick { height: 3px; background: var(--color-text); margin-top: 20px; }
  .masthead-rule-thin { height: 1px; background: var(--color-text); margin-top: 3px; }
  .masthead-rule-hair { height: 1px; background: var(--color-divider); }

  .masthead-identity {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 10px 0;
    font-size: 12.5px;
    color: var(--color-neutral-700);
  }

  .masthead-nav { display: flex; gap: 34px; padding: 13px 0 0; }
  .masthead-nav a {
    padding-bottom: 12px;
    border-bottom: 2px solid transparent;
    color: var(--color-neutral-700);
    font-size: 16px;
    font-family: var(--font-body);
    white-space: nowrap;
    text-decoration: none;
  }
  .masthead-nav a:hover { color: var(--color-text); }
  /* The current screen, marked the way a broadsheet marks a section: heavier ink and a
     rule under the word, never a filled tab. */
  .masthead-nav a[aria-current="page"] {
    color: var(--color-text);
    font-family: var(--font-heading);
    border-bottom-color: var(--color-accent);
  }
  /* The hairline under the nav row sits UNDER the active underline, so the two do not
     stack into a 3px band. */
  .masthead-nav + .masthead-rule-hair { margin-top: -1px; }
}

/* .phone-lang-switch — AR/FR/EN row visible only below 768px (fix wave
   finding 2b): .topnav above (which held the only other lang switch) is
   display:none there, so FR/EN were unreachable on a real phone. Reuses
   the existing .btn/.btn-secondary component (no new button styling) —
   only visibility and a compact centered row are bespoke here. */
.phone-lang-switch {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
}
@media (min-width: 768px) {
  .phone-lang-switch {
    display: none;
  }
}

/* .tabbar — mobile bottom tab bar, seven items, hidden at/above 768px.

   It said "seven equal-width items" and they were not equal and they did not fit. `1fr` is
   `minmax(auto, 1fr)`, and `auto` will not shrink a column below its content, so seven
   columns sized by their longest word came to 483px in French and 411px in English inside
   a 390px phone. The overflow went off the end of the bar: `Simulateur` was entirely off
   screen and `Sujets` half off, so on a French phone the simulator was UNREACHABLE from
   the tab bar. Arabic fitted only because its labels are short.

   `minmax(0, 1fr)` is what makes the columns actually equal and actually shrink. At 320px
   that is 45px each, which no French label fits on one line, so they wrap;
   `overflow-wrap: break-word` is the guarantee that a single long word breaks rather than
   pushing the grid wide again.

   Below 360px only (from 360px the bar fits whole words; see the rule after `body` below):
   WHERE it breaks is decided in content/ui-copy.js, by soft hyphens in the labels. Left to
   the browser this read `Entraîne/ment` and `Calendri/er`, mid-syllable. `hyphens: auto` is
   set — js/i18n.js puts the language on <html> for it — but it needs a hyphenation
   dictionary, and neither a headless Chromium nor a stripped-down Android build reliably
   ships one, so it is a bonus and not the mechanism.

   --tabbar-h is the second half of the fix and it is not cosmetic. The bar is
   `position: fixed` and nothing reserved its height, so the bottom of every long page sat
   UNDERNEATH it: at the foot of the lesson screen that was the "next unit" call to action
   and the footer — a button a student could see and not press. `body` gets the height back
   as padding, plus the safe-area inset so it clears the home indicator on a notched
   phone. One number, used by both, so they cannot drift. */
:root {
  --tabbar-h: 52px;
}
.tabbar {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  min-height: var(--tabbar-h);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: var(--color-surface);
  border-top: 1px solid var(--color-divider);
}
.tabbar > button {
  /* A grid item's default `min-width: auto` refuses to shrink below its content, which is
     the same refusal as `1fr`'s one level down: without this the column obeys minmax(0,1fr)
     and the BUTTON inside it still pushes wide. */
  min-width: 0;
  padding: 6px 2px;
  font-family: inherit;
  font-size: 11px;
  line-height: 1.2;
  hyphens: auto;
  overflow-wrap: break-word;
}
body {
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
}
/* From 360px, every label is whole words (the final tour: at 390px French read «Entraî-nement»,
   «Calen-drier», «Simu-lateur»). Equal columns give each label 48px at 390px and «Entraînement»
   needs 73px at 11px, so no font size alone fits it. The tabs share the bar from a zero basis and
   none goes below its longest word (min-width: min-content), so the short labels give way to the
   long ones; soft hyphens and break-word are off, and two-word labels («Le cours», «Exam bank»)
   wrap between their words. French is drawn at 10px. Measured in Chromium (tests/final-tour-
   browser.test.js): the narrowest tab is 41px at 360px in French, 50px at 390px; ar and en keep
   11px. Below 360px the equal columns and the soft hyphens above stay: at 320px no French layout
   fits whole words at a legible size.
   The row wraps. Android's WebView scales the text by the system font size (the app sets no text
   zoom), and a tab that may not go below its longest word cannot shrink when its label grows: at
   ×1.15 «Simulateur» left the screen at 360px. A tab that does not fit goes to a second row,
   whole, as the equal columns grew taller; at the default size nothing wraps. */
@media (min-width: 360px) {
  .tabbar {
    display: flex;
    flex-wrap: wrap;
  }
  .tabbar > button {
    flex: 1 1 0;
    min-width: min-content;
    padding-inline: 1px;
    -webkit-hyphens: none;
    hyphens: none;
    overflow-wrap: normal;
  }
  :root:lang(fr) .tabbar > button {
    font-size: 10px;
  }
}
@media (min-width: 768px) {
  .tabbar {
    display: none;
  }
  /* The bar is gone above 768px, so the space it reserved has to go with it. */
  body {
    padding-bottom: 0;
  }
}

/* .lesson-tex — a display formula that wraps.
   KaTeX produces one unbreakable box per formula, so on a 390px phone seven formulas in
   the corpus overflowed their 320px column by 47 to 233px and had to be dragged sideways
   inside a page that scrolls vertically. js/screens/lesson.js splits each one at the
   `\qquad` gaps its author put between separate statements; these rules let the parts wrap
   onto as many lines as they need.
   `direction: ltr` because mathematics reads left to right whatever the page does. */
.lesson-tex {
  direction: ltr;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 26px;
  margin: 10px 0;
}
/* A single part that is still wider than the column keeps its own scroll — the difference
   quotient with its limit has nothing in it to break at — with a fade on the trailing edge
   so a reader can see that it continues. `background-attachment: local` on the covering
   gradient and `scroll` on the shadow is the standard scroll-shadow pair: the cover moves
   with the content and hides the shadow at rest, so the fade appears only when there is
   something past the edge. No JavaScript, and it stays right after a resize. */
.lesson-tex-part {
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  background:
    linear-gradient(to right, var(--color-surface) 60%, transparent) 0 0 / 20px 100% no-repeat local,
    linear-gradient(to left, var(--color-surface) 60%, transparent) 100% 0 / 20px 100% no-repeat local,
    linear-gradient(to left, rgba(0, 0, 0, 0.10), transparent) 100% 0 / 16px 100% no-repeat scroll,
    linear-gradient(to right, rgba(0, 0, 0, 0.10), transparent) 0 0 / 16px 100% no-repeat scroll;
}

/* -- Lesson reading (lesson_reading.feature) ---------------------------------------------
   Each lesson is a landmark the reading detector measures; the wrapper is layout-neutral
   (the main column's cards have no margins, and the gap is inherited). Not display: contents,
   which would leave no box to measure, and not content-visibility, whose estimated sizes would
   move the resume target. The read mark is muted, never an accent colour: accents mean mastery. */
.lesson-section { display: flex; flex-direction: column; gap: inherit; }
.lesson-kicker-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.lesson-read-mark { font-size: 12px; color: var(--color-neutral-700); }
.lesson-toc ol { list-style: none; margin: 0; padding: 0; }
.lesson-toc li + li { border-block-start: 1px solid var(--color-divider); }
.lesson-toc summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; gap: 8px; list-style: none; }
.lesson-toc summary::-webkit-details-marker { display: none; }
/* A flex summary loses the native marker, and a closed list must look openable: the same
   direction-aware glyphs the path screen's tree toggle uses. */
.lesson-toc summary::before { content: '▸'; font-size: 10px; flex: none; color: var(--color-accent-700); }
[dir="rtl"] .lesson-toc summary::before { content: '◂'; }
.lesson-toc details[open] > summary::before { content: '▾'; }
.lesson-toc button {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 8px;
  width: 100%; min-height: 44px; padding-block: 6px; padding-inline: 0;
  font: inherit; text-align: start; background: none; border: none; cursor: pointer; color: inherit;
}
.lesson-toc-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lesson-toc-kicker { font-size: 11px; color: var(--color-neutral-700); }

/* The path screen's reading row: a muted line and one ghost button, apart from mastery. */
.path-reading {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
  padding: 0 var(--space-3) 8px; min-height: 44px;
}
.path-reading [data-open-lesson] { min-height: 44px; }
