/* marketing-site/report/print.css
 *
 * Paper geometry, the letterhead frame, the shared block styles, and the
 * PREVIEW watermark for the web client report. Port of the layout
 * `ReportFrameMetrics`/`ReportPageFrame` describe in SwiftUI
 * (AsylumTracker/Views/ClientReport/ReportPages.swift) -- see
 * docs/superpowers/reports/2026-09-04-web-report/pages.md §1 for the spec.
 *
 * Two independent geometry systems live side by side here on purpose:
 *   - the --pw/--ph/--margin custom properties size the on-screen `.page`
 *     element so the in-browser builder preview LOOKS like the target
 *     paper;
 *   - the `@page` at-rules (below) tell Chromium what page box to put in
 *     the actual PDF when printed with `preferCSSPageSize: true`.
 * A single report is one paper size end to end (config.appearance.paper),
 * so per-page-box switching is done with CSS Paged Media's NAMED PAGES (the
 * `page` property + `@page <name>`), the one mechanism Chromium's
 * print-to-PDF actually honours for "this document uses page box X, not the
 * UA default" -- an `@page` rule cannot itself be scoped by an attribute
 * selector (`@page` is not tied to the DOM), so `html[data-paper="a4"]`
 * assigns every `.page` to the named `a4` page box instead of trying to
 * make `@page`'s `size` conditional.
 */

:root {
  --ink: #111827;
  --muted: #6b7280;
  --rule: #e5e7eb;
  --soft: #f8fafc;
  --brand: #0f4c5c;

  --pw: 612pt;
  --ph: 792pt;
  --margin: 40pt;
}

html[data-paper="a4"] {
  --pw: 595.28pt;
  --ph: 841.89pt;
  --margin: 40pt;
}

/* Print page box (the actual PDF output). `size: letter`/`size: A4` are the
 * CSS Paged Media page-size keywords -- they resolve to exactly the
 * chapters/frame.js GEOMETRY numbers (612x792pt / 595.28x841.89pt). */
@page {
  size: letter;
  margin: 0;
}
@page a4 {
  size: A4;
  margin: 0;
}
html[data-paper="a4"] .page {
  page: a4;
}

/* ---------------------------------------------------------------------
 * The page frame itself (pages.md §1.2)
 * --------------------------------------------------------------------- */

.page {
  width: var(--pw);
  height: var(--ph);
  box-sizing: border-box;
  padding: var(--margin);
  position: relative;
  overflow: hidden; /* safety net only -- chapters/frame.js#budget is what
                        actually refuses to build an over-budget page */
  background: #fff;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  page-break-after: always;
  break-after: page;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.page:last-child {
  page-break-after: auto;
  break-after: auto;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8pt;
  flex: 0 0 auto;
  padding-bottom: 8pt; /* ruleTopPadding */
  margin-bottom: 14pt; /* titleTopPadding */
  border-bottom: 1pt solid var(--brand, #0f4c5c);
}
.header-left {
  display: flex;
  align-items: center;
  gap: 8pt;
  min-width: 0;
}
.header-left .logo {
  height: 26pt;
  width: auto;
  display: block;
}
.header-left .firm-name {
  font: 700 12pt/1.2 inherit;
  color: var(--brand, #0f4c5c);
  white-space: nowrap;
}
.header-right {
  text-align: right;
  flex: 0 0 auto;
}
.header-right .prepared-for {
  color: var(--muted);
  font-size: 9pt;
}
.header-right .case-number {
  color: var(--muted);
}

.page-title {
  flex: 0 0 auto;
  margin: 0 0 10pt; /* contentTopPadding, gap to the content below */
  display: flex; align-items: baseline; justify-content: space-between; gap: 12pt;
}
.page-title-text { flex: 1 1 auto; min-width: 0; }
/* Scope tag (judge · court · nationwide) on the title line -- no body budget. */
.page-scope { flex: 0 1 auto; max-width: 48%; font-weight: 400; color: #6B7280; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.page[dir="rtl"] .page-title { flex-direction: row-reverse; }

.page-body {
  flex: 1 1 auto;
  min-height: 0;
}

.page-footer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8pt;
  margin-top: 8pt; /* footerRuleTopPadding */
  padding-top: 4pt; /* footerTopPadding */
  border-top: 0.5pt solid var(--rule);
}
.footer-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.footer-page {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Preview blur (frame.js#previewBlurred) -- an unpaid render keeps every
 * chart's SHAPE and every page's structure but blurs the numbers: stat
 * values, table cells (the label column stays), case-list rows, reading
 * sentences, body lines (summary comparators, AI narrative), wait
 * headlines, rate pills, and every numeric label inside a chart
 * (`.blur-num`, stamped by frame.js#blurNumericSvgText). Titles, legends,
 * captions, how-to-read lines and notes stay legible. `user-select: none`
 * keeps the blurred text out of the clipboard; `pointer-events: none`
 * keeps it out of the find-in-page highlight. Print and PNG export of an
 * unpaid render carry the same blur -- the paid render is a fresh render
 * without the class, not a class toggle. */
.page.preview-blur .stat-value,
.page.preview-blur .t-stat-value,
.page.preview-blur .row-value,
.page.preview-blur .report-table td:not(:first-child),
.page.preview-blur .record-row,
.page.preview-blur .reading,
.page.preview-blur .body-line,
.page.preview-blur .wait-headline,
.page.preview-blur .vs-national-pill,
.page.preview-blur .attorney-rate-pct,
.page.preview-blur .attorney-rate-decided,
.page.preview-blur .attorney-multiple-pill,
.page.preview-blur .share-bar-pct,
.page.preview-blur .share-bar-of {
  filter: blur(4.5px);
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}
.page.preview-blur .blur-num { filter: blur(2.5px); }
.page.preview-blur .stat-value,
.page.preview-blur .t-stat-value { filter: blur(8px); }
/* The chart itself -- bars, lines, areas, tracks and their axes -- blurs as
 * one unit (`plotGroup()` in charts/kit.js wraps it in <g class="plot">);
 * the legend, caption and row labels around it stay legible, so the reader
 * sees WHAT each chart is but not how much. HTML-drawn fills (share bars,
 * the attorney rate track) get the same treatment. iOS twin:
 * `.reportValue(.plot)` on the Chart view. 2026-09-13: numbers alone were
 * not enough -- bar heights and fill widths read as values. */
.page.preview-blur svg .plot { filter: blur(7px); }
.page.preview-blur .share-bar-track,
.page.preview-blur .attorney-rate-track { filter: blur(5px); }

/* PREVIEW watermark (pages.md §1.6) -- rendered whenever the page carries a
 * data-watermark value; render.js sets it to strings.t('cr_preview_watermark')
 * on every page it builds. NOT part of the exported/paid PDF: a later
 * export task omits the attribute (or this stylesheet) for that pass, the
 * same way the Swift app only applies its watermark in the in-app preview
 * overlay, never in ClientReportRenderer.writePDF. */
.page::after {
  content: attr(data-watermark);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-28deg);
  font-size: 64pt;
  font-weight: 700;
  color: rgba(0, 0, 0, 0.07);
  white-space: nowrap;
  pointer-events: none;
  z-index: 1000;
}
.page:not([data-watermark])::after,
.page[data-watermark=""]::after {
  content: none;
}

/* Task 16 addition (chapters/frame.js, spec §6 option 15
 * `appearance.watermark`): a SECOND watermark line, under the PREVIEW one --
 * same diagonal style as `.page::after` above, offset lower (68% instead of
 * 50% down the page) so CONFIDENTIAL/DRAFT and PREVIEW are both legible
 * rather than fully overlapping, and a touch smaller so the pair reads as
 * two stacked stamps rather than one illegible smear. Only present when
 * render.js sets `data-watermark2` (`appearance.watermark` is non-null);
 * absent (the common case) it renders nothing, same on/off convention as
 * `.page::after`. */
.page::before {
  content: attr(data-watermark2);
  position: absolute;
  top: 68%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-28deg);
  font-size: 40pt;
  font-weight: 700;
  color: rgba(0, 0, 0, 0.07);
  white-space: nowrap;
  pointer-events: none;
  z-index: 999;
}
.page:not([data-watermark2])::before,
.page[data-watermark2=""]::before {
  content: none;
}

/* Monochrome print (final-review fix wave item 3). Three layers, each
 * covering what the OTHERS can't:
 *   1. kit.js's STYLE.monochrome() already greys chart series colours at
 *      the DATA level before drawing (so an SVG's fill/stroke ATTRIBUTES
 *      are real greys, not colour hidden behind a filter).
 *   2. `--ink`/`--muted`/`--brand`/`--rule`/`--soft` are overridden here to
 *      luminance-matched greys (same `greyForLuminance` transform kit.js
 *      uses, applied to the SAME source hex each token already was) --
 *      every element that reads its colour through a CSS class
 *      (`.t-body`, `.t-note`, `.stat-label`, `.chart-title`, etc., all of
 *      which reference `var(--ink)`/`var(--muted)`/`var(--brand)`) picks
 *      this up through the ordinary cascade, with NO markup change needed
 *      -- `html[data-mono]`'s specificity (0,1,1) already beats `:root`'s
 *      (0,1,0) for the same custom properties, no `!important` needed.
 *   3. `filter: grayscale(1)` on `.page` is the remaining belt-and-
 *      suspenders backstop for anything NEITHER of the above reaches --
 *      an embedded raster firm logo, primarily -- since it operates at
 *      PAINT time regardless of what a computed style/custom property
 *      says. It is NOT load-bearing for text/tile colour any more: an
 *      inline `style="color:..."` (statBox's own `color` prop, rateColor()'s
 *      dynamic red/amber/green, the handful of hardcoded tile colours) is
 *      stripped by chapters/frame.js#pageFrame's own stripInlineColorsForMono
 *      BEFORE this rule ever needs to hide it, precisely because `filter`
 *      changes what is PAINTED but never what `getComputedStyle` reports --
 *      a computed-style-based check (this fix wave's own smoke test) would
 *      otherwise still see the original, non-grey colour.
 */
html[data-mono="1"] {
  --ink: #181818; /* greyForLuminance(luminance('#111827')) */
  --muted: #727272; /* greyForLuminance(luminance('#6B7280')) */
  --rule: #e7e7e7; /* greyForLuminance(luminance('#E5E7EB')) */
  --soft: #fafafa; /* greyForLuminance(luminance('#F8FAFC')) */
  --brand: #464646; /* greyForLuminance(luminance('#0F4C5C')) -- fallback only: chapters/frame.js#pageFrame already sets `--brand` per-page from ctx.style.brand, itself already grey when monochrome (STYLE.monochrome()) */
}
html[data-mono="1"] .page {
  filter: grayscale(1);
}

/* RTL (pages.md: layoutDirection .rightToLeft when strings.isRTL). */
.page[dir="rtl"] {
  direction: rtl;
  text-align: right;
}
.page[dir="rtl"] .page-header {
  flex-direction: row-reverse;
}
.page[dir="rtl"] .header-right {
  text-align: left;
}
.page[dir="rtl"] .page-footer {
  flex-direction: row-reverse;
}
.page[dir="rtl"] .row,
.page[dir="rtl"] .t-td,
.page[dir="rtl"] .t-th {
  flex-direction: row-reverse;
}

/* ---------------------------------------------------------------------
 * Fonts (pages.md §1.7)
 * --------------------------------------------------------------------- */

/* Task 20 -- self-hosted script fallback fonts, layered UNDER `.page`'s own
 * system stack (line ~76) via `.page[lang="..."]` overrides, applied ONLY
 * to the three report languages whose script a bare/minimal Chromium can
 * plausibly lack entirely: zh-Hans, hi, ar. Pages.md §1.7 documents the
 * iOS app using ONLY `.system(size:weight:)` because iOS's system font
 * (San Francisco) already ships full CJK/Devanagari/Arabic coverage --
 * this web port's print pipeline (Chromium's `page.pdf()`, the same engine
 * render.spec.mjs/measure-blocks.mjs already drive, and wherever a future
 * server-side export eventually runs) cannot assume that: a bare/minimal
 * Linux Chromium install commonly has NO system CJK/Devanagari/Arabic font
 * at all, so those three report languages would render as tofu boxes
 * end to end with no code-level fallback available -- a gap a developer's
 * own Mac (which happens to have all three already installed) would never
 * surface in a manual check.
 *
 * Source TTFs, exact versions and the `pyftsubset` command for all six
 * files live in fonts/README.md. Every `@font-face`'s `unicode-range`
 * below is the PRECISE codepoint set that `@font-face`'s file actually
 * contains (fix round 1, review finding: this claim used to be true only
 * for zh-Hans -- hi/ar declared the full `U+0020-007E` ASCII range even
 * though upstream's Noto Sans Devanagari/Arabic builds never shipped a
 * complementary Latin alphabet at all, only a handful of shared digits/
 * punctuation codepoints; verified against the SOURCE TTFs directly, not
 * just the subset output, so this isn't a pyftsubset artifact -- the
 * glyphs for 'A'-'Z'/'a'-'z' simply do not exist upstream to subset in the
 * first place. Every hi/ar range below is now the exact cmap of the
 * shipped file, computed with fontTools). The zh-Hans range is generated
 * from fonts/sc-chars.txt (fonts/README.md documents the exact Node
 * script): every character in strings/zh-Hans.json + ui-strings/
 * zh-Hans.json, UNION every character `Intl.DisplayNames(['zh-Hans'],
 * {type:'region'})` produces for every ICU-recognized ISO 3166-1 alpha-2
 * code (closes a real, severe bug found in review: 8 of 10 spot-checked
 * EOIR nationality names -- Venezuela, Honduras, Guatemala, El Salvador,
 * Nicaragua, Ecuador, Colombia, Ethiopia, Cameroon -- were missing at
 * least one glyph in the original catalog-only subset, since a
 * nationality's zh-Hans name is ICU-generated at render time, never
 * itself a catalog string), UNION CJK Symbols/Punctuation, fullwidth
 * forms, and ASCII. `marketing-site/tests/test_web_regressions.py`'s
 * font-coverage test asserts every EOIR_TO_ISO-listed nationality's
 * zh-Hans name is fully covered in both the Regular and Bold cmaps.
 *
 * `unicode-range` matters this precisely because it makes the browser
 * commit to THIS face for any codepoint inside it: a range wider than the
 * file's real coverage prints a missing-glyph box for the gap instead of
 * falling through to the next family in the stack -- exactly the bug
 * above, and exactly why hi/ar's ranges are now the files' literal cmap
 * rather than the scripts' full Unicode blocks. Any codepoint genuinely
 * outside these ranges (ASCII in hi/ar, covered by the fallback stack
 * instead) falls straight through to the SAME system stack `.page`
 * already declares.
 *
 * `font-display: block` (not the usual web `swap`): this pipeline renders
 * once and captures a PDF/PNG/screenshot immediately after -- a brief
 * invisible-text period while a same-origin woff2 loads is preferable to
 * guaranteeing a one-time flash of the WRONG script's fallback glyphs baked
 * permanently into the exported file. */
@font-face {
  font-family: "AT Report Noto Sans SC";
  src: url("/report/fonts/noto-sans-sc-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
  unicode-range:
    U+0020-007E, U+00B7, U+00D7, U+00F7, U+2013-2014, U+201C-201D, U+2026, U+2192, U+25B2,
    U+25BC, U+3000-303F, U+4E00-4E01, U+4E07, U+4E0A-4E0B, U+4E0D-4E0E, U+4E18, U+4E1C, U+4E24,
    U+4E2A, U+4E2D, U+4E34, U+4E39-4E3B, U+4E3E, U+4E45, U+4E48-4E49, U+4E4B-4E4D, U+4E54,
    U+4E5F, U+4E66, U+4E70, U+4E86, U+4E88, U+4E8B-4E8C, U+4E8E, U+4E9A-4E9B, U+4EA4,
    U+4EAB-4EAC, U+4EBA, U+4EC0, U+4EC5, U+4ECD-4ECE, U+4ED6, U+4EE3-4EE5, U+4EF6, U+4EFB,
    U+4EFD, U+4F0A, U+4F10-4F11, U+4F1A, U+4F26, U+4F2A, U+4F2F-4F30, U+4F3C, U+4F4D-4F4E,
    U+4F53, U+4F55, U+4F59, U+4F5B-4F5C, U+4F60, U+4F7F, U+4F8B, U+4F9B, U+4F9D, U+4FBF,
    U+4FC4, U+4FDD, U+4FE1, U+500D, U+503C, U+5047, U+504F, U+5143, U+5145, U+5148, U+514B,
    U+514D, U+5165, U+5168, U+516C, U+5170-5171, U+5173, U+5176-5179, U+5185, U+5188, U+518D,
    U+5199, U+51B0, U+51B3, U+51B5, U+51C6, U+51E0, U+51ED, U+51EF, U+51FA-51FB, U+51FD,
    U+5206-5207, U+5211-5212, U+5217, U+521A, U+521D, U+5220, U+5224, U+5229, U+522B, U+5230,
    U+5236, U+524D, U+5269, U+526F, U+529B, U+529E, U+52A0-52A1, U+52A8-52AA, U+52B3, U+52BF,
    U+52D2, U+5305, U+5308, U+5316-5317, U+533A, U+533F, U+5355, U+5357, U+535A, U+5360-5362,
    U+5370-5371, U+5373, U+5377, U+5384, U+5386, U+538B, U+539F, U+53BB, U+53BF, U+53C2,
    U+53CA, U+53CC, U+53D1, U+53D6-53D9, U+53E3-53E4, U+53E6, U+53EA, U+53EF-53F0, U+53F2,
    U+53F7, U+5404, U+5408-5409, U+540C-540E, U+5411, U+5417, U+542B-542C, U+544A, U+5458,
    U+5468, U+547D, U+548C, U+54A8, U+54C1, U+54C8, U+54CD, U+54E5, U+54EA, U+5510, U+5580,
    U+5668, U+56DE, U+56E0, U+56F4, U+56FA, U+56FD-56FE, U+571F, U+5723, U+5728, U+572D,
    U+5730, U+573A, U+5747, U+5761, U+5766, U+578B, U+57C3, U+57CE, U+57D4, U+57FA, U+5821,
    U+5854, U+585E, U+586B, U+5883, U+58A8, U+58EB, U+58F0, U+5904, U+5907, U+590D, U+5916,
    U+591A, U+5927, U+5929-592B, U+5931, U+5934, U+5947, U+5957, U+5965, U+597D, U+5982,
    U+59C6, U+59CB, U+59D3-59D4, U+5A01, U+5AE9, U+5B50, U+5B57-5B58, U+5B5F, U+5B63, U+5B81,
    U+5B83, U+5B89, U+5B8C, U+5B98, U+5B9A-5B9C, U+5B9E, U+5BA1-5BA2, U+5BA4, U+5BB6, U+5BB9,
    U+5BBD-5BBE, U+5BC6, U+5BCC, U+5BE8, U+5BF8-5BF9, U+5BFC, U+5C01, U+5C06, U+5C0F, U+5C11,
    U+5C14, U+5C1A, U+5C1D, U+5C31, U+5C3A, U+5C3C, U+5C42, U+5C45, U+5C4F, U+5C55, U+5C5E,
    U+5C71, U+5C7F, U+5C9B, U+5DDE, U+5DE5, U+5DEE, U+5DF1-5DF2, U+5DF4, U+5E02-5E03, U+5E08,
    U+5E0C, U+5E15, U+5E1D, U+5E26, U+5E2D, U+5E38, U+5E55, U+5E72-5E74, U+5E76, U+5E87,
    U+5E8F, U+5E93, U+5E95, U+5EA6, U+5EAD, U+5EF6-5EF7, U+5EFA, U+5F00, U+5F0F, U+5F20,
    U+5F3A, U+5F53, U+5F55, U+5F62, U+5F71, U+5F80, U+5F85, U+5F88, U+5F8B, U+5F97, U+5FB7,
    U+5FC3, U+5FD7, U+5FD9, U+5FEB, U+6001, U+6027, U+603B, U+6050, U+6062, U+6069, U+606F,
    U+60A8, U+60C5, U+60E7, U+610F, U+613F, U+6155, U+6162, U+61C2, U+6208, U+6210-6211,
    U+6216, U+622A, U+6237, U+6240, U+624B, U+624D, U+6253, U+6258, U+6263, U+6267,
    U+626B-626C, U+6279, U+628A, U+62A4-62A5, U+62AC, U+62BC, U+62C9, U+62D2, U+62D8, U+62DC,
    U+62E9, U+62EC, U+62FF, U+6301, U+6307, U+6309, U+631D, U+632A, U+6355, U+6362, U+636E,
    U+6377, U+6392, U+63A5, U+63A7-63A8, U+63AA, U+63CF-63D0, U+641C, U+643A, U+6458, U+6469,
    U+6492, U+64A4, U+64B0, U+652F, U+6536, U+6539, U+653E-653F, U+6548, U+6551, U+6566,
    U+6570, U+6574, U+6587, U+6590, U+6599, U+65AF-65B0, U+65B9, U+65BD, U+65C1, U+65C5,
    U+65E0, U+65E5-65E6, U+65E9, U+65F6, U+65FA, U+6602, U+660E, U+662F, U+663E, U+666E-666F,
    U+667A, U+6682, U+66F2, U+66F4, U+66FC, U+66FE-6700, U+6708-6709, U+670D, U+6717, U+671D,
    U+671F, U+672A, U+672C, U+672F, U+673A, U+6743, U+6750, U+6761, U+6765, U+677E-677F,
    U+6781, U+6784, U+6790, U+6797, U+679C, U+67D0, U+67E5, U+67EC, U+67F1, U+6807,
    U+6837-6839, U+683C, U+6848, U+6851, U+6885, U+68B5, U+68C0, U+68EE, U+697C, U+6982,
    U+6A21, U+6A2A, U+6B21, U+6B27, U+6B62-6B65, U+6B8A, U+6BB5, U+6BCD, U+6BCF, U+6BD4,
    U+6BDB, U+6BEB, U+6C11, U+6C34, U+6C38, U+6C42, U+6C47, U+6C57, U+6C64, U+6C6A, U+6C76,
    U+6C83, U+6C99, U+6CA1, U+6CBB, U+6CCA, U+6CD5, U+6CE2, U+6CE8, U+6CF0, U+6CFD, U+6D0B,
    U+6D1B, U+6D25, U+6D2A, U+6D32, U+6D3B, U+6D4B, U+6D4E, U+6D66, U+6D77, U+6D88, U+6DFB,
    U+6E05, U+6E2F, U+6E7E, U+6E90, U+6EE1, U+6FB3, U+7070, U+70B9, U+7136, U+7231,
    U+7247-7248, U+724C, U+7259, U+7279, U+72AF, U+72B6, U+72EC, U+72F1, U+7387, U+73BB,
    U+73C0, U+73ED, U+7406, U+7459, U+745E, U+74DC, U+74E6, U+751F, U+7528, U+7531, U+7533,
    U+7535, U+7538, U+754C, U+7559, U+7586, U+767B, U+767D-767E, U+7684, U+76AE, U+76D1,
    U+76D6, U+76DF, U+76EE, U+76F4, U+76F8, U+770B, U+771F, U+77E5, U+77ED, U+7801, U+786E,
    U+793A, U+793E, U+7981, U+798F, U+79BB, U+79CD, U+79D1, U+79D8, U+79EF-79F0, U+79FB,
    U+7A0B, U+7A0D, U+7A3F, U+7A81, U+7A97, U+7ACB, U+7AE0, U+7B2C, U+7B49, U+7B5B, U+7B7E,
    U+7B80, U+7B97, U+7BA1, U+7BB1, U+7C4D, U+7C73, U+7C7B, U+7CA4, U+7CFB, U+7D20, U+7D22,
    U+7D2F, U+7E41, U+7EA6-7EA7, U+7EAA, U+7EB3, U+7EB8, U+7EBD, U+7EBF, U+7EC6, U+7EC8,
    U+7ECD, U+7ECF, U+7ED3, U+7ED8-7ED9, U+7EDC-7EDD, U+7EDF, U+7EE7, U+7EED, U+7EF4, U+7F05,
    U+7F13, U+7F16, U+7F29, U+7F34, U+7F3A, U+7F51, U+7F57, U+7F6A, U+7F6E, U+7F81, U+7F8E,
    U+7FA4, U+7FFB, U+8001, U+8003, U+8005, U+800C, U+8033, U+8054, U+80AF, U+80CC, U+80FD,
    U+8111, U+811A, U+8131, U+814A, U+81EA, U+81F3-81F4, U+820C, U+8272, U+8282, U+82AC,
    U+82CF, U+82E5, U+82F1, U+8303, U+8328, U+8349, U+8377, U+83AB, U+83B1, U+83B7, U+83F2,
    U+8404, U+8428, U+8461, U+8482, U+8499, U+84EC, U+884C, U+8857, U+8868, U+88AB, U+88C1,
    U+897F, U+8981, U+8986, U+89C1-89C2, U+89C6, U+89C8, U+89D2, U+89E3, U+8A00, U+8B66,
    U+8BA1, U+8BA4, U+8BA9, U+8BAE, U+8BB0, U+8BB8, U+8BBE, U+8BC1, U+8BC9, U+8BD5,
    U+8BDD-8BDE, U+8BE2, U+8BE5-8BE6, U+8BED, U+8BF4, U+8BF7, U+8BFA-8BFB, U+8C01, U+8C03,
    U+8C61, U+8D1D, U+8D1F, U+8D22-8D23, U+8D25-8D26, U+8D28, U+8D2D, U+8D39, U+8D5E, U+8D64,
    U+8D6B, U+8D8A-8D8B, U+8DB3, U+8DDD, U+8DDF, U+8DE8, U+8DEF, U+8DF3, U+8EAB, U+8F6C,
    U+8F7D, U+8F83, U+8F96, U+8FA9, U+8FB9, U+8FBE, U+8FC7, U+8FD0-8FD1, U+8FD4, U+8FD8-8FD9,
    U+8FDB-8FDC, U+8FDF, U+8FEA, U+8FF0, U+9000-9002, U+9009, U+9010, U+9012, U+901A, U+902E,
    U+903E, U+9053, U+9063, U+906D-906E, U+90A3, U+90A6, U+90E8, U+90FD, U+914B, U+9177,
    U+91C7, U+91CA, U+91CC-91CD, U+91CF, U+91D1, U+9488, U+949F, U+94A6, U+9500, U+952E,
    U+957F, U+95E8, U+95ED-95EE, U+95F4, U+9605, U+961F, U+9636, U+963B, U+963F-9640, U+9644,
    U+9650, U+9662, U+9664, U+9669, U+9675-9676, U+9686, U+968F-9690, U+969C, U+96C5-96C6,
    U+9700, U+975E, U+9760, U+9762, U+97E6, U+97E9, U+9875-9876, U+9879-987B, U+987F, U+9884,
    U+9886, U+9891, U+9898, U+989C-989D, U+98CE, U+9910, U+9996, U+9999, U+9A6C, U+9A71,
    U+9A73, U+9A8C, U+9AA4, U+9AD8, U+9C81, U+9C9C, U+9EA6, U+9ECE, U+9ED1, U+9ED8, U+9F84,
    U+FF01-FF5E;
}
@font-face {
  font-family: "AT Report Noto Sans SC";
  src: url("/report/fonts/noto-sans-sc-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
  unicode-range:
    U+0020-007E, U+00B7, U+00D7, U+00F7, U+2013-2014, U+201C-201D, U+2026, U+2192, U+25B2,
    U+25BC, U+3000-303F, U+4E00-4E01, U+4E07, U+4E0A-4E0B, U+4E0D-4E0E, U+4E18, U+4E1C, U+4E24,
    U+4E2A, U+4E2D, U+4E34, U+4E39-4E3B, U+4E3E, U+4E45, U+4E48-4E49, U+4E4B-4E4D, U+4E54,
    U+4E5F, U+4E66, U+4E70, U+4E86, U+4E88, U+4E8B-4E8C, U+4E8E, U+4E9A-4E9B, U+4EA4,
    U+4EAB-4EAC, U+4EBA, U+4EC0, U+4EC5, U+4ECD-4ECE, U+4ED6, U+4EE3-4EE5, U+4EF6, U+4EFB,
    U+4EFD, U+4F0A, U+4F10-4F11, U+4F1A, U+4F26, U+4F2A, U+4F2F-4F30, U+4F3C, U+4F4D-4F4E,
    U+4F53, U+4F55, U+4F59, U+4F5B-4F5C, U+4F60, U+4F7F, U+4F8B, U+4F9B, U+4F9D, U+4FBF,
    U+4FC4, U+4FDD, U+4FE1, U+500D, U+503C, U+5047, U+504F, U+5143, U+5145, U+5148, U+514B,
    U+514D, U+5165, U+5168, U+516C, U+5170-5171, U+5173, U+5176-5179, U+5185, U+5188, U+518D,
    U+5199, U+51B0, U+51B3, U+51B5, U+51C6, U+51E0, U+51ED, U+51EF, U+51FA-51FB, U+51FD,
    U+5206-5207, U+5211-5212, U+5217, U+521A, U+521D, U+5220, U+5224, U+5229, U+522B, U+5230,
    U+5236, U+524D, U+5269, U+526F, U+529B, U+529E, U+52A0-52A1, U+52A8-52AA, U+52B3, U+52BF,
    U+52D2, U+5305, U+5308, U+5316-5317, U+533A, U+533F, U+5355, U+5357, U+535A, U+5360-5362,
    U+5370-5371, U+5373, U+5377, U+5384, U+5386, U+538B, U+539F, U+53BB, U+53BF, U+53C2,
    U+53CA, U+53CC, U+53D1, U+53D6-53D9, U+53E3-53E4, U+53E6, U+53EA, U+53EF-53F0, U+53F2,
    U+53F7, U+5404, U+5408-5409, U+540C-540E, U+5411, U+5417, U+542B-542C, U+544A, U+5458,
    U+5468, U+547D, U+548C, U+54A8, U+54C1, U+54C8, U+54CD, U+54E5, U+54EA, U+5510, U+5580,
    U+5668, U+56DE, U+56E0, U+56F4, U+56FA, U+56FD-56FE, U+571F, U+5723, U+5728, U+572D,
    U+5730, U+573A, U+5747, U+5761, U+5766, U+578B, U+57C3, U+57CE, U+57D4, U+57FA, U+5821,
    U+5854, U+585E, U+586B, U+5883, U+58A8, U+58EB, U+58F0, U+5904, U+5907, U+590D, U+5916,
    U+591A, U+5927, U+5929-592B, U+5931, U+5934, U+5947, U+5957, U+5965, U+597D, U+5982,
    U+59C6, U+59CB, U+59D3-59D4, U+5A01, U+5AE9, U+5B50, U+5B57-5B58, U+5B5F, U+5B63, U+5B81,
    U+5B83, U+5B89, U+5B8C, U+5B98, U+5B9A-5B9C, U+5B9E, U+5BA1-5BA2, U+5BA4, U+5BB6, U+5BB9,
    U+5BBD-5BBE, U+5BC6, U+5BCC, U+5BE8, U+5BF8-5BF9, U+5BFC, U+5C01, U+5C06, U+5C0F, U+5C11,
    U+5C14, U+5C1A, U+5C1D, U+5C31, U+5C3A, U+5C3C, U+5C42, U+5C45, U+5C4F, U+5C55, U+5C5E,
    U+5C71, U+5C7F, U+5C9B, U+5DDE, U+5DE5, U+5DEE, U+5DF1-5DF2, U+5DF4, U+5E02-5E03, U+5E08,
    U+5E0C, U+5E15, U+5E1D, U+5E26, U+5E2D, U+5E38, U+5E55, U+5E72-5E74, U+5E76, U+5E87,
    U+5E8F, U+5E93, U+5E95, U+5EA6, U+5EAD, U+5EF6-5EF7, U+5EFA, U+5F00, U+5F0F, U+5F20,
    U+5F3A, U+5F53, U+5F55, U+5F62, U+5F71, U+5F80, U+5F85, U+5F88, U+5F8B, U+5F97, U+5FB7,
    U+5FC3, U+5FD7, U+5FD9, U+5FEB, U+6001, U+6027, U+603B, U+6050, U+6062, U+6069, U+606F,
    U+60A8, U+60C5, U+60E7, U+610F, U+613F, U+6155, U+6162, U+61C2, U+6208, U+6210-6211,
    U+6216, U+622A, U+6237, U+6240, U+624B, U+624D, U+6253, U+6258, U+6263, U+6267,
    U+626B-626C, U+6279, U+628A, U+62A4-62A5, U+62AC, U+62BC, U+62C9, U+62D2, U+62D8, U+62DC,
    U+62E9, U+62EC, U+62FF, U+6301, U+6307, U+6309, U+631D, U+632A, U+6355, U+6362, U+636E,
    U+6377, U+6392, U+63A5, U+63A7-63A8, U+63AA, U+63CF-63D0, U+641C, U+643A, U+6458, U+6469,
    U+6492, U+64A4, U+64B0, U+652F, U+6536, U+6539, U+653E-653F, U+6548, U+6551, U+6566,
    U+6570, U+6574, U+6587, U+6590, U+6599, U+65AF-65B0, U+65B9, U+65BD, U+65C1, U+65C5,
    U+65E0, U+65E5-65E6, U+65E9, U+65F6, U+65FA, U+6602, U+660E, U+662F, U+663E, U+666E-666F,
    U+667A, U+6682, U+66F2, U+66F4, U+66FC, U+66FE-6700, U+6708-6709, U+670D, U+6717, U+671D,
    U+671F, U+672A, U+672C, U+672F, U+673A, U+6743, U+6750, U+6761, U+6765, U+677E-677F,
    U+6781, U+6784, U+6790, U+6797, U+679C, U+67D0, U+67E5, U+67EC, U+67F1, U+6807,
    U+6837-6839, U+683C, U+6848, U+6851, U+6885, U+68B5, U+68C0, U+68EE, U+697C, U+6982,
    U+6A21, U+6A2A, U+6B21, U+6B27, U+6B62-6B65, U+6B8A, U+6BB5, U+6BCD, U+6BCF, U+6BD4,
    U+6BDB, U+6BEB, U+6C11, U+6C34, U+6C38, U+6C42, U+6C47, U+6C57, U+6C64, U+6C6A, U+6C76,
    U+6C83, U+6C99, U+6CA1, U+6CBB, U+6CCA, U+6CD5, U+6CE2, U+6CE8, U+6CF0, U+6CFD, U+6D0B,
    U+6D1B, U+6D25, U+6D2A, U+6D32, U+6D3B, U+6D4B, U+6D4E, U+6D66, U+6D77, U+6D88, U+6DFB,
    U+6E05, U+6E2F, U+6E7E, U+6E90, U+6EE1, U+6FB3, U+7070, U+70B9, U+7136, U+7231,
    U+7247-7248, U+724C, U+7259, U+7279, U+72AF, U+72B6, U+72EC, U+72F1, U+7387, U+73BB,
    U+73C0, U+73ED, U+7406, U+7459, U+745E, U+74DC, U+74E6, U+751F, U+7528, U+7531, U+7533,
    U+7535, U+7538, U+754C, U+7559, U+7586, U+767B, U+767D-767E, U+7684, U+76AE, U+76D1,
    U+76D6, U+76DF, U+76EE, U+76F4, U+76F8, U+770B, U+771F, U+77E5, U+77ED, U+7801, U+786E,
    U+793A, U+793E, U+7981, U+798F, U+79BB, U+79CD, U+79D1, U+79D8, U+79EF-79F0, U+79FB,
    U+7A0B, U+7A0D, U+7A3F, U+7A81, U+7A97, U+7ACB, U+7AE0, U+7B2C, U+7B49, U+7B5B, U+7B7E,
    U+7B80, U+7B97, U+7BA1, U+7BB1, U+7C4D, U+7C73, U+7C7B, U+7CA4, U+7CFB, U+7D20, U+7D22,
    U+7D2F, U+7E41, U+7EA6-7EA7, U+7EAA, U+7EB3, U+7EB8, U+7EBD, U+7EBF, U+7EC6, U+7EC8,
    U+7ECD, U+7ECF, U+7ED3, U+7ED8-7ED9, U+7EDC-7EDD, U+7EDF, U+7EE7, U+7EED, U+7EF4, U+7F05,
    U+7F13, U+7F16, U+7F29, U+7F34, U+7F3A, U+7F51, U+7F57, U+7F6A, U+7F6E, U+7F81, U+7F8E,
    U+7FA4, U+7FFB, U+8001, U+8003, U+8005, U+800C, U+8033, U+8054, U+80AF, U+80CC, U+80FD,
    U+8111, U+811A, U+8131, U+814A, U+81EA, U+81F3-81F4, U+820C, U+8272, U+8282, U+82AC,
    U+82CF, U+82E5, U+82F1, U+8303, U+8328, U+8349, U+8377, U+83AB, U+83B1, U+83B7, U+83F2,
    U+8404, U+8428, U+8461, U+8482, U+8499, U+84EC, U+884C, U+8857, U+8868, U+88AB, U+88C1,
    U+897F, U+8981, U+8986, U+89C1-89C2, U+89C6, U+89C8, U+89D2, U+89E3, U+8A00, U+8B66,
    U+8BA1, U+8BA4, U+8BA9, U+8BAE, U+8BB0, U+8BB8, U+8BBE, U+8BC1, U+8BC9, U+8BD5,
    U+8BDD-8BDE, U+8BE2, U+8BE5-8BE6, U+8BED, U+8BF4, U+8BF7, U+8BFA-8BFB, U+8C01, U+8C03,
    U+8C61, U+8D1D, U+8D1F, U+8D22-8D23, U+8D25-8D26, U+8D28, U+8D2D, U+8D39, U+8D5E, U+8D64,
    U+8D6B, U+8D8A-8D8B, U+8DB3, U+8DDD, U+8DDF, U+8DE8, U+8DEF, U+8DF3, U+8EAB, U+8F6C,
    U+8F7D, U+8F83, U+8F96, U+8FA9, U+8FB9, U+8FBE, U+8FC7, U+8FD0-8FD1, U+8FD4, U+8FD8-8FD9,
    U+8FDB-8FDC, U+8FDF, U+8FEA, U+8FF0, U+9000-9002, U+9009, U+9010, U+9012, U+901A, U+902E,
    U+903E, U+9053, U+9063, U+906D-906E, U+90A3, U+90A6, U+90E8, U+90FD, U+914B, U+9177,
    U+91C7, U+91CA, U+91CC-91CD, U+91CF, U+91D1, U+9488, U+949F, U+94A6, U+9500, U+952E,
    U+957F, U+95E8, U+95ED-95EE, U+95F4, U+9605, U+961F, U+9636, U+963B, U+963F-9640, U+9644,
    U+9650, U+9662, U+9664, U+9669, U+9675-9676, U+9686, U+968F-9690, U+969C, U+96C5-96C6,
    U+9700, U+975E, U+9760, U+9762, U+97E6, U+97E9, U+9875-9876, U+9879-987B, U+987F, U+9884,
    U+9886, U+9891, U+9898, U+989C-989D, U+98CE, U+9910, U+9996, U+9999, U+9A6C, U+9A71,
    U+9A73, U+9A8C, U+9AA4, U+9AD8, U+9C81, U+9C9C, U+9EA6, U+9ECE, U+9ED1, U+9ED8, U+9F84,
    U+FF01-FF5E;
}
@font-face {
  font-family: "AT Report Noto Sans Devanagari";
  src: url("/report/fonts/noto-sans-devanagari-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
  unicode-range: U+0020-0023, U+0025, U+0027-003F, U+005B-005F, U+007B-007E, U+0900-097F;
}
@font-face {
  font-family: "AT Report Noto Sans Devanagari";
  src: url("/report/fonts/noto-sans-devanagari-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
  unicode-range: U+0020-0023, U+0025, U+0027-003F, U+005B-005F, U+007B-007E, U+0900-097F;
}
@font-face {
  font-family: "AT Report Noto Sans Arabic";
  src: url("/report/fonts/noto-sans-arabic-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
  unicode-range: U+0020-0021, U+002C-002E, U+0030-003A, U+0600-06FF, U+0750-077F, U+FB50-FBC2, U+FBD3-FD8F, U+FD92-FDC7, U+FDCF, U+FDF0-FDFF, U+FE70-FE74, U+FE76-FEFC, U+FEFF;
}
@font-face {
  font-family: "AT Report Noto Sans Arabic";
  src: url("/report/fonts/noto-sans-arabic-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
  unicode-range: U+0020-0021, U+002C-002E, U+0030-003A, U+0600-06FF, U+0750-077F, U+FB50-FBC2, U+FBD3-FD8F, U+FD92-FDC7, U+FDCF, U+FDF0-FDFF, U+FE70-FE74, U+FE76-FEFC, U+FEFF;
}

/* `.page`'s own font-family (line ~76) is the fallback tail every one of
 * these repeats verbatim, so a codepoint outside the subset -- or the
 * whole family failing to load at all -- degrades to EXACTLY what every
 * other report language already gets, never anything worse. */
.page[lang="zh-Hans"] {
  font-family: "AT Report Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.page[lang="hi"] {
  font-family: "AT Report Noto Sans Devanagari", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.page[lang="ar"] {
  font-family: "AT Report Noto Sans Arabic", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}


/* Task 12 fix: the `font: <weight> <size>/<line-height> inherit;` shorthand
 * these 10 rules used is INVALID CSS -- `inherit` is only a legal value for
 * the font-family COMPONENT of the shorthand's global-keyword form (`font:
 * inherit;` alone), not as a positional value inside `<weight> <size>/<lh>
 * <family>`. Chromium (and every other engine) drops the WHOLE malformed
 * declaration on a per-declaration basis, silently discarding every one of
 * these `font:` lines -- so every plain `<div>`/`<span>` styled only by a
 * `.t-*` class (stat labels/values, note/body/row/table-cell text, the
 * footer) was rendering at the browser's UA default (16px, normal weight),
 * not its intended pt size. It happened to be invisible on `.page-title`/
 * `.section-title` in earlier manual checks only because those sit on an
 * `<h1>`/`<h2>` tag whose OWN default UA styling (large + bold) coincidentally
 * looks about right -- `measure-blocks.mjs` (task 11) never caught it either,
 * since it only asserts container HEIGHTS, which these fixed-height blocks
 * still hit correctly regardless of the text's actual rendered size. Found
 * via task 12's Playwright screenshot of JudgeOverviewPage/WaitLegsPage,
 * where a 16px "Individual hearings scheduled" label visibly crowded the
 * value below it inside a 48pt stat tile. Fixed by splitting each shorthand
 * into its individual longhands (font-family is intentionally omitted --
 * it already inherits from `.page`'s own `font-family` declaration without
 * needing to be restated here, which is what every one of these rules was
 * actually trying to say). */
.t-title { font-weight: 700; font-size: 18pt; line-height: 1.2; color: var(--ink); }
.t-section { font-weight: 700; font-size: 11pt; line-height: 1.3; color: var(--ink); }
.t-body { font-weight: 400; font-size: 10.5pt; line-height: 1.3; color: var(--ink); }
.t-note { font-weight: 400; font-size: 8.5pt; line-height: 1.3; color: var(--muted); }
.t-stat-value { font-weight: 700; font-size: 22pt; line-height: 1.1; color: var(--ink); }
.t-stat-label { font-weight: 400; font-size: 9pt; line-height: 1.3; color: var(--muted); }
.t-row { font-weight: 400; font-size: 10pt; line-height: 1.3; color: var(--ink); }
.t-th { font-weight: 600; font-size: 8.5pt; line-height: 1.2; color: var(--muted); }
.t-td { font-weight: 400; font-size: 9.5pt; line-height: 1.2; color: var(--ink); }
.t-footer { font-weight: 400; font-size: 7pt; line-height: 1.3; color: var(--muted); }

/* ---------------------------------------------------------------------
 * Shared blocks (chapters/blocks.js)
 *
 * Review round 1 finding: these blocks' rendered heights must equal their
 * DECLARED budget numbers (chapters/blocks.js) EXACTLY, regardless of
 * content -- `budget()` (chapters/frame.js) only sums the numbers a block
 * claims, it never renders anything, so a block whose CSS renders taller
 * than its own declared height can silently overflow a page's
 * `overflow:hidden` clip even though `budget()` said the page fit. Every
 * rule below therefore pins a FIXED box height (`box-sizing: border-box`,
 * explicit `height`, never `min-height`/`auto`) and clips/truncates
 * (`overflow:hidden` + line-clamp or `text-overflow:ellipsis`) instead of
 * letting content grow the box. The arithmetic behind each fixed number:
 *
 *   .stat-box   -- 48pt total = 2.1pt top padding + 20.7pt label
 *                  (2 lines @ 9pt * line-height 1.15) + 23.1pt value
 *                  (1 line @ 22pt * line-height 1.05) + 2.1pt bottom
 *                  padding. The rule is drawn with `outline` (offset
 *                  inward), not `border` -- an outline does not
 *                  participate in the box model, so it costs no extra
 *                  height the arithmetic above would otherwise have to
 *                  account for.
 *   .row        -- 18pt total = 17.5pt line-height + 0.5pt bottom border
 *                  (box-sizing: border-box folds the border into the 18pt,
 *                  it does not add to it).
 *   .report-table td/th -- 17pt/16pt total = 16.5pt/15.5pt line-height +
 *                  0.5pt bottom border, same border-box accounting as
 *                  `.row`. Set on the CELLS, not the `<tr>` (an explicit
 *                  `height` on `<tr>` is not reliably authoritative across
 *                  table layout engines; a cell's `height` is what a
 *                  `table-layout: fixed` row actually sizes itself to).
 *   .note       -- capped with `max-height`/`line-height` set INLINE per
 *                  call by chapters/blocks.js#note (not by a single CSS
 *                  number here), because `note(text, size)` legitimately
 *                  declares a DIFFERENT height for a larger `size` (pages.md
 *                  §1.7 call sites pass 9/9.5/10/10.5, not just the 8.5pt
 *                  default) -- a single hardcoded `max-height: 24pt` here
 *                  would truncate those larger notes below their own
 *                  correctly-declared (and budgeted) height. `.note`'s
 *                  static rule below only carries the declarative bits that
 *                  never vary by size (2-line clamp, overflow).
 * --------------------------------------------------------------------- */

.stat-box {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 2.1pt 8pt;
  height: 48pt;
  box-sizing: border-box;
  border-radius: 6pt;
  outline: 1pt solid var(--rule);
  outline-offset: -1pt;
}
.stat-box .stat-label {
  height: 20.7pt; /* 2 lines @ 9pt * 1.15 */
  box-sizing: border-box;
  line-height: 1.15;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.stat-box .stat-value {
  height: 23.1pt; /* 1 line @ 22pt * 1.05 */
  box-sizing: border-box;
  line-height: 1.05;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row {
  height: 18pt; /* 17.5pt line-height + 0.5pt border, border-box */
  box-sizing: border-box;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8pt;
  padding: 0;
  line-height: 17.5pt;
  border-bottom: 0.5pt solid var(--rule);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-label,
.row-value {
  min-width: 0; /* flex items default to min-width:auto, which defeats
                   overflow/ellipsis truncation on a shrinking child */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-emphasize .row-value {
  font-weight: 700;
}

.report-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}
.report-table th,
.report-table td {
  height: 16pt; /* header: 15.5pt line-height + 0.5pt border */
  box-sizing: border-box;
  line-height: 15.5pt;
  text-align: left;
  padding: 0 3pt;
  border-bottom: 0.5pt solid var(--rule);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-table td {
  height: 17pt; /* body row: 16.5pt line-height + 0.5pt border */
  line-height: 16.5pt;
}
.report-table td.num,
.report-table th.num {
  text-align: right;
}
.report-table tr.row-emphasize td {
  background: var(--soft);
  font-weight: 700;
}

.note {
  color: var(--muted);
  box-sizing: border-box;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.no-data { color: var(--muted); padding-top: 6pt; }
.slot-message { color: var(--muted); }

.section-title { margin: 0; }
.data-title {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Task 12 additions (chapters/blocks.js#gap/#bodyText): a counted spacer and
 * the report's plain-ink prose voice (distinct from `.t-note`'s 8.5pt muted
 * captions) -- see chapters/blocks.js for why `.gap`'s height must always be
 * counted in a page's own `heights` array rather than left as free CSS
 * margin/gap. */
.gap {
  width: 100%;
}
.body-lines .body-line {
  margin: 0;
}

.stat-row {
  display: flex;
  gap: 10pt;
}
.stat-row > .stat-box {
  flex: 1 1 0;
  min-width: 0;
}

/* Spec §7 readings + §5 tile pairs */
.reading-note { overflow: hidden; }
/* Redesign Task 6: the wait chapter's headline row (JudgeDetailView.swift:7791/7806) */
.wait-headline { display: flex; align-items: baseline; gap: 8pt; height: 30pt; line-height: 30pt; overflow: hidden; white-space: nowrap; }
.wait-headline .wait-median { color: var(--brand); }
.wait-headline .wait-trend { color: var(--muted); }
.reading, .how { overflow: hidden; line-height: 12pt; }
.how { white-space: nowrap; text-overflow: ellipsis; height: 12pt; }
/* The reading sentence may run to two lines (24pt budget, see blocks.js#readingNote). */
.reading { color: var(--ink); font-weight: 500; height: 24pt; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.how { color: var(--muted); }
.reading-note .how-2 { -webkit-line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical; white-space: normal; overflow: hidden; height: 24pt; }
.reading-note .how-3 { -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; white-space: normal; overflow: hidden; height: 36pt; }
.pair-label { height: 12pt; line-height: 12pt; }
.chart-title { height: 12pt; line-height: 12pt; font-weight: 600; }
.pair-tiles.stat-row > .stat-box { height: 36pt; padding: 1.2pt 8pt; }
.pair-tiles .stat-box .stat-label { height: 10.35pt; -webkit-line-clamp: 1; }
.pair-tiles .stat-value.t-body { color: var(--muted); }
.tile-dot { display: inline-block; width: 6pt; height: 6pt; border-radius: 3pt; margin-right: 4pt; vertical-align: middle; }
html[data-mono="1"] .tile-dot { background: #555 !important; }

/* Plan C Task 4: recordRows (app BIAAppealRow / JudgeMotionRow / JudgeBondRow).
 * 26pt per row includes the 2pt divider (border-box), matching .row's
 * "border lives inside the budgeted height" rule. Each line is a single
 * nowrap+ellipsis clamp so a long title/badge never grows the box. */
.record-rows { overflow: hidden; }
.record-row {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1pt;
  height: 26pt;
  box-sizing: border-box;
  overflow: hidden;
  border-bottom: 2pt solid var(--rule);
  padding: 1pt 0;
}
.record-line {
  display: flex;
  align-items: center;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.record-dot {
  width: 4pt;
  height: 4pt;
  border-radius: 2pt;
  flex: 0 0 4pt;
  margin-right: 4pt;
}
.record-title {
  font-size: 10pt;
  font-weight: 600;
  color: var(--ink);
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.record-badge {
  font-size: 8pt;
  font-weight: 700;
  color: #fff;
  border-radius: 3pt;
  padding: 0 4pt;
  line-height: 11pt;
  height: 11pt;
  flex: 0 0 auto;
  max-width: 40%;
  margin-left: 6pt;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.record-sub,
.record-trailing {
  font-size: 8.5pt;
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.record-sub { flex: 1 1 auto; }
.record-trailing {
  flex: 0 1 auto;
  margin-left: 6pt;
  text-align: right;
}
.record-trailing-strong {
  font-size: 9pt;
  font-weight: 700;
  color: var(--ink);
}

.chart-block {
  display: block;
}
.chart-block svg {
  display: block;
}

.page-qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2pt;
}
.page-qr .qr {
  display: block;
}
.qr-hint { color: var(--muted); }

/* Task 13 addition (chapters/attorney.js#rateBarBlock): AttorneyPage's own
 * hand-drawn headline rate bars -- pages.md's own callout that
 * `AttorneyPage.rateBar` is a private SwiftUI helper, NOT a ReportChartKit
 * component, so this is plain HTML/CSS rather than an SVG chart(). Without
 * these rules the label/decided-count/percent spans (inline by default)
 * collided with no gap between them -- caught in this task's own screenshot
 * pass (docs/.../pages/AttorneyPage.png).
 */
.attorney-rate-bar {
  display: flex;
  flex-direction: column;
  gap: 3pt;
}
.attorney-rate-label-row {
  display: flex;
  align-items: baseline;
  gap: 6pt;
}
.attorney-rate-pct {
  margin-left: auto;
}
.attorney-rate-track {
  position: relative;
  height: 12pt;
  border-radius: 6pt;
  background: var(--rule);
  overflow: hidden;
}
.attorney-rate-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  border-radius: 6pt;
}

/* Task 15 addition (chapters/blocks.js#shareBar): ArrestsPage's own
 * "share of a stated denominator" capsule track (ReportShareBar port,
 * controller ruling 4 -- implemented in blocks.js, not charts/kit.js).
 * 28pt total = 13pt label row + 3pt gap + 12pt track, mirroring the
 * already-established .attorney-rate-bar shape above almost exactly.
 */
.share-bar {
  display: flex;
  flex-direction: column;
  gap: 3pt;
}
.share-bar-label {
  height: 13pt;
  box-sizing: border-box;
  display: flex;
  align-items: baseline;
  gap: 6pt;
  overflow: hidden;
  white-space: nowrap;
}
.share-bar-title {
  font-size: 10pt;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
}
.share-bar-pct {
  margin-left: auto;
  font-size: 11pt;
  font-weight: 700;
  flex: 0 0 auto;
}
.share-bar-track {
  position: relative;
  height: 12pt;
  border-radius: 6pt;
  background: var(--rule);
  overflow: hidden;
}
.share-bar-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  border-radius: 6pt;
}

/* Task 14 addition (chapters/transfers.js#partnerBlock): TransfersPage's own
 * side-by-side source/destination partner lists (Swift's
 * `HStack(alignment:.top, spacing:24)`, ReportChapterPages.swift:670-673) --
 * two equal columns; the taller of the two decides the row's real rendered
 * height, so transfers.js's own `heights` array counts the MAX of the two
 * blocks, never their sum (a plain vertical stack would double-count the
 * shorter column's blank space).
 */
.two-col {
  display: flex;
  gap: 24pt;
}
.two-col > .two-col-item {
  flex: 1 1 0;
  min-width: 0;
}

/* Task 16 addition (chapters/cover.js#CoverPage): the hero firm-name/logo
 * banner and judge/court headline -- distinct from the small running
 * `.header-left` strip every page already carries (pages.md §1.2), this is
 * the large, once-per-report presentation controller ruling 3 asks for.
 */
.cover-logo img {
  display: block;
  width: 120pt;
  height: 90pt;
  object-fit: contain;
  object-position: left center;
}
.cover-brand { display: flex; align-items: center; gap: 5pt; color: var(--muted); }
/* Product links print as plain text (same colour, no underline) and stay
 * real links in the PDF. */
.page a.brand-link, .page a.brand-link:visited { color: inherit; text-decoration: none; }
.cover-brand-icon { width: 14pt; height: 14pt; border-radius: 3pt; flex: 0 0 auto; }
.page[dir="rtl"] .cover-brand { flex-direction: row-reverse; }
.cover-firm-name {
  font-weight: 700;
  font-size: 24pt;
  line-height: 1.2;
  color: var(--brand, #0f4c5c);
}
.cover-judge {
  font-weight: 700;
  font-size: 22pt;
  line-height: 1.2;
  color: var(--ink);
}
.cover-court {
  font-size: 13pt;
}
.cover-row {
  font-size: 11pt;
}

/* Task 16 addition (chapters/text.js#CoverLetterPage): the wrapped letter
 * body -- one `<div>` per already-wrapped line (plan.js#wrapCoverLetter).
 *
 * Final-review fix wave item 1 (Critical): `white-space: pre-wrap` let the
 * BROWSER wrap this div's own text again on top of the JS-side wrap --
 * harmless for Latin text (wrapCoverLetter's line already fits the column
 * budget, so pre-wrap never had anything left to do), but for CJK text a
 * browser applies Unicode line-breaking BETWEEN ADJACENT IDEOGRAPHS even
 * with no whitespace present, so a wide (width-2-per-char) line the old,
 * character-count-only wrapCoverLetter treated as "one word" (no spaces to
 * split on) got silently re-wrapped into many more visual lines than
 * text.js's slice/coverLetterPageCount ever budgeted for -- the excess
 * pushed past `.page{overflow:hidden}` and was lost with no error.
 * `wrapCoverLetter` is now itself width-aware (a CJK/full-width code point
 * counts as 2 columns) and breaks any span with no whitespace at the
 * column budget, so every line it returns is ALREADY guaranteed to fit --
 * `white-space: pre` (never `pre-wrap`) means the browser performs NO
 * wrapping of its own any more, and `overflow: hidden` is the
 * belt-and-suspenders backstop should a line somehow still be too wide
 * (a JS bug would then clip visibly on that one line, rather than
 * silently corrupting every later page's line numbering the way the
 * original bug did).
 */
.cover-letter-line {
  margin: 0;
  white-space: pre;
  overflow: hidden;
}

/* Task 16 addition (chapters/text.js#NextStepsPage): a checkbox-prefixed,
 * 2-line-clamped checklist row (chapters/text.js#checklistItem) -- free
 * text an attorney typed, so (unlike a computed, bounded-length sentence)
 * it can wrap and must clip safely rather than assume one line.
 */
.checklist-item {
  display: flex;
  align-items: flex-start;
  gap: 6pt;
  box-sizing: border-box;
  overflow: hidden;
}
.checklist-box {
  flex: 0 0 auto;
  font-size: 11pt;
  line-height: inherit;
}
.checklist-text {
  min-width: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Task 16 addition (chapters/text.js#NextStepsPage): the intro sentence
 * above the checklist -- budgeted (and clamped) as a 2-line worst case,
 * same reasoning as `.checklist-text` above, even though the English
 * catalog copy is one short sentence. */
.next-steps-intro {
  box-sizing: border-box;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Task 16 addition (chapters/appendix.js#paragraph): a 4-line-clamped
 * static-copy paragraph -- see appendix.js's own module comment for why
 * this is not a reuse of `.note`'s tighter 2-line clamp. */
.appx-paragraph {
  box-sizing: border-box;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}

/* Task 16 addition (chapters/datatable.js#DataTablePage): the truncation
 * note under a data table that didn't fit every row in the source chart's
 * points -- plain `.note` styling, no new rule needed beyond this comment
 * marking why `.note` (not `.appx-paragraph`) is the right fit here: the
 * truncation sentence is always short (a single "N of M rows shown"
 * count), matching `.note`'s own established 2-line role exactly.
 */

/* ---------------------------------------------------------------------
 * Print-only visibility (builder chrome never prints/exports)
 *
 * Task 19 fix: this block previously targeted a `.builder` class that
 * does not exist anywhere in index.html (the shell markup is `#app-shell`
 * > `#screen-preview` > `.preview-main` > `.preview-pane`/`#drawer` -- no
 * element is literally `class="builder"`), so it was a no-op: every bit of
 * the builder chrome (topbar, tabs, left panel, preview toolbar/progress)
 * printed right alongside the report pages. Worse, nothing here released
 * `.app-shell`'s `height: 100vh` / `.preview-pages`' `overflow: auto`
 * scrollable-viewport sizing (report.css) for print, so Chromium's
 * print-to-PDF clipped the WHOLE report to that one scrollable box's
 * content instead of paginating it -- verified live via Playwright's
 * `page.pdf()` (Task 19's render.spec.mjs): an 11-page "brief" report
 * came out as a literal one-page PDF (`/Kids [2 0 R]`, `/Count 1`) before
 * this fix. Found and fixed here rather than shipped as a smoke-test
 * workaround, per the task brief ("fix it minimally").
 * --------------------------------------------------------------------- */

@media print {
  #topbar,
  #screen-client,
  #drawer,
  #sample-banner,
  #preview-toolbar,
  #preview-progress,
  #letterhead-dialog,
  #account-dialog,
  #narrow-notice,
  #app-notice,
  .no-print {
    display: none !important;
  }
  html, body, #app-shell, #screen-preview, .preview-main, .preview-pane, .preview-pages {
    display: block !important;
    height: auto !important;
    min-height: 0 !important;
    overflow: visible !important;
    padding: 0 !important;
    margin: 0 !important;
    background: #fff !important;
  }
  /* `wrapPage()` (ui/preview.js) sizes/clips/scales every `.page` down to
   * PREVIEW_SCALE (0.9) for the on-screen preview via INLINE styles (an
   * `overflow: hidden` wrapper sized to the already-scaled box, plus an
   * inner div carrying `transform: scale(0.9)`) -- a stylesheet rule
   * cannot beat an inline style without `!important`. Undone here so each
   * `.page` prints at its own real, already-correct `--pw` x `--ph` size
   * instead of 0.9-scaled and clipped to a tiny window.
   */
  .preview-page-frame {
    width: auto !important;
    height: auto !important;
    overflow: visible !important;
    box-shadow: none !important;
  }
  .preview-page-frame > div {
    transform: none !important;
  }
  .page {
    break-after: page;
  }
}
