/* Self-hosted fonts (2026-07-04, all needed subsets: latin, latin-ext, cyrillic, greek, vietnamese). No third-party call. OFL.
   Covers en/es/fr/pt/it (latin+ext), pl/vi (latin-ext/vietnamese), ru (cyrillic), el (greek).
   UPDATE 2026-09-07: Khmer, Lao, Bengali, Arabic, Chinese, Korean are now ALSO self-hosted (see the
   Noto Sans block appended near the end of this file) after every device without those language packs
   was found to fall back with no safety net. See that block's comment for sourcing, sizing, and
   verification.
   UPDATE 2026-09-07/08 (GATECLEAR): U+2190 (LEFTWARDS ARROW, the "← " prefix on the backHome string,
   used identically in all 16 shipped languages plus English) added to both weight-400 and weight-700
   `source-sans-pro-latin-*-normal.woff2` files (glyph copied from the unsubsetted Adobe Source Sans
   Pro Regular/Bold TTFs, every pre-existing glyph verified byte-for-byte unchanged, only the one
   codepoint added - NEVER_OVERWRITE_BACKUPS/gateclear_2026-09-07/ has the pre-edit files) AND added
   to this rule's own `unicode-range` (previously U+2191/U+2193 were explicitly listed but U+2190 was
   not, even though U+2190-21FF is the same Arrows block one codepoint later - so the browser would
   never have selected this face for U+2190 even after the file itself carried the glyph; both had to
   change together). Closes predeploy_gate.mjs check 39 (FONTPROOF)'s U+2190 finding for real, not by
   relying on a device's own fallback font. See GATECLEAR_2026-09-07.md. */
/* FONT-DISPLAY, 2026-09-02. CHANGED FROM `swap` TO `optional`, WITH EVIDENCE.
   Measured in a real browser through _HARNESS/_fonts_2026-09-02/fontshift.mjs, which settles the
   page completely with these @font-face rules withheld, arms a layout-shift observer, and only then
   lets the real fonts in, so every shift it records is the font's and nothing else's. At 320x568,
   home, 100% text, the swap alone moved layout by:
       en 0.1085   pl 0.0888   pt 0.0886   ru 0.0886   it 0.0754   ar 0.0303   es 0.0189
       ht 0.0189   vi 0.0087   zh 0.0078   ko 0.0079   el 0.0067   fr 0.0069   bn 0.0044
       km 0.0001   lo 0.0001
   English alone is over the 0.1 CLS ceiling on the font swap by itself. What moves: the subtitle
   "City of Woonsocket" re-measures, the search row lifts 18px, and every card below follows.
   `optional` ends it by construction - the browser decides before the first paint whether the real
   font is ready, and never swaps afterwards. sw.js already precaches all 21 woff2, so from the
   second load on they come out of Cache Storage and win the block period; the cost is that on a
   first-ever load on a slow phone the person reads the system font instead of Source Sans Pro,
   which is the correct trade for an audience on cheap old Android.
   The <link rel=preload> tags in index.html exist to win that block period on the first load too.
   Undo: replace `font-display:optional` with `font-display:swap` here and drop those four tags.
   Backup of the previous file: app_fonts.css.bak_2026-09-02_pre_fontjank */
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/source-sans-pro-latin-400-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2190,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/source-sans-pro-latin-ext-400-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0300-0301,U+0304,U+0308,U+0323,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/source-sans-pro-cyrillic-400-normal.woff2') format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/source-sans-pro-greek-400-normal.woff2') format('woff2');unicode-range:U+0370-03FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/source-sans-pro-vietnamese-400-normal.woff2') format('woff2');unicode-range:U+0300-0301,U+0323,U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/source-sans-pro-latin-600-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/source-sans-pro-latin-ext-600-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0300-0301,U+0304,U+0308,U+0323,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/source-sans-pro-cyrillic-600-normal.woff2') format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/source-sans-pro-greek-600-normal.woff2') format('woff2');unicode-range:U+0370-03FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/source-sans-pro-vietnamese-600-normal.woff2') format('woff2');unicode-range:U+0300-0301,U+0323,U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/source-sans-pro-latin-700-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2190,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/source-sans-pro-latin-ext-700-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0300-0301,U+0304,U+0308,U+0323,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/source-sans-pro-cyrillic-700-normal.woff2') format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/source-sans-pro-greek-700-normal.woff2') format('woff2');unicode-range:U+0370-03FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/source-sans-pro-vietnamese-700-normal.woff2') format('woff2');unicode-range:U+0300-0301,U+0323,U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/josefin-sans-latin-600-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/josefin-sans-latin-ext-600-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/josefin-sans-vietnamese-600-normal.woff2') format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/josefin-sans-latin-700-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/josefin-sans-latin-ext-700-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Josefin Sans';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/josefin-sans-vietnamese-700-normal.woff2') format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB;}

/* ADDED 2026-09-07 (TRACE_ARRIVALS audit). Khmer, Lao, Bengali, Arabic, Chinese, Korean previously had
   ZERO self-hosted font coverage - see the comment at the top of this file, lines 1-3, which documented
   that gap as accepted behavior. It should not have been: these six scripts serve Woonsocket's largest
   limited-English-proficiency groups (ACS C16001: "Other Asian & Pacific" bucket 1,588 speakers, mostly
   Lao/Khmer given 697 Laos-born residents per B05006; plus Arabic 32, Chinese 43 speakers at home) and on
   a Latin-only device font set (a plain Windows install with no CJK/Southeast-Asian language pack, common
   on older shared/library/shelter computers) these scripts render as tofu boxes with no self-hosted
   fallback to prevent it - only whatever the OS happens to ship.
   Source: Google Noto Sans (Khmer/Lao/Bengali/Arabic) and Noto Sans SC/KR, OFL-1.1 licensed, same license
   family as the fonts above. Subsetted host-side 2026-09-02 to the exact codepoints this app's own
   strings.<lang>.js files use (_HARNESS/_fonts_2026-09-02/subset_report.json: 0 missing codepoints for
   every language at build time), then re-verified 2026-09-07 against the CURRENT strings.<lang>.js files
   (also 0 missing). Converted TTF->WOFF2 host-side with fontTools; no glyphs hand-typed or invented.
   font-display:optional matches the policy above: first-ever load may still show the system font for
   these six languages (identical to today's behavior) but every load after the service worker caches
   these files renders the branded, guaranteed-complete glyph set instead of gambling on the device's
   own font set. Sizes (woff2, brotli-compressed internally): lo 6.2/6.4KB, km 12.6/13.7KB, ar 22/22.3KB,
   bn 32.7/33.1KB, ko 48.2/49KB, zh 318.7/323KB (400/700 weight). Backup of the prior file:
   app_fonts.css.bak_2026-09-07_addscripts. Also added to the sw.js FONTS precache list (same date) so
   these are available offline too; deliberately NOT added to the index.html <link rel=preload> block,
   since those fonts always download on every load regardless of language and preloading six
   rarely-selected scripts would tax the common case - see TRACE_ARRIVALS_2026-09-07.md for the reasoning.
   A deploy is required before any of this is live, and per house rule nothing ships without Ryan's yes. */
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-khmer-400.woff2') format('woff2');unicode-range:U+1780-17FF,U+19E0-19FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-khmer-700.woff2') format('woff2');unicode-range:U+1780-17FF,U+19E0-19FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-lao-400.woff2') format('woff2');unicode-range:U+0E80-0EFF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-lao-700.woff2') format('woff2');unicode-range:U+0E80-0EFF;}
/* FONTCSS 2026-09-27. U+0964 DEVANAGARI DANDA added to both Bengali ranges. It is the full stop in
 * Bengali prose and it is measured 2,923 times across 36 files at the wall root (events.js 1564,
 * strings.bn.js 310, events_home_subset 147, res_data_4 146, res_data_city 136, app.css 16,
 * index.html 5). It lives in the Devanagari block, outside U+0980-09FF, so for as long as the range
 * ended at 09FF the browser was FORBIDDEN from using a glyph both these files already carry and the
 * phone already downloads: every Bengali sentence ended in whatever the handset happened to have,
 * or in a box. Verified with fontTools against both files on disk, not from a report: U+0964 IS in
 * the cmap of noto-sans-bengali-400.woff2 and noto-sans-bengali-700.woff2 (333 glyphs each).
 * U+0965 DOUBLE DANDA is deliberately NOT admitted here. It was recommended as U+0964-0965, but
 * measured: it is ABSENT from both cmaps and occurs ZERO times anywhere in the app. Declaring it
 * would be a range writing a cheque the file cannot cash, the same defect as dm-sans-latin-ext
 * declaring U+1E00-1E9F without carrying s-dot. Declare what the font has. No .woff2 was added,
 * removed or re-subset; this is CSS only and costs zero new bytes.
 * STILL BROKEN AND NOT FIXABLE HERE: U+09DC, U+09DF, U+0990, U+098B and U+0983 are five ordinary
 * Bengali letters INSIDE the admitted range and absent from both subsets. That is content/subset
 * work, not CSS. See outputs\FONTCSS_2026-09-27.md. */
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-bengali-400.woff2') format('woff2');unicode-range:U+0964,U+0980-09FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-bengali-700.woff2') format('woff2');unicode-range:U+0964,U+0980-09FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-arabic-400.woff2') format('woff2');unicode-range:U+0600-06FF,U+0750-077F,U+FB50-FDFF,U+FE70-FEFF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-arabic-700.woff2') format('woff2');unicode-range:U+0600-06FF,U+0750-077F,U+FB50-FDFF,U+FE70-FEFF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-sc-400.woff2') format('woff2');unicode-range:U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FF00-FFEF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-sc-700.woff2') format('woff2');unicode-range:U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FF00-FFEF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-kr-400.woff2') format('woff2');unicode-range:U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7A3,U+D7B0-D7FF;}
@font-face{font-family:'Source Sans Pro';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-kr-700.woff2') format('woff2');unicode-range:U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7A3,U+D7B0-D7FF;}

/* ADDED 2026-09-07 (FONTPROOF session) - Traditional Chinese (zh-Hant). Simplified and Traditional
   Chinese share most of the same Unicode codepoints (Han unification), so unicode-range alone cannot
   pick between the SC face above and this one the way it does for Khmer/Lao/Bengali/Arabic/Korean -
   a DIFFERENT font-family name is required, applied via a CSS rule keyed on the resident's chosen
   language (staged, not yet wired - see FONTPROOF_2026-09-07.md for the exact app.css/setLang() diff).
   Font: Google Noto Sans TC, OFL-1.1, same license family as every other self-hosted font in this
   file. Subsetted with fontTools/pyftsubset to the exact 731 codepoints strings.zh-hant.js uses;
   verified 731/731 (100%) covered by reading the built woff2's own cmap back, 0 missing. Sizes (woff2):
   400 111.8KB, 700 114.3KB - a real Traditional reader gets a real Traditional glyph shape, not a
   Simplified-style glyph pretending to be Traditional. NOT added to sw.js's precache list yet for the
   same reason: staged only, not wired into any selectable UI path this session. */
@font-face{font-family:'Source Sans Pro TC';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/noto-sans-tc-400.woff2') format('woff2');}
@font-face{font-family:'Source Sans Pro TC';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/noto-sans-tc-700.woff2') format('woff2');}

/* WIRED 2026-09-07 (SHIPBUILD ship-build session). setLang() (app.js:118) sets document.documentElement.lang
   to the exact language code, and nothing else (no data-lang attribute, no body class exists) - confirmed by
   reading setLang() before landing this, not guessed. zh-Hant is NOT yet in app.js's visible language picker
   (see app.js ~line 54-58: zh, the Simplified sibling, is itself still HIDDEN pending native review per the
   I18N_GAP_MATRIX gate, and zh-Hant has even less review behind it) - so this rule is currently INERT, staged
   for the day a future session clears that gate and adds zh-Hant to the picker, same discipline as the
   font-face rules above already being staged-but-unreachable for bn/ar/zh. */
html[lang="zh-Hant" i] body { font-family:'Source Sans Pro TC','Source Sans Pro',sans-serif; }

/* ADDED 2026-09-18 (BRANDFONTS, agent A2 of REBUILD_PLAN_2026-09-18). `[RYAN 2026-09-18]` "use the fonts from
   the branding kit". The three Beloved Woonsocket brand faces, self-hosted from npm @fontsource v5.3.0, OFL-1.1,
   fontsource filenames kept: Cormorant Garamond (display), DM Sans (body), Barlow Condensed (labels). Latin
   subsets only (DM Sans also latin-ext, for Polish). The 15 files were byte-verified (wOF2 magic, declared
   length, SHA-256 against npm) before this block was written; see LFG4\NEVER_OVERWRITE\BRANDFONTS_2026-09-18.md
   sections 2 and 4 and A2_MARKUP_2026-09-18.md.
   THE UNICODE-RANGE ON EVERY RULE BELOW IS THE SAFETY NET, NOT DECORATION. Every rule carries fontsource's own
   latin (or latin-ext) range, so any character outside it (Khmer, Lao, Bengali, Arabic, Han, Hangul, Greek,
   Cyrillic, Vietnamese tone letters, the U+2190 arrow) skips these faces and falls through the stack to
   'Source Sans Pro', which is where the 2026-09-07 Noto faces above claim their scripts. A brand rule without
   a range would claim those codepoints and paint tofu for the six non-Latin languages. The stacks that read
   these faces live ONLY in app.css :root (--font-serif, --font-sans, --font-label) and each lists
   'Source Sans Pro' after the brand face for that reason. font-display:optional is the 2026-09-02 CLS ruling
   at the top of this file, unchanged. The three faces that paint above the fold are preloaded in index.html. */
/* CORMORANT GARAMOND, the DISPLAY face. FONTWIRE 2026-09-25.
   166 elements asked for Cormorant Garamond 500 NORMAL and painted Source Sans Pro: that is the
   big serif heading at the top of every view, on every tab, above the fold. Only the 500 ITALIC
   file was preloaded (it carries the wordmark's "Beloved"), so the upright never won the block
   period, even though it was downloaded on every load anyway - it just started at 11,157ms, after
   the 11,216ms first paint. index.html now preloads the 500 upright too. The two 400 files were
   declared but never requested by any rule; the 400-italic requests that DO exist (5 elements,
   .hp-why and friends) are now served by the 500 italic through the widened range, so they paint
   a real Cormorant instead of a grotesque. Both 500 rules now declare 400-700: everything this
   app asks of its display serif lands on a face that is genuinely on the screen.
   Undo: uncomment the two 400 rules and put `font-weight:400`/`500` back on the four rules. */
/* RETIRED 2026-09-25 (FONTWIRE), covered by the 400-700 ranges below:
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:400;font-display:optional;src:url('app_fonts/cormorant-garamond-latin-400-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Cormorant Garamond';font-style:italic;font-weight:400;font-display:optional;src:url('app_fonts/cormorant-garamond-latin-400-italic.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
*/
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:400 700;font-display:optional;src:url('app_fonts/cormorant-garamond-latin-500-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'Cormorant Garamond';font-style:italic;font-weight:400 700;font-display:optional;src:url('app_fonts/cormorant-garamond-latin-500-italic.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
/* DM SANS, the BODY face. FONTWIRE 2026-09-25.
   Only the latin 400 file was preloaded, so 400 painted and every heavier body weight fell
   through to Source Sans Pro: 108 elements at 500, 232 at 600, 670 at 700 and 195 at 800, 1,205
   in all, set in a face the brand does not use. The 500 and 600 files were downloaded on every
   load and painted nothing (28,448 bytes of pure waste); the 700 file was downloaded late and
   also painted nothing. index.html now preloads the latin 700, and the ranges below carry the
   rest with no further request:
       400 file -> 400 500   (500 was already painting at Regular weight through the fallback,
                              so this changes the family and NOT the apparent weight)
       700 file -> 600 800   (600 moves from Source Sans Pro SemiBold to DM Sans Bold; 800 is a
                              weight DM Sans does not ship and was being synthesised off the
                              wrong family, and now resolves to the real Bold)
   latin-ext follows latin exactly, so Polish keeps one face per word rather than swapping family
   mid-word on its diacritics. The latin-ext files are NOT preloaded: they are dead weight for the
   fifteen languages that do not need them, and the audience is on cheap phones.
   Undo: uncomment the four retired rules and restore the plain weights on the other four. */
/* RETIRED 2026-09-25 (FONTWIRE), covered by the widened ranges below:
@font-face{font-family:'DM Sans';font-style:normal;font-weight:500;font-display:optional;src:url('app_fonts/dm-sans-latin-500-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:500;font-display:optional;src:url('app_fonts/dm-sans-latin-ext-500-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/dm-sans-latin-600-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:600;font-display:optional;src:url('app_fonts/dm-sans-latin-ext-600-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
*/
@font-face{font-family:'DM Sans';font-style:normal;font-weight:400 500;font-display:optional;src:url('app_fonts/dm-sans-latin-400-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:400 500;font-display:optional;src:url('app_fonts/dm-sans-latin-ext-400-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:600 800;font-display:optional;src:url('app_fonts/dm-sans-latin-700-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'DM Sans';font-style:normal;font-weight:600 800;font-display:optional;src:url('app_fonts/dm-sans-latin-ext-700-normal.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}

/* ============================================================================================
   FONTWIRE 2026-09-25. THE DECLARED FACE AND THE PAINTED FACE WERE NOT THE SAME FACE.
   Measured on the real shipped page, real headless Chromium over real HTTP, every visible
   text-bearing element on all five tabs, requested face from getComputedStyle and painted face
   from CDP `CSS.getPlatformFontsForNode`. English, 390 wide, first load. BEFORE:

     REQUESTED                        ELEMENTS   ACTUALLY PAINTED
     Barlow Condensed 600 normal          11     Barlow Condensed SemiBold        correct
     Barlow Condensed 700 normal         189     Source Sans Pro                  WRONG
     Cormorant Garamond 500 italic        24     Cormorant Garamond Medium Italic correct
     Cormorant Garamond 500 normal       166     Source Sans Pro                  WRONG
     Cormorant Garamond 400 italic         5     Source Sans Pro                  WRONG
     DM Sans 400 normal                  277     DM Sans                          correct
     DM Sans 500 normal                  108     Source Sans Pro                  WRONG
     DM Sans 600 normal                  232     Source Sans Pro SemiBold         WRONG
     DM Sans 700 normal                  670     Source Sans Pro                  WRONG
     DM Sans 800 normal                  195     Source Sans Pro                  WRONG

   1,565 of 1,877 painted elements were in a face nobody asked for. Not a near miss: Source Sans
   Pro Bold is 32 per cent wider than Barlow Condensed at the same size (brand.css:1250-1256,
   measured), and Source Sans Pro standing in for Cormorant Garamond replaces a display serif
   with a grotesque on every heading in the app. This is what `[RYAN 2026-09-24]` "looks like
   shit not elegant" was looking at.

   THE CAUSE, PROVED ON THE WIRE AND NOT REASONED. Every face in this file is
   `font-display:optional` (the 2026-09-02 CLS ruling above, which stands and is not touched
   here). Under `optional` the browser decides before the first paint whether the real face is
   ready and NEVER swaps afterwards. index.html preloaded six files. On a throttled first load
   (1.6 Mbps, 4x CPU) the six preloads left the wire at 915-931ms; the faces that were declared
   but NOT preloaded did not start until 11,156ms and first contentful paint was 11,216ms. They
   arrived 60ms too late, forever, on every load. They still cost their bytes. Three of them
   (barlow-condensed-700, dm-sans-500, dm-sans-600) were downloaded on every single load, 50,892
   bytes, and painted not one glyph in the life of the page.

   THE FIX, AND WHY IT IS ALMOST FREE. A `font-weight` descriptor on `@font-face` is a RANGE, not
   a number. Widening the range on the file that is ALREADY preloaded and ALREADY painting makes
   it the exact match for the neighbouring weights too, so those rules paint the real brand face
   with NO new request and NO new byte. That is done below for Barlow Condensed, and it is the
   whole of the Barlow fix: zero preloads added, 22,444 bytes removed from every load.
   Two faces could not be fixed that way because they are genuinely needed and genuinely absent
   from the first paint, and both were already being downloaded late and wasted, so preloading
   them moves bytes earlier rather than adding them:
       cormorant-garamond-latin-500-normal.woff2   every view heading, above the fold on every tab
       dm-sans-latin-700-normal.woff2              670 elements, the body face in bold
   Cost of those two preloads on the critical path: 37,616 bytes, about 188ms of bandwidth at
   1.6 Mbps, against a page that already ships 10.2 MB. Net wire change for the whole pass is
   NEGATIVE: 50,892 bytes removed, 37,616 bytes moved earlier.

   RETIRED, NOT DELETED. The @font-face rules below that are commented out are the ones whose
   file can never win the block period and whose weight is now covered by a widened neighbour.
   NOT ONE .woff2 FILE IS ADDED, REMOVED, RENAMED OR RE-SUBSET (L5); every retired file is still
   on disk in app_fonts/ and still in the sw.js precache list, byte-identical.
   UNDO, in one move: restore NEVER_OVERWRITE_BACKUPS/fontwire_2026-09-25/app_fonts.css.PRE_FONTWIRE
   and index.html.PRE_FONTWIRE.
   ============================================================================================ */
/* BARLOW CONDENSED, the LABEL face. FONTWIRE 2026-09-25.
   The 600 file is the one index.html preloads and the only Barlow weight that has ever painted.
   Its descriptor now declares the range 500-700, so `font-weight:500`, `600` and `700` all match
   THIS face exactly (an exact range match, so no synthetic bold is applied either) and every one
   of the 189 elements that asked for Barlow Condensed 700 and got Source Sans Pro now paints
   Barlow Condensed SemiBold. The masthead is untouched by construction: brand.css asks 600, and
   600 resolves to the same file it already resolved to. brand.css:1266 wrote this handback in
   full - "preloading the 500 and 700 Barlow files would give the masthead a weight axis it does
   not have today" - and this is the cheaper half of that answer: one axis, the real face, no new
   bytes. The guide's own running head is Barlow Condensed SemiBold (brand.css:1296).
   RETIRED below: the 500 file was never requested by any rule in the app, and the 700 file was
   requested on every load, downloaded, and discarded unpainted. Both stay on disk and in sw.js.
   Undo: uncomment the two rules and restore `font-weight:600` on the rule between them. */
/* RETIRED 2026-09-25 (FONTWIRE), covered by the 500-700 range below:
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:500;font-display:optional;src:url('app_fonts/barlow-condensed-latin-500-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
*/
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:500 700;font-display:optional;src:url('app_fonts/barlow-condensed-latin-600-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
/* RETIRED 2026-09-25 (FONTWIRE), covered by the 500-700 range above:
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:700;font-display:optional;src:url('app_fonts/barlow-condensed-latin-700-normal.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
*/

/* ADDED 2026-09-18 (BRANDFONTS, agent A2). DM Sans has no Vietnamese subset at all (fontsource ships only latin
   and latin-ext for it; latin-ext covers 6 of the 53 Vietnamese letters the app's strings use). Without this
   rule a Vietnamese body line would paint base letters in DM Sans and the 53 tone-marked letters in Source Sans
   Pro inside nearly every word: never tofu, but two sans faces in one word, on the body face. Keep the body on
   Source Sans Pro for vi. Same pattern as the zh-Hant rule directly above; setLang() sets html[lang] before
   first paint. Cormorant and Barlow are not affected (their Vietnamese subsets were not copied, so Vietnamese
   headings and labels fall to Source Sans Pro whole, one face per word). */
html[lang="vi" i] body { font-family:'Source Sans Pro',system-ui,sans-serif; }

/* ============================================================================================
   YOFONT 2026-09-27. YORUBA TONE MARKS. THREE AGENTS MEASURED THIS AND GOT THREE ANSWERS.
   THE RANGES ARE RIGHT. THE CACHE STATE IS WHAT THEY WERE DISAGREEING ABOUT.

   FONTCSS's fix IS on this file and always was. Live sha256 on 2026-09-27 was
   605236554adf75e8a304d8ddca7a2901590e6bda90d739680ee64d24c3f2fe1c, byte for byte the value
   FONTCSS recorded when it wrote. The audit's claim that "no Yoruba range exists in the file" is
   false: `U+0300-0301` and `U+0323` are on all six Source Sans Pro latin-ext and vietnamese faces,
   at 400, 600 and 700. fontTools 4.65.0 confirms every one of those nine codepoints is really in
   the cmap of every face that declares it. No uncashable cheque was added.

   WHY THE THREE RESULTS DIFFERED: `font-display:optional`. Every face in this file carries it.
   On a COLD first load Chrome will not swap an optional face in, so the whole Yoruba cluster falls
   to the system font. On a WARM load it paints from the web font. Measured on the real shipped
   page, real headless Chromium over real HTTP, `CSS.getPlatformFontsForNode`, with a known-good
   control (`abc`) and a known-bad control (`U+AF43`) in every single run, all controls valid:

     probe, 4 stacks x 4 weights      COLD               WARM RELOAD
     e-dotbelow + acute               DejaVu Sans        Source Sans Pro
     o-dotbelow + grave               DejaVu Sans        Source Sans Pro
     s-dotbelow                       DejaVu Sans        Source Sans Pro
     control abc                      web font           web font        (valid both)
     control U+AF43                   UnDotum            UnDotum         (valid both)

     the app's own 15 real Yoruba text nodes, 390 wide, after setLang('yo'):
     COLD  45 glyphs DejaVu Sans + 22 Liberation Sans, zero from Source Sans Pro
     WARM  zero from any system font, 59 Source Sans Pro + 35 Source Sans Pro SemiBold

   So LANGSCOUT was right when it looked, before the ranges existed. FONTCSS was right that its
   edit works, and it landed. YOSTRINGS was right, on a warm cache. The audit measured a cold load
   correctly and then blamed a missing range that was in fact present. Nobody was lying. NOBODY
   SAID WHICH CACHE STATE THEY WERE IN. Say it, every time, or this argument happens again.

   POSITIONING IS REAL, NOT JUST PRESENCE. Canvas ink centroid, mark ink taken strictly above the
   bare base's ink top, against a precomposed anchored control. At 400 the real e-dotbelow + acute
   sits 0.70 px from precomposed e-acute; at 700, 2.98 px at a 100 px em. A planted 8 px
   misplacement was recovered as 8.00 px at both weights, so the metric resolves the error it is
   there to catch. `ccmp` decomposes the dotted vowel, `mark` anchors the tone, `mkmk` stacks the
   dot. No tone mark lands on the wrong vowel.

   WHAT CSS CANNOT FIX HERE, AND NO .woff2 MAY BE ADDED, REMOVED OR RE-SUBSET TO FIX IT:
   - DM Sans has NO face carrying `U+0323`, and no face carrying e-dotbelow, o-dotbelow or
     s-dotbelow. Declaring those on DM Sans would be exactly the cheque this file cannot cash that
     `dm-sans-latin-ext`'s `U+1E00-1E9F` already is (it declares that range and its cmap has no
     s-dotbelow). ONE such defect is enough. Nothing was added to DM Sans.
   - Cormorant Garamond has no latin-ext and no vietnamese subset on disk at all, so it cannot draw
     a dotted vowel in any weight.
   Both fall through whole to Source Sans Pro, which is already next in all three stacks, so there
   is no tofu. The cost is that a Yoruba word mixes two faces: the precomposed Latin-1 vowels
   (a-grave, o-acute and the rest) paint in DM Sans or Barlow Condensed while the dotted vowels
   paint in Source Sans Pro. Measured: a real h2 came back 4 glyphs Barlow Condensed SemiBold and
   4 glyphs Source Sans Pro. This is the same defect the `html[lang="vi"]` rule at the bottom of
   this file was written to stop, and Yoruba has it worse than Vietnamese, because Vietnamese tone
   vowels sit in U+1EA0-1EF9 where the brand faces have nothing, while Yoruba's sit in Latin-1
   where they do. The one-line rule that would fix it is left for Ryan, because it changes the body
   face of a whole language and his eye decides that, not this file.

   TWO THINGS HANDED OVER, NEITHER OF THEM IN THIS FILE:
   1. `font-display:optional` means a Yoruba resident's FIRST EVER load shows system glyphs for
      every tone mark, and on a phone with no Yoruba-capable system font that is a box, not a
      letter. A changed mark is a changed word. Fixing it means either preloading the Source Sans
      Pro latin-ext and vietnamese subsets in index.html, or moving those faces off `optional`,
      which would also change first paint for Vietnamese and Polish. Not this file's call.
   2. The `Ọ̀fẹ́` chip. It is NOT a 320 px effect. Every quantity is identical at 320 and 390:
      font-size 12px, computed font-weight 800, line-height 18.6px from body's 1.55, padding 4px,
      clientHeight 27, content height 19, `overflow-y:hidden`. The app's real `text-xlarge` mode
      does not tighten it either. The ink of that string measures 15 px, 12 above the baseline and
      2 below, so about 2 px of headroom, and a stacked tone mark on a dotted capital is the
      tallest thing the app puts in that box. `overflow:hidden` comes from
      `.event-row .ev-tags .chip` in `app.css` and the 800 weight from `.chip` there. Owned by
      app.css. Not clipped in the harness, but it is 2 px from being clipped.

   NOTHING IN THIS BLOCK CHANGES A SINGLE DECLARATION. It is a comment. It exists because three
   agents burned a day disagreeing for want of it.
   ============================================================================================ */
