/* tokens.css — TechCritic's foundation layer (Task 4).

   This file carries two things and nothing else: the three @font-face
   declarations, and the :root token layer every page and component draws from.
   No component rules, no element selectors, no reset — those arrive in Task 5.
   Keeping the token layer alone in its own file is what lets
   scripts/token-contrast.mjs parse it without a CSS engine.

   Every number here that was measured says what it was measured against, so the
   next person to touch it can tell whether the margin still survives their
   change. A number with no stated basis is a number nobody can safely edit.

   Checked by: node company/techcritic/scripts/token-contrast.mjs
   Written up in: company/techcritic/DESIGN-SYSTEM.md */

/* Declared before anything else so the UA picks light form controls, scrollbars
   and canvas from the first paint. There is no dark theme yet, and this states
   that as a fact rather than leaving the UA to guess. See DESIGN-SYSTEM.md §9. */
:root { color-scheme: light; }

/* ---------------------------------------------------------------------------
   TYPEFACES

   Two families, three files, self-hosted from fonts/, shipped byte-for-byte as
   published by Fontsource 5.3.0. Provenance and hashes: fonts/PROVENANCE.md.

   The weight ranges below are not guesses and not round numbers — they are the
   fvar axis ranges read out of the font binaries themselves (Newsreader
   wght 200..800, Inter wght 100..900). Declaring the true range is what makes
   600 and 700 real interpolated weights instead of a browser-synthesised fake
   bold. That defect ran unnoticed on the Tireless site for years and is the
   reason company/website/scripts/font-gate.mjs exists.

   font-display:swap — text is readable in the fallback face immediately and
   re-renders when the webfont lands. On a site that is entirely typography we
   would rather show the reader words in Georgia for 200ms than show them
   nothing.

   unicode-range is DELIBERATELY OMITTED, and this is the least obvious decision
   in the file. Declaring one would tell the browser "everything in this range
   comes from this file", and it would then stop falling back for characters the
   file does not actually contain. Measured against the cmap of all three
   binaries, these six characters are absent from both families:

       U+2248  U+2020  U+2713  U+2717  U+2605  U+2192
       (approx, dagger, tick, cross, star, arrow)

   With no unicode-range they render from the fallback stack instead of
   disappearing. That is the safety net, not the plan: the plan is that
   templates never use them at all. A tick, a cross, a star or an arrow is an
   inline <svg> with an accessible name. A verdict box that says "pass" with a
   character we do not own would change typeface mid-sentence.
   --------------------------------------------------------------------------- */

@font-face{font-family:'Newsreader';font-style:normal;font-weight:200 800;font-display:swap;src:url(fonts/newsreader-latin-wght-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:normal;font-weight:100 900;font-display:swap;src:url(fonts/inter-latin-wght-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:italic;font-weight:100 900;font-display:swap;src:url(fonts/inter-latin-wght-italic.woff2) format('woff2')}

/* ---------------------------------------------------------------------------
   METRIC-MATCHED FALLBACKS — added in Task 5, on a measurement, not on principle.

   THE SHIFT, MEASURED. With the webfonts blocked, the specimen article rendered
   3,981px tall; with them loaded, 3,688px. The footer moved 261px and the first
   h2 moved 33px. font-display:swap means a real reader sees the first state and
   then the second, so that is a 293px jump on a page one screen tall.

   THE CAUSE, WHICH IS NOT THE OBVIOUS ONE. It is not that the glyphs are shaped
   differently. It is that --tc-measure is expressed in `ch`, and `ch` is the
   advance of "0" IN WHATEVER FONT IS CURRENTLY RESOLVED. Measured on this
   machine, mean prose advance: Inter 0.4784em against Segoe UI 0.4474em — Inter
   is 6.9% wider. So before the webfont arrives the column itself is ~7%
   narrower, the text wraps into more lines, and the page is taller. The swap
   changes the LAYOUT, not just the letterforms.

   THE FIX. size-adjust scales the fallback until its advances match, which pulls
   `ch` back into agreement and holds the column still. ascent/descent-override
   are expressed against the ADJUSTED em, so they are the real metrics divided by
   the size-adjust factor.
     sans  : 0.4784 / 0.4474 = 106.93%.  Inter hhea 1984/-494 per 2048 upem,
             so 96.88% / 24.12% before adjustment, 90.59% / 22.56% after.
     serif : 0.4423 / 0.4420 = 100.07%.  Georgia is already an almost perfect
             width match for Newsreader — a genuinely lucky pairing, and worth
             knowing before anyone swaps the serif for something else.

   MEASURED AFTER: see the Task 5 report. THIS IS A PARTIAL FIX AND SAYS SO:
   local() only resolves a face that is actually installed. Georgia and Segoe UI
   are Windows and macOS faces; on most Linux and Android devices neither exists,
   these two rules resolve to nothing, and the stack falls through to the
   unadjusted generic. Tuned on Windows 11 / Chrome 151. Re-measure elsewhere
   before claiming it holds there. */
@font-face{font-family:'Inter Fallback';src:local('Segoe UI'),local('Roboto'),local('Helvetica Neue'),local('Arial');size-adjust:106.93%;ascent-override:90.59%;descent-override:22.56%;line-gap-override:0%}
@font-face{font-family:'Newsreader Fallback';src:local('Georgia'),local('Times New Roman');size-adjust:100.07%;ascent-override:73.45%;descent-override:26.48%;line-gap-override:0%}

:root{

  /* -- surfaces ----------------------------------------------------------- */
  --tc-paper: #fbfaf8;      /* the page. Warm off-white, not #fff — this is a broadsheet, not a spreadsheet */
  --tc-wash:  #f2efe9;      /* section wash, table header rows, verdict-box fill */
  --tc-card:  #ffffff;      /* the one true white, used where a block must lift off the paper */

  /* -- ink ---------------------------------------------------------------- */
  /* Contrast ratios in this block are measured against the three surfaces by
     token-contrast.mjs, printed paper / wash / card. All three inks clear the
     4.5:1 floor of SC 1.4.3 on every surface, including the meta ink, which is
     the one that usually fails on sites like this. */
  --tc-ink-1: #14161a;      /* display ink — headlines, the wordmark.  17.36 / 15.78 / 18.11 */
  --tc-ink-2: #2c3138;      /* body ink.                                12.55 / 11.41 / 13.10 */
  --tc-ink-3: #545c66;      /* meta ink — bylines, captions, sub-labels. 6.49 /  5.90 /  6.77 */

  /* -- the single editorial accent ---------------------------------------- */
  /* Blue, and the reason is negative rather than positive: red is already
     spoken for by --tc-bad. On a site whose whole job is delivering verdicts, a
     reader must never have to work out whether a red thing is a link or a
     warning. See DESIGN-SYSTEM.md §4 rule 3.        9.56 / 8.69 / 9.97 */
  --tc-accent: #17427c;

  /* -- rules -------------------------------------------------------------- */
  --tc-rule-hair:   #dcd7cd;   /* DECORATIVE ONLY — 1.37:1 on paper. Never carries meaning. */
  --tc-rule-strong: #8f8778;   /* meaningful borders: table grid, verdict-box keyline, input edges.
                                  3.41:1 on paper, against the 3:1 floor of SC 1.4.11 non-text. */

  /* -- focus -------------------------------------------------------------- */
  --tc-focus: #17427c;         /* 3px solid, 2px offset, never removed. 9.56:1 on paper. */

  /* -- status — RESERVED --------------------------------------------------- */
  /* Never a chart series. Always shipped with an icon AND a word: colour alone
     fails SC 1.4.1. Ratios paper / wash / card. */
  --tc-good: #1f6b3a;       /*  6.25 / 5.68 / 6.52 */
  --tc-warn: #8a5a00;       /*  5.68 / 5.16 / 5.93 */
  --tc-bad:  #a01722;       /*  7.62 / 6.93 / 7.95 */

  /* -- chart categorical — FIXED ORDER, FOUR SLOTS, NEVER CYCLED ----------- */
  /* Four, not five. A candidate five-slot ramp including a purple was rejected
     on measurement: purple against blue came out at OKLab dE 11.5 under normal
     colour vision, below our floor of 15 — hard to tell apart even for a reader
     with no colour-vision deficiency at all. The four that ship clear the floor
     on every pair, not merely on adjacent pairs. A fifth series folds into
     "Other", or the chart becomes small multiples. Do not re-add the purple. */
  --tc-cat-1: #3b6fd4;
  --tc-cat-2: #c77a05;
  --tc-cat-3: #00a0b0;
  --tc-cat-4: #b8336a;

  /* -- type --------------------------------------------------------------- */
  /* Newsreader sets headlines, the wordmark and verdict-box headings. Inter
     carries all body text, all UI and every table. A serif body at 17px on a
     screen is a nostalgia decision, not a legibility one. */
  --tc-serif: 'Newsreader', 'Newsreader Fallback', Georgia, 'Times New Roman', serif;
  --tc-sans:  'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* Every fluid size keeps a rem term alongside its vw term. That is not
     decoration: a size expressed in vw alone stops responding to the reader's
     own font size and breaks WCAG 2.1 SC 1.4.4 Resize Text.

     The trailing comments are the evaluated endpoints, checked at a 320px
     viewport (1vw = 3.2px) and a 1280px viewport (1vw = 12.8px) with a 16px
     root. All six land within 0.01px of the stated figures. */
  --tc-t-display: clamp(2.125rem, 1.667rem + 2.292vw, 3.5rem);    /* 34 -> 56px */
  /* CHANGED in Task 5, on measurement. The floor was 26px, which rendered a
     Newsreader x-height of 11.08px against a 9.28px Inter body at a 320px
     viewport — a ratio of 1.200, measured. A section heading only 20% taller in
     the letter-body than its own paragraphs does not read as a heading on a
     phone, which is where most of this site's traffic will be. Raising the floor
     to 30px puts the measured ratio at 1.377, inside the 1.25-1.45 band that
     render-check.mjs now enforces. The 1280px end is unchanged at 34px. */
  --tc-t-h2:      clamp(1.875rem, 1.792rem + 0.417vw, 2.125rem);  /* 30 -> 34px */
  --tc-t-h3:      clamp(1.25rem,  1.167rem + 0.417vw, 1.5rem);    /* 20 -> 24px */
  --tc-t-lede:    clamp(1.1875rem,1.125rem + 0.313vw, 1.375rem);  /* 19 -> 22px */
  /* ADDED in Task 5, and only because measurement left no alternative. The pull
     quote is set in Newsreader inside an Inter body, and Newsreader's letter-body
     is much smaller at the same nominal size, so at --tc-t-lede it MEASURED
     0.900x the body's x-height — the loudest break on the page reading quieter
     than the paragraphs around it. Nothing already in the scale lands in the
     1.25-1.45 band: --tc-t-h3 measures 1.05 and --tc-t-h2 measures 1.48. Hence a
     token of its own, per DESIGN-SYSTEM.md §3's rule about not inventing sizes
     unless the scale genuinely has no answer. Measured with it: 1.365 at 320px,
     1.380 at 768px, 1.396 at 1280px. */
  --tc-t-quote:   clamp(1.75rem,  1.667rem + 0.417vw, 2rem);      /* 28 -> 32px */
  --tc-t-body:    clamp(1.0625rem,1.021rem + 0.208vw, 1.1875rem); /* 17 -> 19px */
  --tc-t-data:    1rem;
  --tc-t-meta:    0.875rem;
  --tc-t-kicker:  0.75rem;

  --tc-lh-display: 1.08;
  --tc-lh-h2: 1.16;
  --tc-lh-h3: 1.28;
  --tc-lh-lede: 1.5;
  --tc-lh-body: 1.65;
  --tc-lh-data: 1.45;

  /* MEASURE — three tokens, one per job, each stating the family it assumes.
     CHANGED in Task 5, on measurement, and the reason is worth understanding
     because it is the least obvious thing in this file.

     `ch` is the advance of the digit "0" in the element's own font, and "0" is
     much wider than the mean character in running prose: measured over real
     prose, 1.3363x in Newsreader and 1.3143x in Inter. So a measure written in
     `ch` is about a third wider than its number suggests, AND it means a
     different width in each family — 66ch is 41.64em of Inter but 37.39em of
     Newsreader, an 11.4% difference from the unit alone. One shared token used
     on an Inter paragraph and a Newsreader pull quote was silently doing two
     different jobs.

     Measured before: --tc-measure: 66ch produced a median of 79 rendered
     characters per line at a 768px viewport, against the 45-75 comfortable
     range for continuous reading. Measured after: 68. render-check.mjs enforces
     the 75 ceiling on every run.

     Layout note: .wrap adds the gutter with calc() rather than letting
     border-box padding eat the measure, so these numbers are the width of the
     text itself, not of the padded box around it.

     WHY THESE ARE IN `em` AND NOT `ch`, WHICH IS THE OPPOSITE OF WHAT YOU WOULD
     EXPECT FOR A MEASURE. `ch` resolves against the font that is CURRENTLY
     loaded. With font-display:swap that is the fallback first and the webfont
     second, so a column sized in `ch` physically changes width when the webfont
     lands — measured at 1280px: 697.3px before the swap, 750.9px after. The text
     then rewraps into a different number of lines and the whole page jumps,
     which is most of the 293px shift documented above the @font-face block.

     size-adjust cannot rescue it. It was tried and measured. A single
     size-adjust factor can match the fallback's MEAN advance (which governs how
     prose wraps) or its "0" advance (which governs `ch`), but not both, because
     the two families have different zero-to-mean proportions: Inter 1.319,
     Segoe UI 1.205. Matching the mean at 106.93% left `ch` still 8.6% short.

     `em` depends only on font-size, never on which font is loaded, so the column
     holds still and the only thing the swap changes is the glyph widths inside a
     column of fixed width — which is exactly what the size-adjust fallbacks are
     good at. Each value below is its `ch` equivalent multiplied out against the
     "0" advance of the family named, so the intent is unchanged and only the
     dependency on font loading is gone. */
  --tc-measure:       32.81em; /* Inter body prose = 52ch (52 x 0.6309). Measured 68 chars/line. */

  /* CHANGED in Task 6, from 29.02em, and the old value was not merely wrong: it
     was INERT. It never once bound, and every check passed for the whole of
     Task 5.

     .standfirst sits inside .wrap. .wrap's content box is --tc-measure resolved
     at the BODY font-size; this token resolves at the LEDE font-size, which is
     larger. Measured at 1280px: 29.02em x 22px = 638.4px, asking for a column
     WIDER than the 623.0px its parent already allowed. The parent won at every
     viewport tested, so the standfirst was simply body-prose width.

     This is the ch -> em fix one level down. That change removed the
     font-FAMILY dependency and left the font-SIZE dependency standing. 46
     characters at 22px is physically wider than 52 characters at 19px, even
     though 46 is less than 52.

     TWO DIFFERENT NUMBERS, AND CONFUSING THEM IS WHAT HID THIS. The old comment
     said "Measured 60 chars/line". That figure was the clamped PARENT width
     (623.4 / (0.4800 x 22) = 59.0), which happened to land near the intended
     figure — a coincidence, and the reason nothing caught it.
       nominal   38ch = 38 x 0.6309 = 23.97em, taken as 24em. `ch` is the
                 advance of the digit "0", 0.6309em in Inter.
       rendered  ~50 characters per line: 24em / 0.4800em = 50.0, where 0.4800em
                 is the mean prose advance (0.6309 divided by the 1.3143
                 zero-to-mean ratio recorded above). This is the number a READER
                 sees. The nominal one is not.

     Binding is now asserted rather than assumed: render-check.mjs measurement 8
     fails at 1280 the moment this token stops being the constraint in force. */
  --tc-measure-lede:  24em;

  /* Newsreader pull quote = 32ch (32 x 0.5665), set at --tc-t-quote. Note the
     family differs here, which is exactly why this is its own token: the same
     `ch` count means a different width in Newsreader than in Inter, by 11.4%.

     THIS ONE DOES BIND, and here is the margin, measured at 1280px rather than
     assumed: 18.13em x 32px = 580.2px inside a 623.0px parent content box, which
     is 93.1% of it. It binds, but only by 42.8px. That is a real margin and a
     narrow one — a larger --tc-t-quote, or a wider --tc-measure, would close it
     and turn this token inert exactly the way the lede token was. Measurement 8
     prints this percentage on every run so the margin stays visible. */
  --tc-measure-quote: 18.13em;

  /* -- space — 8px base ---------------------------------------------------- */
  --tc-s-1: 0.25rem;  --tc-s-2: 0.5rem;  --tc-s-3: 0.75rem;  --tc-s-4: 1rem;
  --tc-s-5: 1.5rem;   --tc-s-6: 2rem;    --tc-s-7: 3rem;     --tc-s-8: 4rem;  --tc-s-9: 6rem;
  --tc-gutter: clamp(1.25rem, 0.333rem + 4.583vw, 4rem);  /* 20 -> 64px */

  /* -- page shell (Task 16) ------------------------------------------------ */
  /* The measure tokens above cap a COLUMN OF PROSE. Until Task 16 they were also
     the only cap the page had, so from a large tablet upward the whole site was
     one narrow column adrift in margin — the CEO's "doesn't look professional on
     a computer", measured: base.css contained no breakpoint above 640px.
     This token caps the PAGE: the masthead, the footer, the homepage grids and
     the archive lists fill it, while prose inside an article still stops at
     --tc-measure. 80rem = 1280px at a 16px root — wide enough for a hero plus a
     secondary column plus three list columns, and the width the laptop viewport
     in render-check already exercises, so the cap itself is measured on every
     run. In rem, not px, so it tracks the reader's own font size (SC 1.4.4). */
  --tc-page-max: 80rem;

  /* The sticky masthead's clearance. scroll-padding-top must clear the masthead
     or an anchor jump and a Tab scroll both hide their target under it —
     SC 2.4.11 Focus Not Obscured. 5rem = 80px at a 16px root, which clears the
     measured masthead height (~62px at 1280px) with margin at every viewport. */
  --tc-sticky-clear: 5rem;

  /* -- component dimensions — NOT space, and that distinction is the point --- */
  /* A column width is not a gap. Until Task 7 the table cells' minimum column
     width was `var(--tc-s-9)`, a SPACE token, and 6rem happened to be the right
     answer — §5 of DESIGN-SYSTEM.md reserves that same 96px for the gap between
     major page regions. It passed the letter of the no-raw-values law and it was
     a category error: the day somebody retunes the space scale, table columns
     move with it, silently, and nothing connects the two changes. Separated in
     Task 7 at the same 6rem, so nothing moved — the rendered column widths were
     measured identical before and after, which is the only acceptable outcome
     for a refactor.
     The two chart tokens below are the same lesson applied forward instead of
     backward: they are named for the chart rather than borrowed from a scale
     that means something else. The chart's ROW GAP is deliberately NOT here —
     a gap between rows is exactly what the space scale is for, so it uses
     --tc-s-3, and that is a correct use rather than a repeat of the mistake. */
  --tc-table-col-min: 6rem;    /* was var(--tc-s-9) until Task 7. Table cell min-width. */
  --tc-chart-bar-h:   1rem;    /* bar thickness. 16px: thick enough to read as a bar at 320px,
                                  thin enough that three rows do not dominate the column. */
  --tc-chart-name-col: 9rem;   /* the name column, once the row goes single-line at 30em.
                                  Measured 2026-08-06: the longest specimen name
                                  "Pellum Tessellate 4" sets at 127.58px at --tc-t-meta in
                                  Inter, so 144px carries it with 16.42px spare. A longer
                                  name WRAPS inside the column rather than overflowing it. */

  /* -- form ---------------------------------------------------------------- */
  --tc-r: 2px;
  --tc-r-lg: 4px;

  /* -- motion -------------------------------------------------------------- */
  --tc-t-fast: 120ms;
  --tc-t-base: 200ms;
  --tc-ease: cubic-bezier(.2, 0, 0, 1);

  /* -- advertising slots — PROVISIONAL heights ----------------------------- */
  /* These two reserve vertical space so the page does not jump when an ad
     arrives. They are guesses until a real unit renders, and they are marked as
     guesses on purpose. Re-measure at the AdSense step. DESIGN-SYSTEM.md §7. */
  --tc-slot-reserve-s: 280px;
  --tc-slot-reserve-m: 250px;

  /* -- what is deliberately absent ----------------------------------------- */
  /* No shadow tokens, no gradient tokens, no blur. Depth on this site is
     expressed with a rule and a background wash, the way a printed page
     expresses it. The omission is a decision, not an oversight.
     No dark-mode values either — deferred on purpose, see DESIGN-SYSTEM.md §9.
     Every token above is named for its role rather than its value precisely so
     that deferring it stays cheap. */
}

/* Motion is a courtesy, never a carrier of meaning, so it can go to zero with
   nothing lost. Both durations collapse; the easing curve is left alone because
   a curve with no duration has nothing to interpolate. */
@media (prefers-reduced-motion: reduce){
  :root{ --tc-t-fast: 0ms; --tc-t-base: 0ms; }
}
