/* ===========================================================================
   HIRAVEEN WEBAPP -- DESIGN LANGUAGE
   document/product/design/design-webapp/language/hrv-webapp.css
   v0.2.0 -- 2026-09-08 (IST) -- webAppDesign chair
   (v0.1.0 2026-09-03. v0.2.0 adds SECTION 9B, THE SHELL, and the five tokens
    the shell forced: --hrv-shell-h, --hrv-sticky-top, --hrv-z-shell,
    --hrv-z-menu, --hrv-elev-menu. Nothing else changed except two retraction
    repairs in section 14. No existing value moved.)

   STATUS: BUILD DEFAULT. Adopted under D-WAD-004 (the WebApp sets its own
   values) and D-WAD-005 (two tiers, both WebApp-authored). Written to be
   REFACTORED -- every judgment call carries its reason and a REFACTOR TRIGGER
   so the next pass changes values, not archaeology.

   PRODUCT: social trade analytics platform, browser-class application.
   HOOK AND BAIT, as the owner ruled them on 2026-09-27 (backlog I-069): a
   visitor arrives searching for a stock to buy, so a call is the bait, and
   social is the hook that keeps them. That reverses the framing D-WAD-005
   s0 records; D-WAD-005's density partition rests on reading mode, not on
   which surface is the bait, and stands. The tools (watchlist, screener,
   charting, strategies) are built from this language's common modules.
   AUDIENCE: Indian retail, majority arrival organic and mobile, many
   first-time investors. Not a Terminal on the web.

   PROVENANCE: colour, radius, motion, layout floors and the numeric pin are
   the shipped kit (designer/kit/hrv-tokens.json v1.4.0, 18,332 B, verified at
   source). Type, space, rows and the mono face are WebApp-authored. Every
   departure is logged in ../divergence/LEDGER.md.

   D012: color.neutral is PRIVATE. The ramp is resolved ONLY into the semantic
   properties in section 2. NO RULE BELOW SECTION 2 MAY REFERENCE A NEUTRAL
   VALUE OR A RAW HEX. If you need a colour that is not here, that is a token
   gap -- raise it, do not inline it.
   =========================================================================== */


/* ===========================================================================
   1. FONTS
   ===========================================================================
   RULING -- SELF-HOST THE UPSTREAM BUILD. DO NOT USE THE GOOGLE FONTS CDN.

   Obs-WAD-16 (measured 2026-09-03): the Google Fonts build of Public Sans
   reports the SAME name-table version string as the upstream build --
   "Version 2.001" -- and LACKS U+20B9, the rupee. A version-string check
   passes a binary that cannot render a price. This is a shipping product for
   the Indian market. The rupee is not optional.
   VERIFY PER BINARY, BY CMAP, NOT BY VERSION STRING.

   Measured metrics, upstream Public Sans (UPM 2000):
     x-height   1034 = 0.517 em     cap-height 1446 = 0.723 em
     figure ht  1488 = 0.744 em     zero adv   1224 = 0.612 em  (prose ch)

   Place woff2 files at /fonts/ and keep the cmap assertion in the build.  */

@font-face{
  font-family:"Public Sans"; font-style:normal; font-weight:400;
  font-display:swap; src:url("/fonts/public-sans-400.woff2") format("woff2");
}
@font-face{
  font-family:"Public Sans"; font-style:normal; font-weight:600;
  font-display:swap; src:url("/fonts/public-sans-600.woff2") format("woff2");
}
/* NOTE: 500 is deliberately absent. See --hrv-weight-* in section 3. */


/* ===========================================================================
   2. COLOUR -- semantic tokens only, both themes
   =========================================================================== */
:root{
  color-scheme:light dark;

  --hrv-surface-base:#FAFAFB;
  --hrv-surface-raised:#FFFFFF;
  --hrv-surface-overlay:#F2F3F5;

  --hrv-ink-primary:#14171C;
  --hrv-ink-secondary:#33373D;
  --hrv-ink-muted:#6A7078;
  --hrv-ink-stale:#57595E;   /* was #6F7277 -- FAILED AA on overlay (4.35). Now 4.61. */
  /* ^ that 4.61 was #6B6E73 (ledger D-8). Walked 2026-09-24 (D-WAD-018, ledger D-9): Lc 77.12, 6.31:1 on overlay, its tightest. */
  --hrv-border-subtle:#E4E6EA;
  --hrv-border-default:#C9CDD3;
  --hrv-border-control:#878B91;  /* WCAG 1.4.11: a control boundary needs 3:1. */
  --hrv-border-strong:#898C92;

  /* equal-loudness matched by the desk (5.63 / 5.55 against white).
     P2: gain and loss take the SAME size and the SAME weight, always. */
  --hrv-gain-ink:#0B7841;   --hrv-gain-surface:#DAEFDF;
  --hrv-loss-ink:#A94B43;   --hrv-loss-surface:#FEE4E0;

  --hrv-alert-text:#7A4A02; --hrv-alert-surface:#F8E7D5;
  --hrv-action-text:#4636A8;
  --hrv-action-primary:#6B5BD2;
  --hrv-on-action:#FFFFFF;
  --hrv-focus-ring:#6B5BD2;

  /* ELEVATION -- minted 2026-09-08 (IST). ONE level, forced by the shell's
     three menus: symbol-search suggestions, the account menu, the mobile
     overflow panel.
     ONE AND NOT TWO, and the reason is a product law rather than restraint:
     NOTHING IN THE SHELL IS MODAL. Guest gating sits at the SAVE boundary and
     never the READ boundary, so no shell surface may block, disable or
     interrupt the page behind it. That is also WHY NO SCRIM TOKEN IS MINTED
     -- a scrim's whole meaning is "what is behind this is disabled", which is
     the exact posture this surface forbids.
     CONSUMED BY NO RULE SINCE 2026-09-16, and the measurement is why. Its
     consumers went one by one: v1's overflow panel with the panel
     (2026-09-13), the skip link's focus box (2026-09-15, D-WAD-010 c2) and
     the suggestion list (2026-09-16, D-WAD-012 c2). In LIGHT the shadow
     darkens the ground just outside the element's own border, which pulled
     that border under D-WAD-009's 3:1 overlay floor (the suggestion list's
     to 2.31 at its bottom edge); in DARK, measured, it HELPS. So an
     overlay's edge is border-control alone, with no shadow, and
     measurements/shell_gate.py's G7 reds a shadow on the shell, the tab bar,
     the suggestion list or the account menu, which takes none either
     (2026-10-02, M07-2, D-WAD-022). MEASURED. The token stays declared:
     M06 moves no token (D-WAD-021 c8), and the surface the trigger below
     names may yet want a depth.
     REFACTOR TRIGGER: a genuinely modal surface -- an order ticket, a
     destructive confirm -- is the first thing that legitimately needs a
     second depth AND a scrim. Mint them at that surface, not before, and
     price its edge against D-WAD-009 in LIGHT before it takes a shadow. */
  --hrv-elev-menu:0 6px 16px rgba(20,23,28,.14), 0 1px 3px rgba(20,23,28,.10);

  /* P1: a price series is NOT a gain. Never plot price in gain-ink. */
  --hrv-series-primary:#3969A6;
  --hrv-grid-line:#E4E6EA;
  --hrv-axis-text:#4A505A;
}

[data-theme="dark"]{
  --hrv-surface-base:#101318;
  --hrv-surface-raised:#14171C;
  --hrv-surface-overlay:#1A1D22;

  --hrv-ink-primary:#F2F3F5;
  /* SECONDARY RAISED 2026-09-16, D-WAD-015. Was #C9CDD3. That pair measured
     Lc 75.04 on surface-raised against the target's requirement of 75 -- four
     hundredths of margin, which is not a margin: any rounding, antialiasing
     change or later tweak flips it, and the flip is SILENT because nothing
     breaks when it happens. #D2D6DB gives Lc 80.42.
     ONE VALUE, THREE ROWS: T-03 (tab label at rest), S-05 (.hrv-nav__link)
     and S-15 (.hrv-btn label) all sit on this pair. The latter two are
     EITHER-class -- fixable by size OR ink -- and both are DESKTOP elements,
     so taking the ink here left the desktop bar at its then-current sizes where
     taking the size would have pushed nav and buttons to 15px.
     OVERTAKEN 2026-09-22: D-WAD-016's ladder moved `.hrv-nav__link` and
     `.hrv-btn` to 16px/600 regardless, because both ride the `data` rung.
     The ink ruling stands; the "leaves the bar alone" consequence does not.
     THE CONSTRAINT GOING UP IS HIERARCHY, NOT CONTRAST. ink-primary sits at
     Lc 98.95 on this ground. The primary/secondary gap was 23.9 and is now
     18.5; by #DBDEE2 it would be 13.5 and the two ranks start to blur.
     MUST STAY IDENTICAL to the prefers-color-scheme block below -- the two
     dark definitions hold the same 22 declarations (decl_audit.py gates it),
     and an edit to one alone would split them silently. */
  --hrv-ink-secondary:#D2D6DB;
  --hrv-ink-muted:#8A9099;
  --hrv-ink-stale:#CDD2D6;   /* was #7B7F83 -- FAILED AA on raised (4.45) and overlay (4.19). Now 4.61. */
  /* ^ that 4.61 was #82868A (ledger D-8). Walked 2026-09-24 (D-WAD-018, ledger D-9): Lc 77.14, 11.09:1 on overlay, its tightest. */
  --hrv-border-subtle:#24282D;
  --hrv-border-default:#2B2E34;
  --hrv-border-control:#66696F;  /* 3:1 control boundary */
  --hrv-border-strong:#65686E;

  --hrv-gain-ink:#41A477;   --hrv-gain-surface:#1C2E25;
  --hrv-loss-ink:#D47970;   --hrv-loss-surface:#392523;
  --hrv-alert-text:#D99F4E; --hrv-alert-surface:#34281A;
  --hrv-action-text:#A99BF0;

  /* Dark elevation carries more opacity than the light one. Measured, the
     dark shadow HELPS an overlay's border where the light one breaks it
     (D-WAD-010 c2), and no rule consumes either -- see the elevation note
     in :root. */
  --hrv-elev-menu:0 6px 16px rgba(0,0,0,.48), 0 1px 3px rgba(0,0,0,.36);

  --hrv-series-primary:#5889C9;
  --hrv-grid-line:#24272C;
  --hrv-axis-text:#B0B7C1;  /* kit chart.dark.axis-text #A8AEB8. Walked 2026-09-24 (D-WAD-018, ledger D-10): Lc 62.07 at 15/600 on raised */
}

/* RF-3, fixed 2026-09-15 (IST). :root says `color-scheme:light dark`, which tells
   agent to follow the OS. That is right when the OS is the theme source and
   WRONG the moment [data-theme] overrides it: an app-level dark theme on a
   light-OS machine got WHITE UA form internals -- scrollbars, date pickers,
   select popups -- on a #101318 page, measured at 18.61:1. The explicit theme
   must narrow color-scheme to match, or the agent paints the other one.
   The [data-theme="light"] line is the same bug mirrored. NOT MEASURED --
   inferred by symmetry from the same mechanism, and labelled so. */
[data-theme="dark"]{color-scheme:dark}
[data-theme="light"]{color-scheme:light}

@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --hrv-surface-base:#101318; --hrv-surface-raised:#14171C; --hrv-surface-overlay:#1A1D22;
    --hrv-ink-primary:#F2F3F5; --hrv-ink-secondary:#D2D6DB; /* D-WAD-015 -- keep identical to [data-theme="dark"] above */
    --hrv-ink-muted:#8A9099;   --hrv-ink-stale:#CDD2D6;
    --hrv-border-subtle:#24282D; --hrv-border-default:#2B2E34;
    --hrv-border-control:#66696F; --hrv-border-strong:#65686E;
    --hrv-gain-ink:#41A477; --hrv-gain-surface:#1C2E25;
    --hrv-loss-ink:#D47970; --hrv-loss-surface:#392523;
    --hrv-alert-text:#D99F4E; --hrv-alert-surface:#34281A;
    --hrv-action-text:#A99BF0;
    --hrv-elev-menu:0 6px 16px rgba(0,0,0,.48), 0 1px 3px rgba(0,0,0,.36);
    --hrv-series-primary:#5889C9; --hrv-grid-line:#24272C; --hrv-axis-text:#B0B7C1;
  }
}


/* ===========================================================================
   3. TYPE
   ===========================================================================
   TWO TIERS (D-WAD-005). comfortable = social/guest. compact = tools.
   `label` and `data` are PINNED ACROSS BOTH and the density axis operates on
   prose rungs and spacing only. What PUTS them where they are is the APCA
   readability floor of D-WAD-013, bound as a ladder by D-WAD-016 c1 and landed
   2026-09-22 -- not the touch and phone floors below, which no longer select
   either rung.

   WHY data IS 16 -- D-WAD-016 c1, forced by the readability floor, landed
   2026-09-22 (M01-2). The two constraints recorded below were what selected
   14 before that, and BOTH ARE STILL SATISFIED at 16: the row floor is met
   EXACTLY as before -- the LINE did not move, only the size, so a 16/20 line
   sits in the 44 px row on the same 24 px of padding 14/20 used, with no
   spare created and none lost -- and the phone angular floor rises with the
   glyph. They are kept because they are the reason the rung may never go
   BACK DOWN, not because they still choose it.
   SUPERSEDED AS A SELECTOR, 2026-09-22, retained as a floor:
   (a) TOUCH FLOOR, binding. Rows must clear 44 px. One line at 14/20 with
       12 px vertical padding measures EXACTLY 44. 13/18 gives 42, 12/16
       gives 40 -- both need invented padding and buy NO density, because the
       ROW costs the vertical space, not the glyph.
   (b) PHONE FLOOR, permissive. Measured on the right glyph -- lining figures
       at 0.744 em, not x-height (nobody reads an x-height in a price cell) --
       14 px yielded 0.282-0.300 deg on the phone band. CPS 0.20 deg governs
       FLUENT PROSE, not scanning, so this floor alone would allow 11 or 12.

   WHY body IS 18 comfortable / 16 compact -- D-WAD-016 c1, landed 2026-09-22.
   The angular measurement below is why it is NOT LOWER; what set it at 18 is
   the readability floor plus the desktop reading distance this very trigger
   named.
   SUPERSEDED AS A SELECTOR, 2026-09-22, retained as the floor it establishes:
   angular x-height against Legge & Bigelow CPS 0.20 deg -- 14 px holds to
   32-34 cm, 16 px to 37-39 cm. Phone reading distance clusters 30-40 cm and
   its centre falls BETWEEN them.
   REFACTOR TRIGGER -- **FIRED AND DISCHARGED 2026-09-19 by D-WAD-016, landed
   2026-09-22.** It read: "this discriminates 14 from 16 and NOT 16 from 18.
   On a laptop at ~58 cm, 16 px lands at 0.163 deg -- BELOW CPS. If sustained
   prose reads poorly on desktop, 17 or 18 is the move, not a line-height
   change." It was answered at 18, which is what comfortable `body` now
   declares. The re-measurement is in TYPE_ANGLE s3c and in LEDGER D-1 note
   (b), whose REVISIT closed at 18 on its own evidence.                      */

:root{
  --hrv-font-sans:"Public Sans",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;

  /* MONO -- system stack, deliberately not a webfont.
     Needed by the strategy expression editor and any code surface.
     RULING: the system stack resolves to SF Mono on Apple, Cascadia/Consolas
     on Windows, Roboto Mono on Android -- all designed for code, all with
     disambiguated 0/O and 1/l, all costing ZERO bytes. On a mobile-majority
     audience a webfont here would be paid for by every visitor to subsidise
     one surface most never open.
     REFACTOR TRIGGER: if cross-platform glyph-width consistency becomes
     load-bearing (a column-aligned expression editor, or diffing two
     expressions side by side), self-host one face -- JetBrains Mono or IBM
     Plex Mono -- and drop the stack. Until then this is the honest default. */
  --hrv-font-mono:ui-monospace,"SF Mono",Menlo,Consolas,"Roboto Mono",monospace;

  /* WEIGHTS -- TWO, not three.
     RULING: 400 and 600 only. 500 is dropped.
     REASON, AS WRITTEN AND NOW PARTLY SUPERSEDED: "14 px is the dominant size
     in this product (every table, every watchlist column). At 14 px the
     discrimination between 500 and 600 is unreliable, so 500 costs a font file
     and a decision point while adding no legible hierarchy."
     **14 px is no longer the dominant size of anything.** Since 2026-09-22 the
     table body rides `data` 16 and every header and watchlist label rides
     `label` 15. The RULING stands -- two weights, 400 and 600 -- but its stated
     reason is about a rung that moved, and **500-vs-600 discrimination has NOT
     been re-checked at 15/16 px.** The argument that does survive untouched is
     that two weights make the "is this emphasised" question binary, which is
     what a scanning surface wants.
     REFACTOR TRIGGER: if controls read too heavy at 600, reintroduce 500 for
     interactive chrome ONLY -- never for data. */
  --hrv-weight-regular:400;
  --hrv-weight-strong:600;

  /* comfortable tier -- social, guest, indexed surfaces */
  --hrv-label-size:15px;   --hrv-label-line:20px;
  --hrv-data-size:16px;    --hrv-data-line:20px;
  --hrv-body-size:18px;    --hrv-body-line:28px;
  --hrv-title-size:22px;   --hrv-title-line:28px;
  --hrv-section-size:28px; --hrv-section-line:36px;
  --hrv-display-size:36px; --hrv-display-line:44px;
}

[data-tier="compact"]{
  /* tools tier -- label and data unchanged; prose rungs step down */
  --hrv-body-size:16px;    --hrv-body-line:24px;
  --hrv-title-size:18px;   --hrv-title-line:24px;
  --hrv-section-size:22px; --hrv-section-line:28px;
  --hrv-display-size:28px; --hrv-display-line:36px;
}


/* ===========================================================================
   4. SPACE, ROWS, GEOMETRY
   ===========================================================================
   SPACE -- the 2 px step is DROPPED from the compact tier.
   The kit's compact scale opens [2, 4, 6, ...]. A 2 px gap is sub-perceptual
   beside a 44 px row and cannot express separation; keeping it invites its use
   and produces collapses that read as bugs.

   ROWS -- 44 in BOTH tiers. The kit ships tableRow 24 (compact) and 32
   (comfortable); NEITHER reaches the touch floor. This is the single most
   visible difference between the WebApp and the Terminal, and it is an
   accessibility floor rather than a taste choice.
   REFACTOR TRIGGER: the entire compact tier hangs off this 44. If the touch
   floor ever moves, section 3's derivation is VOID and both tiers must be
   rebuilt from scratch.                                                     */

:root{
  --hrv-space-1:4px;  --hrv-space-2:6px;  --hrv-space-3:8px;
  --hrv-space-4:12px; --hrv-space-5:16px; --hrv-space-6:20px;
  --hrv-space-7:28px; --hrv-space-8:32px; --hrv-space-9:44px; --hrv-space-10:56px;

  --hrv-row:44px;
  --hrv-cell-pad-x:12px;
  --hrv-tap-min:44px;

  --hrv-radius-sm:4px; --hrv-radius-md:6px; --hrv-radius-lg:10px;

  /* ICON -- kit family (Lucide, curated subset; DES-D026/D032), verified at
     source. Omitted from v0.1.0 by oversight; every tool surface needs it
     (sort arrows, dismiss, chart toolbar, nav). Kit values kept unchanged --
     no divergence, so no ledger row.
     --hrv-icon-stroke IS ALSO WRITTEN INTO THE MEANING MARKS' MASKS in
     sections 7 and 9 (2 here, 1.75 compact), because a custom property cannot
     reach inside a mask image (D-WAD-019). The masks do NOT follow a change
     here: set the new strokes in
     ../M09-meaning-hues-as-marks/design-evidence/make_rules.py (it hard-codes
     them) and in ../measurements/ink_census.py's STROKE, run a scratch copy of
     make_rules.py (it writes to its working directory and would overwrite the
     dated evidence), and copy its masks into sections 7 and 9. */
  --hrv-icon-sm:16px; --hrv-icon-md:20px; --hrv-icon-lg:24px; --hrv-icon-stroke:2;

  /* RETAINED ARGUED since 2026-09-25 (M09-3), pending the owner's ruling on
     I-025, the cap against the bar. M09-2's marks widened the widest table,
     W-4 was re-measured, and the floor is now 1642.70. 1680 still clears it,
     but it is no longer the derivation's output: the headroom rule below gives
     1700, and the floor alone gives 1660. Moving to either would decide part
     of I-025 first, and 1700 would widen the bar's void, which I-025 is about.
     So 1680 is HELD. That is a stated exception to W-4's "a derived output,
     never a chosen number", owned by I-025; it is not a derivation. See
     ../WIDTH_RULE.md W-4, ../divergence/LEDGER.md D-6a's M09-3 addendum and
     D-WAD-019 c8.
     HOW 1680 WAS SET, 2026-09-22 (M01-3): the 2026-09-08 derivation gave 1520
     on a table measured at label 12 / data 14. D-WAD-016's ladder moved both
     of those rungs, which fired W-4's own RE-TEST TRIGGER, and the
     re-measurement REFUTED 1520 exactly as the first one refuted 1360. On
     that floor, 1626.70, the headroom rule below gave 1672.97 (computed then
     from the unrounded widths, 0.01 under the rounded 2.80 x 16.53 form used
     below), and 1680 was that rounded up to the next 20px step (ledger D-6a).
     THE DERIVATION, re-measured 2026-09-25 at the landed marks:
       social = 720 col + 32 gap + 300 aside + 32 gutters      = 1084
       tool   = 240 rail + 1370.70 sorted table + 32 gutters   = 1642.70
       max                                                      = 1642.70
     The table measures 1354.17 unsorted and 1370.70 with the widest-growing
     column sorted (1338.17 / 1354.70 before M09-2's marks, 1197.02 / 1210.44
     before the ladder). The marks widened Chg % by 16.00; Chg's width did not
     move. FIVE headers grow when the sort arrow is appended, where six did
     before the marks: Chg %'s cells now outgrow its sorted header. So the
     SORTED figure is the honest one -- sizing to the unsorted width means the
     table reflows the first time a user sorts it. The same three columns still
     tie for widest-growing, at +16.53 (52w High, Mkt Cap (Cr), Sector), where
     2026-09-08 had Mkt Cap (Cr) alone at +13.42: the arrow's cost is set by the
     label RUNG, not by one header's text.
     WHICH TIER: the rig's table carries no tier, so its mark is 16px. The
     product's watchlist renders compact, where the mark is 14px and the table
     measures 2.00px narrower in place (a scratch run, 2026-09-25, record
     measurements/W4_COMPACT_SCRATCH_2026-09-25.json), so its floor is about
     1640.70. 1642.70 is the conservative bound.
     WORST SINGLE SORTED COLUMN governs, not all thirteen at once. That was the
     2026-09-08 premise (it used 1210.44, not the 1240.56 it also measured) but
     it was never written down, so it is written here: the language's own
     affordance is th[aria-sort="ascending"|"descending"], a single-column sort,
     and no surface produces thirteen simultaneously. IF MULTI-COLUMN SORT IS
     EVER SHIPPED this derivation re-opens at 240 + 1422.97 + 32 = 1694.97,
     and 1680 fails.
     RAIL-INCLUSIVE, and that is a ruling: this token governs the OUTERMOST
     content box, rail included. The token exists so the shell bar ALIGNS with
     the page beneath it; if the rail sat outside the governed box the bar
     would cap at one width while the content region spanned another, and the
     alignment the token exists for would be gone.
     THE 240 RAIL IS AN UNDECLARED INPUT. There is no --hrv-rail-w in this file;
     the only 240 is the one in this comment. The floor is sensitive to it 1:1,
     so if a rail is ever built at another width this number is wrong and
     NOTHING HERE WOULD NOTICE.
     1642.70 is the MEASURED FLOOR [C]. The 37.30px up to 1680 is 2.26
     sort-arrow widths. The headroom RULE is [I], ARGUED, not measured: it
     reproduces the 2026-09-08 headroom in the currency that headroom was
     stated in -- "the sort arrow alone moved six headers, so a single label
     change should not immediately breach". That was 37.56px against a 13.42px
     arrow, 2.80 arrow-widths. At today's 16.53px arrow the rule gives
     1642.70 + 2.80 x 16.53 = 1688.98, which the 20px step rounds to 1700; the
     floor alone rounds to 1660. So 1680's buffer is now weaker than the one
     it inherits, which is why it is RETAINED and not derived (the head of this
     comment). The 20px step is the chair's rounding and NOT a declared grid --
     no document in this area sets one.
     WHAT DOES NOT SURVIVE: the old headroom's SECOND limb, "1520 also sits in
     the band the references occupy (Facebook ~1350, Amazon ~1500)". 1680 does
     not. Those reference points are [C], measured from live sites, and this
     cap is now 180px above Amazon's. Whether a content box that wide READS well
     is a visual judgment and is the owner's, by looking -- it is not decided by
     this arithmetic and must not be presented as though it were.
     THE TOKEN HAD TO MOVE on 2026-09-22. Measured, not assumed: the only
     density lever that could have saved 1520 is the cell padding the language
     already drops below 480 (12 -> 8), worth exactly 13 x 8 = 104px. That
     landed the floor at 1522.70 -- still above 1520.
     RE-TEST TRIGGERS, four. Each re-opens this:
       1. any column added, removed or relabelled in the widest table;
       2. any change to a type rung the table reads (--hrv-data-size,
          --hrv-label-size) or to --hrv-row / --hrv-cell-pad-x;
       3. any change to what a cell of the widest table renders: a glyph, a
          mark, a prefix (added 2026-09-25 at M09-3: M09-2's marks widened
          the table, and none of the triggers then written named it);
       4. multi-column sort shipping.
     Re-measure with measurements/w4_rederive.py, do not re-estimate. */
  --hrv-shell-max:1680px;

  /* SHELL GEOMETRY -- minted 2026-09-08 (IST), forced by step 2.
     RULING: ONE bar, ONE height, EVERY breakpoint, ALWAYS sticky.
     WHY 56 -- DERIVED, not chosen: the tallest thing in the bar is a 44 px
     control (--hrv-tap-min) and it needs one space-2 of air above and below.
     44 + 12 = 56, which is exactly space-10. The touch floor sets the row in
     section 4 and it sets the bar here too; same floor, same arithmetic.
     WHY CONSTANT ACROSS BREAKPOINTS: --hrv-sticky-top must equal it. A height
     that varies by breakpoint turns the sticky contract into a per-breakpoint
     calculation, and that WILL drift -- the same failure class as the fixed
     646px measure already corrected in this file.
     REFACTOR TRIGGER: if a second sticky row is ever added ABOVE THE CONTENT
     -- a contextual symbol bar, a module chrome row -- --hrv-sticky-top stops
     being one constant and must become a sum. THAT CONTRACT NOW EXISTS
     (2026-09-27, D-WAD-020) for a module's own row stuck to the page inside
     <main>: the sticky contract's clause (4), below. The sum is declared on
     the module's own content, so --hrv-shell-h and :root's --hrv-sticky-top
     stay one constant. A row inside a .hrv-scroll is outside clause (4), and
     its rule is written before the row is; the shell itself takes no second
     row (SHELL_SPEC s6 c1).
     CORRECTED 2026-09-13 (IST): this trigger used to name "mobile bottom tabs"
     as a case. IT IS NOT ONE. SHELL_SPEC v2 s4 rules that the bottom tab bar
     is a BOTTOM INSET, not a top offset -- --hrv-sticky-top is a distance from
     the viewport's top edge and nothing stacks BENEATH the shell. The stale
     wording was the single line most likely to make whoever builds the tab bar
     rewrite a contract that does not need rewriting. Same correction carried
     to ../divergence/LEDGER.md D-4. [2026-09-27: "nothing" is nothing of the
     shell's own, the tab bar included. A module's own row may stack beneath
     the shell, under clause (4).] */
  --hrv-shell-h:56px;

  /* BOTTOM TAB BAR HEIGHT -- minted 2026-09-13 (IST) under D-WAD-004 c5,
     mint on NEED and not before. The need is now real: SHELL_SPEC v2 s4 puts
     the destinations into a bottom bar below 768, and the content region
     cannot reserve space for a bar that has no height token.
     WHY 56 -- THE SAME DERIVATION AS --hrv-shell-h, deliberately reused: the
     44 px touch floor plus one space-2 of air each side. One floor, two bars;
     if --hrv-tap-min ever moves, both move together and neither drifts apart
     from the other. It also fits the LABELLED tab that SHELL_SPEC s4 now
     requires (icons alone are forbidden): 20 icon + 2 + 20 label line = 42,
     which sits inside 56 with 7 of air above and below. (It was 38 with 9 of
     air until the ladder took `label` to 15/20 on 2026-09-22; measured at a
     true 320, 360 and 767, no label clipped or wrapped.)
     THE PAGE'S END, below 768 (2026-09-27, D-WAD-020): the ROOT reserves the
     bar's footprint -- this token PLUS the bar's 1px top edge PLUS
     env(safe-area-inset-bottom), 57px and 91 at a 34px inset. This token is
     the tabs' height; the bar renders 1px taller (backlog I-009). The rule
     and its evidence are in section 9B's 767 block. */
  --hrv-tabbar-h:56px;

  /* STICKY CONTRACT -- SETTLED 2026-09-08. Clauses (3) and (4) and the
     page's band were added 2026-09-27 (D-WAD-020, milestone M03).
     Section 10's `.hrv-table th` reads this token and shipped with a fallback
     of 0 and NO DEFINITION ANYWHERE. That dangling reference is closed here.
     THE CONTRACT, four clauses, all load-bearing:
       (1) :root carries the shell height, because the shell is ALWAYS sticky.
           There is no non-sticky shell variant, which is what makes this
           unconditional and therefore safe.
       (2) ANY element establishing its own scroll container MUST reset it to
           0 -- inside a scrolling panel the header sticks to the PANEL's top
           edge, not the viewport's. `.hrv-scroll` does both and is the only
           sanctioned way to make a scroll container.
       (3) THE LANDING. What the reader is taken to -- a fragment target, the
           skip link's <main>, a control reached with Tab or Shift+Tab, a
           scrollIntoView() -- lands clear of what is stuck above it: its
           scroll-margin-top is the band above it in its own scroller, floored
           at the page's band, plus the bar's 1px edge. A stuck element's own
           controls take none where it is stuck to the page, because a margin
           there scrolls the page when they take focus. Nor do the account
           menu's rows, which land flush in the menu's own scroll (2026-10-02,
           D-WAD-022). The rules are in
           section 9B, after .hrv-scroll, and in section 10, after the
           header's top.
       (4) A MODULE'S OWN ROW, stuck to the page inside <main>, declares these
           and nothing more:
           (a) the row: position:sticky and top:var(--hrv-sticky-top), on an
               opaque ground, with a 1px lower edge in --hrv-border-control
               (D-WAD-009's overlay-boundary floor, the shell's own edge), at
               a z-index above the table's sticky key cells (3) and below
               --hrv-z-shell;
           (b) scroll-margin-top:0 on the row's own controls, as the shell's
               controls take none;
           (c) on the content after the row, BOTH bands re-declared as the
               sum, --hrv-sticky-top and --hrv-sticky-page each
               calc(var(--hrv-shell-h) + ROW), where ROW is the row's height
               without its edge, so a header parks tucked under the edge as it
               does under the shell's.
           A row inside a .hrv-scroll is outside this clause: nothing measures
           one, and a module that needs one writes its rule then.
           specimens/FRAME_RIG.html's module is the worked example.
     THE PAGE'S BAND, --hrv-sticky-page, minted 2026-09-27 under D-WAD-004 c5
     (ledger D-4): the height stuck at the top of the PAGE -- the shell, plus
     a module's row where clause (4) re-declares it. --hrv-sticky-top is the
     band in an element's OWN scroller, and .hrv-scroll resets it to 0.
     .hrv-scroll does NOT reset --hrv-sticky-page, and that is its whole job:
     the page may still have to scroll a panel's target clear of the page's
     bars. FORCED: with the landing floored at the shell's band instead, a
     row link in a panel under a module's row showed 0 of 19px (the M03
     design probe's state Cmax, 2026-09-26). REFACTOR TRIGGER: anything else
     that sticks to the page re-declares this band too, or a panel's targets
     land under it.
     Clause (2) is the one that gets forgotten, and forgetting it is exactly
     the overlap bug this token exists to prevent.
     measurements/frame_gate.py gates clauses (3) and (4) by render, and
     clause (1)'s tuck. */
  --hrv-sticky-top:var(--hrv-shell-h);
  --hrv-sticky-page:var(--hrv-shell-h);

  /* LAYERING -- minted because the collision is ALREADY LATENT in this file.
     `.hrv-table th` is position:sticky with z-index:1. A sticky shell earlier
     in the DOM carrying an auto z-index paints UNDERNEATH it -- of two
     positioned elements the later one wins -- so the table header would slide
     over the shell on every scroll. The shell must outrank the table header;
     menus must outrank the shell. TWO RUNGS, deliberately no more. */
  --hrv-z-shell:100;
  --hrv-z-menu:200;
  /* MEASURE. RULED 2026-09-08 on CC measurement. See ../WIDTH_RULE.md W-1.
     ================= READ THIS BEFORE TOUCHING THESE =================
     `ch` IS NOT CHARACTERS. 1ch is the advance of the "0" glyph = 0.612em.
     The MEAN PROSE ADVANCE in this face is 0.458em. RATIO 1.33.
     So characters-per-line is roughly ch x 1.33, NOT ch.
     THE OLD VALUES WERE A WCAG BREACH, MEASURED, NOT THEORISED:
     66ch rendered a worst line of **89 CHARACTERS** against SC 1.4.8's
     ceiling of 80. The comment that used to sit here claimed 66ch was the
     66-character measure. It was not. Same 89 at the compact tier, because
     `ch` scales with body size, so no tier setting rescued it.
     WHY 46 AND NOT 48 OR 58: 58ch holds 80 on the five sampled corpora with
     ZERO margin, and same-register prose built from narrow glyphs needs
     46-53ch to stay under 80. 46ch is the value that holds on the WORST
     observed corpus. Typical prose lands ~61-63 characters -- inside the
     50-75 band the literature reports, and near its ~66 optimum.
     WCAG 80 is a CEILING, not a target, and our readers skew L2-English,
     whose perceptual span is smaller. Both point to the conservative end.
     RE-TEST TRIGGER, and it is real: these numbers are CORPUS-DEPENDENT.
     When actual product copy exists, RE-COUNT against it. Do not assume
     these survive a change of register.
     NEVER set a NUMERIC column width in `ch`: tabular figures measure ~14%
     WIDER than 1ch in this face, so `ch` under-estimates there. Second trap,
     opposite direction, same root cause. */
  --hrv-measure-prose:46ch;
  /* ABSOLUTE CEILING -- 80 characters on the NARROWEST observed corpus.
     DO NOT EXCEED. Currently unused; if you reach for it, re-count first. */
  --hrv-measure-wide:53ch;

  --hrv-dur-0:0ms; --hrv-dur-1:120ms; --hrv-dur-2:200ms;
  --hrv-ease:cubic-bezier(0.2,0,0,1);
}

[data-tier="compact"]{
  --hrv-space-7:24px; --hrv-space-8:32px; --hrv-space-9:40px; --hrv-space-10:48px;
  --hrv-icon-sm:14px; --hrv-icon-md:16px; --hrv-icon-lg:20px; --hrv-icon-stroke:1.75;
}

/* HONEST CORRECTION, self-review 2026-09-03. Elsewhere this seat wrote that
   "with label and data pinned, spacing carries half the density axis." The
   values above do not bear that out: compact and comfortable differ at only
   THREE of ten steps (s7, s9, s10) and are identical at s1-s6 and s8. So in
   practice the two tiers differ almost entirely in the PROSE TYPE RUNGS, and
   the spacing axis is close to inert.
   This is left as-is rather than invented into a wider spread, because a real
   spacing derivation needs a rendered tool surface to judge against and none
   exists yet. The claim was wrong; the values are merely unargued. Do not
   quote the "half the density axis" line until this row has been derived. */


/* ===========================================================================
   5. BASE
   =========================================================================== */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--hrv-surface-base);
  color:var(--hrv-ink-primary);
  font-family:var(--hrv-font-sans);
  font-size:var(--hrv-body-size);
  line-height:var(--hrv-body-line);
  font-weight:var(--hrv-weight-regular);
  -webkit-font-smoothing:antialiased;
}
:focus-visible{outline:2px solid var(--hrv-focus-ring);outline-offset:2px;border-radius:var(--hrv-radius-sm)}
:focus:not(:focus-visible){outline:none}
/* LINKS read in the primary ink and carry the action hue on their UNDERLINE
   (D-WAD-019). The action ink cannot read as text in dark (Lc 60.08 at most
   against 90) and one rule serves both themes. The underline is the link's
   non-text mark: 6.95:1 at least, against 3:1. A link that sets its own ink and
   text-decoration:none (nav, tab bar, brand, suggestion rows; since 2026-10-02
   the account menu's rows and a link styled as a plain, primary or icon
   button, D-WAD-022) is unchanged; the skip link keeps its own ink and gains
   this underline. */
a{color:var(--hrv-ink-primary);text-decoration-line:underline;
  text-decoration-color:var(--hrv-action-text);text-decoration-thickness:.125em;text-underline-offset:.2em}
img,svg{display:block;max-width:100%}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;
  }
}


/* ===========================================================================
   6. TYPE PRIMITIVES
   =========================================================================== */
.hrv-label{font-size:var(--hrv-label-size);line-height:var(--hrv-label-line);
  font-weight:var(--hrv-weight-strong);letter-spacing:.04em;text-transform:uppercase;
  color:var(--hrv-ink-secondary)}
.hrv-data{font-size:var(--hrv-data-size);line-height:var(--hrv-data-line)}
.hrv-body{font-size:var(--hrv-body-size);line-height:var(--hrv-body-line)}
.hrv-title{font-size:var(--hrv-title-size);line-height:var(--hrv-title-line);
  font-weight:var(--hrv-weight-strong)}
.hrv-section{font-size:var(--hrv-section-size);line-height:var(--hrv-section-line);
  font-weight:var(--hrv-weight-strong)}
.hrv-display{font-size:var(--hrv-display-size);line-height:var(--hrv-display-line);
  font-weight:var(--hrv-weight-strong)}
.hrv-prose{max-width:var(--hrv-measure-prose)}
.hrv-mono{font-family:var(--hrv-font-mono);font-size:var(--hrv-data-size);
  line-height:var(--hrv-data-line)}


/* ===========================================================================
   7. NUMERALS -- load-bearing, not cosmetic
   ===========================================================================
   Public Sans defaults to PROPORTIONAL digits (advances 814-1295). Without the
   pin, every numeric column misaligns. Apply .hrv-num to ANY element holding a
   figure a user may compare against another figure.                        */
.hrv-num{font-variant-numeric:tabular-nums lining-nums;
  font-feature-settings:"tnum" 1,"lnum" 1}

/* Direction. Same size, same weight, no motion -- P2.
   P2 is a PM-RATIFIED HARD LAW (designer/PRINCIPLES.md, collision order c3:
   P1, P2 and P5 do not bend). This seat may scope it UP, never down, and does
   not. Read D-WAD-004 c2 before thinking otherwise -- the same reasoning that
   kept P7 out of this seat's reach keeps P2 out of it.

   ADJUDICATED 2026-09-13 (IST) against Johnson, Designing with the Mind in
   Mind, 3rd ed. 2021 -- full ruling at ../../decisions/D-WAD-008... c4.
   THE RULE STANDS. What the book exposed is a HOLE BESIDE IT:

   CHANGE SALIENCE. A figure that changes BECAUSE THE USER CHANGED AN INPUT
   must be recognisable AT REST as having changed. Johnson ch 8, printed
   pp. 127-130: older adults changed a trip option on a travel site and did
   not notice that the price moved with it (Finn and Johnson, 2013). This seat
   has been reading "no motion on gain/loss" as though it covered all value
   change. It does not, and the gap was unaddressed anywhere in the language.

   THE DISCRIMINATOR IS WHY THE VALUE CHANGED, NEVER WHAT THE VALUE IS:
     user-caused, discrete, consequential  -> salience is OWED
     ambient re-render (the market ticked) -> no salience. P2 forbids it on
       gain/loss, and frequent motion habituates anyway (ch 1, printed
       pp. 4-5; ch 5, printed p. 71) -- it spends the channel that exists for
       change which actually matters.

   P2 forbids a MEANS (animation) on a CLASS OF VALUE (gain/loss). It does not
   forbid salience. So the means here are NON-MOTION and NON-WEIGHT: a
   persistent changed-state marker, the causing input shown beside the figure,
   or the prior value rendered alongside the new one.

   NOTHING IS MINTED FOR THIS YET, deliberately. The means is a component
   decision and there is no component to hang it on -- mint on need
   (D-WAD-004 c5). The rule is recorded here so the component inherits it
   rather than rediscovering it. */
/* THE DIRECTION MARK (D-WAD-019; M09 spec R6, R13, R14). A gain or loss figure
   reads in the PRIMARY ink, and its hue rides on a glyph before it.
   WHY THE WORDS LEAVE THE HUE. In each theme one half of the calibrated pair
   cannot reach the fluent requirement as text at any lightness (light gain
   peaks at Lc 71.66, dark loss at 63.23, against 92 with the margin), and P2
   keeps the pair calibrated, so neither half moves alone. The calibrated pair
   is also ONE GREY (#686868 and #686868 in light), so for a colour-blind reader
   a coloured figure carried direction by its sign alone. The shape carries it
   now, for every reader (Johnson, ch 4, printed 54, guideline 5).
   THE GLYPH follows the kit's map (design-system ICON_STRATEGY.md, "LAW where
   the underlying color carries meaning"): a figure is a DELTA, so
   arrow-up-right / arrow-down-right; the compact tier takes PriceDelta's
   compact arrows, arrow-up / arrow-down. That reading of the map is ARGUED,
   recorded in D-WAD-019 and put to the desk.
   SIZE AND STROKE are the kit's inline icon token, unchanged: --hrv-icon-sm
   (16 comfortable, 14 compact) and stroke 2 / 1.75. The masks carry the stroke
   as a NUMBER because a custom property cannot reach inside a mask image;
   section 4 says so beside the token, with the procedure for a stroke change.
   The masks are copied from the output of
   ../M09-meaning-hues-as-marks/design-evidence/make_rules.py; do not hand-edit.
   THE MECHANISM. A ::before masked by the Lucide path and filled by
   background-color in the meaning ink. The mask's stroke is currentColor: a
   mask reads alpha only, so no colour value is written below section 2. The
   mark is decorative to assistive technology: a screen reader hears direction
   from the figure's SIGN, which the component must print (Flagged concern 4).
   P2 HOLDS: up and down mirror (same box, offset and stroke per tier), the
   calibrated pair is untouched, and nothing animates. .hrv-flat takes no mark:
   it has no direction. The forced-colours rule for every mark sits in section
   9, after the chip marks it must also override.

   LICENCES, verbatim from lucide-react 1.21.0's LICENSE. They cover every glyph
   mask in this section and in section 9: arrow-down, arrow-down-right,
   arrow-up, arrow-up-right, trending-down, trending-up, triangle-alert.

ISC License

Copyright (c) 2026 Lucide Icons and Contributors

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Feather-derived, per the package LICENSE: arrow-down, arrow-down-right, arrow-up, arrow-up-right, triangle-alert (which the LICENSE lists as alert-triangle).

The MIT License (MIT) (for the icons listed above)

Copyright (c) 2013-present Cole Bemis

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
.hrv-up,.hrv-down{color:var(--hrv-ink-primary)}
.hrv-up::before,.hrv-down::before{
  content:"";display:inline-block;flex:none;width:var(--hrv-icon-sm);height:var(--hrv-icon-sm);
  vertical-align:-.125em;-webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.hrv-up::before{background-color:var(--hrv-gain-ink);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 7h10v10'/%3E%3Cpath d='M7 17 17 7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 7h10v10'/%3E%3Cpath d='M7 17 17 7'/%3E%3C/svg%3E")}
.hrv-down::before{background-color:var(--hrv-loss-ink);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 7 10 10'/%3E%3Cpath d='M17 7v10H7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 7 10 10'/%3E%3Cpath d='M17 7v10H7'/%3E%3C/svg%3E")}
[data-tier="compact"] .hrv-up::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E")}
[data-tier="compact"] .hrv-down::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E")}
.hrv-flat{color:var(--hrv-ink-primary)}

/* VALUE ABSENCE -- two different facts, and they must not be conflated.

   .hrv-stale  a value that EXISTS and is OLD. Renders in ink-stale and MUST
               be accompanied by its reason (P6): "halted 14:52", "last filed
               30 Jun". Staleness without a reason is a defect.
   .hrv-na     a value that DOES NOT EXIST and never will (debt/equity for a
               bank). Renders as an em dash.

   RULING: the two are separated by GLYPH, not by ink. Since 2026-09-24
   .hrv-na renders in ink-primary (D-WAD-018 c3), so their inks now differ,
   but colour must still not be asked to carry this. A dash is not a number;
   a greyed number still is one. The form does the work.                    */
.hrv-stale{color:var(--hrv-ink-stale)}
.hrv-na{color:var(--hrv-ink-primary)}
.hrv-na::before{content:"\2014"}


/* ===========================================================================
   8. SURFACE PRIMITIVES
   ===========================================================================
   Separation is SPACE plus a faint surface shift -- NOT rules. The Terminal
   draws borders because every pixel is spoken for; a browser tool does not
   have to, and this single choice does most of the work of not looking like a
   terminal on the web. Vertical rules inside tables are forbidden.         */
.hrv-card{background:var(--hrv-surface-raised);border:1px solid var(--hrv-border-subtle);
  border-radius:var(--hrv-radius-lg);padding:var(--hrv-space-5)}
.hrv-card--flush{padding:0;overflow:hidden}
.hrv-shell{background:var(--hrv-surface-raised);
  border-bottom:1px solid var(--hrv-border-control)}
/* border-control, NOT border-subtle, and this is D-WAD-009's overlay-boundary
   floor. surface-raised and surface-base are 1.04:1 apart, so the bar has NO
   fill separation from the page scrolling under it and this 1px line carries
   the whole boundary. border-subtle measured 1.20/1.26; border-control gives
   3.28/3.38. No shadow here, so composited equals declared -- render-confirmed
   2026-09-13. The name is a known stretch: it says control, this is an edge. */
.hrv-container{max-width:var(--hrv-shell-max);margin:0 auto;padding:0 var(--hrv-space-5)}


/* ===========================================================================
   9. CONTROLS -- every interactive target clears the touch floor
   =========================================================================== */
/* A LINK STYLED AS A BUTTON RENDERS AS THE BUTTON (2026-10-02, M07-2,
   D-WAD-022; backlog I-056). A control that goes to another page is a link,
   and section 5's bare `a` rule underlines every link, so an
   <a class="hrv-btn"> -- the guest's Sign in and Create account among them --
   took a .125em underline its <button> twin did not. text-decoration:none
   here, and on .hrv-icon-btn in section 9B, takes it off. FORCED: rendered
   side by side, the <a> and the <button> then differ in nothing, box or
   painted fonts (measurements/shell_gate.py G10). THE QUIET BUTTON KEEPS
   ITS UNDERLINE, which is its form (D-WAD-019): .hrv-btn--quiet sets it
   again below, at the same specificity, and wins by coming later.
   REFACTOR TRIGGER: .hrv-btn--quiet must stay AFTER this rule. Moved above
   it, a quiet link loses its underline and nothing else changes; G10 reads
   it red. */
.hrv-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--hrv-space-3);
  min-height:var(--hrv-tap-min);padding:0 var(--hrv-space-5);
  font-family:inherit;font-size:var(--hrv-data-size);line-height:var(--hrv-data-line);
  font-weight:var(--hrv-weight-strong);
  border:1px solid var(--hrv-border-control);border-radius:var(--hrv-radius-md);
  background:var(--hrv-surface-raised);color:var(--hrv-ink-secondary);cursor:pointer;
  text-decoration:none;
  transition:border-color var(--hrv-dur-2) var(--hrv-ease),
             background-color var(--hrv-dur-2) var(--hrv-ease);
}
.hrv-btn:hover{border-color:var(--hrv-border-strong)}
.hrv-btn--primary{background:var(--hrv-action-primary);border-color:var(--hrv-action-primary);
  color:var(--hrv-on-action)}
/* THE QUIET BUTTON takes a link's form (D-WAD-019; M09 spec R9): the primary
   ink, and the action hue on its underline. Its words in the action ink read
   52.52-53.56 in dark at 16/600, against 70. It is the quiet actions in
   content. The bar carries no quiet button (D-WAD-021 c1): Sign in is
   .hrv-btn and unchanged, and Help is not shell anatomy. This rule stays
   after .hrv-btn, whose text-decoration:none it overrides at the same
   specificity (2026-10-02, D-WAD-022). */
.hrv-btn--quiet{border-color:transparent;background:transparent;color:var(--hrv-ink-primary);
  text-decoration-line:underline;text-decoration-color:var(--hrv-action-text);
  text-decoration-thickness:.125em;text-underline-offset:.2em}
/* --compact is for DENSE CHROME ONLY (segmented controls, chart toolbars)
   where the control sits inside an already-tapped region. It drops below the
   touch floor and MUST NOT be used for a primary action or a lone target. */
.hrv-btn--compact{min-height:36px;padding:0 var(--hrv-space-4);
  font-size:var(--hrv-label-size);line-height:var(--hrv-label-line)}

.hrv-field{
  min-height:var(--hrv-tap-min);padding:0 var(--hrv-space-4);width:100%;
  font-family:inherit;font-size:var(--hrv-data-size);color:var(--hrv-ink-primary);
  background:var(--hrv-surface-base);
  border:1px solid var(--hrv-border-control);border-radius:var(--hrv-radius-md);
}
/* A PLACEHOLDER NEVER STANDS IN FOR A LABEL (2026-10-02, M07-2, D-WAD-022;
   backlog I-047). A field states its purpose with a visible label. A
   placeholder never takes a label's, a hint's or an example's place: it
   vanishes when the user starts typing, not all screen readers read it out,
   and its browser default styles often do not meet SC 1.4.3 (GOV.UK Design
   System, Text input). Where one is used, it repeats the visible label and
   adds nothing; only then is it non-informative, priced non-fluent
   (D-WAD-018 c1, c5). The shell's search carries none: its glyph is its
   label (section 9B, SEARCH). The ink census's own fixture, a field whose
   placeholder repeats its aria-label with no visible label, is the case c5
   ruled informative, so it keeps its carry: priced fluent, reported and not
   gated. */
.hrv-field::placeholder{color:var(--hrv-ink-muted)}

.hrv-chip{display:inline-flex;align-items:center;gap:var(--hrv-space-2);
  padding:2px var(--hrv-space-3);border-radius:var(--hrv-radius-sm);
  font-size:var(--hrv-label-size);line-height:var(--hrv-label-line);
  font-weight:var(--hrv-weight-strong)}
/* CHIPS read in the primary ink on the tints they have always had, and the hue
   rides on a glyph (D-WAD-019; M09 spec R7, R8). The chip words in their own
   inks cannot reach 77 at 15/600 (gain 66.19 light, loss 61.02 dark, alert
   73.35 / 72.97); the tint, a large colour patch and not text, stays. The kit's
   map gives a chip, a SUMMARY, the trend lines, and alert its triangle ("never
   color-only"). Box, stroke and mechanism are section 7's; the licences are
   there. */
.hrv-chip--up{background:var(--hrv-gain-surface);color:var(--hrv-ink-primary)}
.hrv-chip--down{background:var(--hrv-loss-surface);color:var(--hrv-ink-primary)}
.hrv-chip--neutral{background:var(--hrv-surface-overlay);color:var(--hrv-ink-secondary)}
.hrv-chip--alert{background:var(--hrv-alert-surface);color:var(--hrv-ink-primary)}
.hrv-chip--up::before,.hrv-chip--down::before,.hrv-chip--alert::before{
  content:"";display:inline-block;flex:none;width:var(--hrv-icon-sm);height:var(--hrv-icon-sm);
  vertical-align:-.125em;-webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.hrv-chip--up::before{background-color:var(--hrv-gain-ink);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 7h6v6'/%3E%3Cpath d='m22 7-8.5 8.5-5-5L2 17'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 7h6v6'/%3E%3Cpath d='m22 7-8.5 8.5-5-5L2 17'/%3E%3C/svg%3E")}
.hrv-chip--down::before{background-color:var(--hrv-loss-ink);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 17h6v-6'/%3E%3Cpath d='m22 17-8.5-8.5-5 5L2 7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 17h6v-6'/%3E%3Cpath d='m22 17-8.5-8.5-5 5L2 7'/%3E%3C/svg%3E")}
.hrv-chip--alert::before{background-color:var(--hrv-alert-text);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E")}
[data-tier="compact"] .hrv-chip--up::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 7h6v6'/%3E%3Cpath d='m22 7-8.5 8.5-5-5L2 17'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 7h6v6'/%3E%3Cpath d='m22 7-8.5 8.5-5-5L2 17'/%3E%3C/svg%3E")}
[data-tier="compact"] .hrv-chip--down::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 17h6v-6'/%3E%3Cpath d='m22 17-8.5-8.5-5 5L2 7'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 17h6v-6'/%3E%3Cpath d='m22 17-8.5-8.5-5 5L2 7'/%3E%3C/svg%3E")}
[data-tier="compact"] .hrv-chip--alert::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E")}
/* FORCED COLOURS (D-WAD-019; M09 spec R12). A masked mark would take the forced
   ground colour and vanish, so every glyph mark paints the reader's own
   CanvasText. CanvasText is a SYSTEM colour the reader's palette resolves: not
   a neutral value, not a raw hex, and not a design colour missing from the
   token set, so the header rule does not reach it (the same class of keyword as
   `transparent`). THIS BLOCK MUST STAY AFTER EVERY MARK RULE IT OVERRIDES: at
   equal specificity an earlier block loses, and the census's
   FORCED_COLORS_MARKS goes red. */
@media (forced-colors:active){
  .hrv-up::before,.hrv-down::before,.hrv-chip--up::before,.hrv-chip--down::before,.hrv-chip--alert::before{forced-color-adjust:none;background-color:CanvasText}
}


/* ===========================================================================
   9B. SHELL -- the one bar every surface wears
   ===========================================================================
   Composes with `.hrv-shell` in section 8, which sets the surface and border.
   Full argument in ../SHELL_SPEC.md. The five rulings that shape everything
   below:

   1. ONE BAR, 56 px, sticky, every breakpoint. The Terminal's two-bar
      L1-utility / L2-module chrome is NOT carried over: two bars cost ~112 px
      of permanent chrome on a surface whose primary case is a phone.
   2. THE SHELL NEVER SWITCHES DENSITY TIER (D-WAD-005 s1). Therefore
      [data-tier] IS NEVER SET ON <body> OR ON ANY ANCESTOR OF THE SHELL --
      it goes on the content region, <main id="main">, or on a region inside
      it (D-WAD-020). This is the shell answering section 14 item 5: the tier
      attribute is PER-REGION, and the shell sits outside it.
      measurements/frame_gate.py proves it every run, at 1280 and 360: compact
      on <main> changes no computed property of any shell or tab bar node.
   3. GUEST GATING SITS AT THE SAVE BOUNDARY. Nothing in the shell blocks,
      covers or interrupts a read. No modal, no scrim, no interstitial.
   4. THE IDENTITY SLOT RESERVES THE GUEST WIDTH IN BOTH STATES, AT EACH
      RUNG (D-WAD-021 c4). SSR renders guest; hydration may then discover a
      session. If the slot resized on that swap, the shell would shift on
      every authenticated load -- a CLS penalty on an SEO-first surface.
      Guest is the WIDER state, so reserving it costs nothing and removes
      the shift entirely.
   5. The nav ITEM SET is a product decision, not a design one. The shell
      collapses on breakpoint, never on count, and its CAPACITY is measured
      (D-WAD-021 c6): with the rig's labels (Markets, Screener, Watchlist,
      Learn; Community and Portfolio as a fifth and sixth) it holds three
      destinations below 768, beside the account tab, and five from 768.
      One more clips a tab label below 768, overflows the bar at 768, or
      collapses the search at 1024. A longer set, or longer labels,
      re-measures it; past three below 768 it needs SHELL_SPEC B4 clause
      3's overflow, which waits on the owner naming a fourth destination
      (backlog I-078; re-pointed 2026-10-02 at M07-2: M07 drew the shell's
      last parts and left the overflow, D-WAD-022 c8).

   COLLAPSE LADDER -- REVISED 2026-09-13 (IST) TO SHELL_SPEC v2 s4.
   This block encoded v1's collapse until today and was the largest live
   inconsistency in the seat. v1 hid the WHOLE nav below 1023 behind an
   overflow panel. THE EVIDENCE RATES THAT PATTERN WORST: hiding costs most at
   desktop-class widths (39% slower; 15% on mobile), and combo -- some visible,
   the rest behind a control -- beat fully hidden 86% to 57% on mobile (NN/g,
   179 participants). v1 chose the losing pattern and then defended it.

   On the existing rungs. No new breakpoint minted:
     >=1024   brand(mark+word) | nav INLINE | search | Sign in + Create account
     768-1023 brand(MARK ONLY) | nav INLINE, still visible | search | Sign in
     <768     brand(mark)      | search, full width | BOTTOM TAB BAR, <=4 tabs
   The identity column is the guest's. A member's identity is the account
   button at both upper rungs (D-WAD-021 c3), and below 768 the member's last
   tab reads Account where the guest's reads Sign in.
   NO HAMBURGER ANYWHERE IN THE SHELL. The overflow panel is DELETED.

   Nav stays visible down to 768, paid for by dropping the WORDMARK rather than
   the nav. Below 768 the destinations move to a bottom bar -- still visible,
   in the thumb zone, the same shape at every width. The guest account
   affordance becomes the fourth tab, which is why v1's weakest ruling
   (burying "Sign in" in an overflow) disappears instead of needing a defence.

   TABS CARRY TEXT LABELS, NOT ICONS ALONE. Johnson, Designing with the Mind
   in Mind, 3rd ed. 2021, ch 9 printed pp. 147-149: the chapter's own worked
   example is symbols PLUS text, and a bare icon must hint at its meaning, be
   distinguishable from its neighbours and be used consistently. A 3-4 item
   icon-only bar on a newcomer surface meets none of that reliably.
   Ruled at ../../decisions/D-WAD-008_JOHNSON_ADJUDICATION_B4_AND_DIRECTION_INK.md
   c3.

   Search NEVER collapses to an icon. Brand and nav are both recoverable --
   the mark still links home, the tab bar still holds the destinations -- but
   search is the one control that gets an organic visitor from the symbol page
   they landed on to the next one. Arithmetic at 480: container padding 12 each
   side leaves 456; the brand takes 28 (its 44 px box gives 8 back each side,
   see BRAND) and the bar's gap 12, which leaves the field 416 px. Its text
   is 362 px wide since 2026-10-02 (M07-2, D-WAD-022): the glyph's inset is
   40, where 12 was. Below 768 the identity yields its row to the tab bar, so
   no account control sits in the bar. MEASURED: the field 2026-09-30 on the
   census page, the bar sweep's page and the frame rig alike (D-WAD-021 c1),
   its text 2026-10-02 on the shell rig.
   NOTHING HAD TO BE SACRIFICED TO KEEP IT.

   THE COST THIS LADDER CARRIES, previously recorded as zero: Johnson ch 9
   printed p. 151 -- changing a prominent visual cue needlessly can make a user
   believe they have moved to a different page, older adults especially (Finn
   and Johnson, 2013). This ladder changes two prominent cues (the wordmark at
   1024, the position of the destinations at 768) as a function of VIEWPORT
   WIDTH, which means nothing to a user. NOT a violation -- the change is
   width-forced, not extraneous -- but the exposed case is anyone who CROSSES
   a rung: tablet rotation, desktop window resize. Acceptance check: the
   SHELL_SPEC s11 check that reads "Cross 1024 and 768", cited by its string,
   which survives s11's renumbering.                                         */

.hrv-shell{position:sticky;top:0;z-index:var(--hrv-z-shell)}
.hrv-shell__bar{display:flex;align-items:center;gap:var(--hrv-space-4);
  height:var(--hrv-shell-h)}

/* Scroll containers reset the sticky contract -- clause (2) in section 4. */
.hrv-scroll{overflow:auto;--hrv-sticky-top:0px}
/* THE LANDING CONTRACT -- clause (3) in section 4; 2026-09-27, D-WAD-020.
   WHY: the browser keeps no focused or targeted element clear of a sticky or
   fixed bar. A control already inside the window counts as in view, so
   Chrome does not scroll it, even when a bar covers all of it: WCAG 2.2
   SC 2.4.11 (AA), whose failure F110 is a sticky header or footer that
   completely hides a focused element. MEASURED on the M03 design session's
   frame probe, 2026-09-26, before this rule, at every width: the skip link
   put <main>'s top at 0, under the shell; a fragment target showed 0 of
   28px; a control reached with Shift+Tab under the shell showed 0 of 44px.
   THE RULE: every element's scroll-margin-top is the band above it in its
   own scroller, floored at the page's band, plus 1px for the bar's lower
   edge. The floor lets the page scroll a PANEL's target clear of the page's
   bars, since a panel resets its own band to 0. Keyed to that band alone, a
   panel's row link under the shell showed 7 of 19px (state C); floored at
   the shell's band, one under a module's row showed 0 of 19 (state Cmax).
   FORCED.
   `*`, not a list of focusable elements: every element with an id is an
   anchor target, and a script's scrollIntoView() lands the same way. ARGUED.
   THE SHELL'S OWN CONTROLS TAKE NONE. A band over a stuck element scrolls
   the page when one of its controls takes focus, because the element cannot
   move out of the band: the root's scroll-padding-top, the first fix tried,
   moved the page by up to 844px at 767 on Shift+Tab into the shell (state
   root). MEASURED.
   NOR DO THE ACCOUNT MENU'S ROWS, in either copy, the phone's in the tab bar
   outside the shell included (2026-10-02, M07-2, D-WAD-022): .hrv-menu__item
   takes scroll-margin-top:0 and lands flush in the menu's own scroll. A
   margin taller than a short menu cannot be met: with the page's 57 px put
   back, at 320 by 180 a Tab to the second row left it at 116-160 against the
   menu's view of 64-116. MEASURED (the design session's MU36;
   measurements/shell_gate.py's C86).
   COST, ARGUED and not measured: a target in a panel lands at least the
   page's band below the panel's top, even far from any bar, and a panel
   shorter than that plus the target cannot meet the landing.
   REFACTOR TRIGGER: anything new that sticks at the top of the page
   re-declares --hrv-sticky-page (section 4's clause 4); a component that
   snaps its scrolling vertically sets its own scroll-margin-top, since this
   one becomes its snap offset; and any change here re-runs
   measurements/frame_gate.py. */
*{scroll-margin-top:calc(max(var(--hrv-sticky-top), var(--hrv-sticky-page)) + 1px)}
.hrv-shell,.hrv-shell *{scroll-margin-top:0px}

.hrv-skip{position:absolute;left:-9999px}
.hrv-skip:focus{position:fixed;left:var(--hrv-space-4);top:var(--hrv-space-4);
  z-index:var(--hrv-z-menu);background:var(--hrv-surface-raised);
  color:var(--hrv-ink-primary);padding:var(--hrv-space-3) var(--hrv-space-4);
  border:1px solid var(--hrv-border-control);border-radius:var(--hrv-radius-md)}
/* SHADOW REMOVED 2026-09-15 (IST), D-WAD-010 c2. It carried --hrv-elev-menu,
   which darkened the bar just outside its own border to #E8E8E8 and dropped the
   border from 3.42 to 2.79 -- UNDER the 3:1 floor, in the shipped file.
   Deleting the shadow removes the cause; darkening the border would have
   chased its own target, since the shadow darkens the ground too. Without it
   the border measures 3.42 light / 3.26 dark and the box is still bounded. */

/* BRAND. Uses title/data rungs, which under ruling 2 are always the
   comfortable tier's -- the shell does not shrink inside a tool route.
   A 44 px TARGET AT EVERY WIDTH (SHELL_SPEC s8 c6; D-WAD-021 c5). Below
   1024 the word is shed, and the link was the 28 px mark alone. The inline
   padding is half the touch floor less the mark, (44 - 28) / 2 = 8 px each
   side: FORCED. The equal negative margin keeps every other part of the bar
   where it was: ARGUED over min-inline-size:44px, which would take 16 px
   from the search at every rung below 1024. MEASURED: with this edit alone,
   every search width and overflow on the M06 states page equals the
   language's before it, at every rung, for both identity states and three
   to six destinations. COST: the box reaches 8 px into the container's
   padding and 8 px into the bar's 12 px gap, leaving 4 px to the next part
   of the bar, and the focus ring (2 px at a 2 px offset, section 5) fills
   them: it meets the next part (MEASURED by keyboard at eight widths, 320 to
   1280; no term varies with width), and at 480 and below the viewport's edge.
   REFACTOR TRIGGER: the mark's 28 px (.hrv-brand__mark) or --hrv-tap-min.
   The 28 in both calc()s is the mark's width and moves with it. */
.hrv-brand{display:inline-flex;align-items:center;gap:var(--hrv-space-3);
  height:var(--hrv-tap-min);flex:none;text-decoration:none;
  color:var(--hrv-ink-primary);font-size:var(--hrv-title-size);
  line-height:var(--hrv-title-line);font-weight:var(--hrv-weight-strong);
  padding-inline:calc((var(--hrv-tap-min) - 28px) / 2);
  margin-inline:calc((28px - var(--hrv-tap-min)) / 2)}
.hrv-brand__mark{width:28px;height:28px;flex:none;border-radius:var(--hrv-radius-md);
  background:var(--hrv-action-primary);color:var(--hrv-on-action);
  display:grid;place-items:center;
  font-size:var(--hrv-data-size);line-height:1}
.hrv-brand__word{white-space:nowrap}

/* NAV. The link is the full bar height so the active underline can sit on the
   bar's own bottom edge; 56 clears the 44 touch floor either way. */
.hrv-nav{display:flex;align-items:center;gap:var(--hrv-space-1);flex:none}
.hrv-nav__link{position:relative;display:inline-flex;align-items:center;
  height:var(--hrv-shell-h);padding:0 var(--hrv-space-4);
  text-decoration:none;color:var(--hrv-ink-secondary);
  font-size:var(--hrv-data-size);line-height:var(--hrv-data-line);
  font-weight:var(--hrv-weight-strong);white-space:nowrap}
.hrv-nav__link:hover{color:var(--hrv-ink-primary)}
.hrv-nav__link[aria-current="page"]{color:var(--hrv-ink-primary)}
.hrv-nav__link[aria-current="page"]::after{content:"";position:absolute;
  left:var(--hrv-space-4);right:var(--hrv-space-4);bottom:0;height:2px;
  background:var(--hrv-action-primary);border-radius:2px 2px 0 0}

/* SEARCH. Flexes into whatever the bar has left; capped so it does not sprawl
   on a wide desktop. min-width:0 is required or the flex item refuses to
   shrink below its content and the bar overflows at 768.
   CORRECTED 2026-09-13 (IST): this cap read 420px against SHELL_SPEC s3's
   stated 560. The spec's anatomy table governs and the language was wrong.
   Caught by reading the two side by side, NOT by the fold, which named only
   the collapse -- so assume the rest of this section was never reconciled
   either and check before trusting it. */
.hrv-search{position:relative;flex:1 1 auto;min-width:0;max-width:560px}
/* THE GLYPH IS THE FIELD'S VISIBLE LABEL, AND THE FIELD CARRIES NO
   PLACEHOLDER (2026-10-02, M07-2, D-WAD-022; backlog I-047). The markup:
   <svg class="hrv-search__icon" aria-hidden="true">, the curated `search`
   glyph, before the field in .hrv-search__form, which takes position:relative
   to hold it; the field keeps its aria-label ("Search a company or symbol" on
   the rig). A placeholder was the field's only visible statement of purpose,
   it vanished at the first keystroke, and in dark it read Lc 41.67 against
   the 90 it needs, which no ink reaches there but the primary, the ink typed
   text uses (D-WAD-018 c5). The glyph with the field's accessible name is a
   label in WCAG 2.2's sense ("other component with a text alternative that
   is presented to a user to identify a component"), and the magnifying glass
   is one of the few icons NN/g reports "mostly universal recognition" for.
   ARGUED.
   20 px (--hrv-icon-md, the tab icon's size), 12 px in (--hrv-space-4, the
   field's own padding), centred, in ink-secondary (the icon button's and the
   tab's resting ink): ARGUED. At 3:1 or more against the field: MEASURED,
   11.47:1 at the least. pointer-events:none, so a click on it lands on the
   field; placed out of flow, so it takes no part of the bar's layout. The
   field's text starts 8 px past it (--hrv-space-3): 40 px of inset where 12
   was. ITS COST, MEASURED: 28 px of text at every width (the bar sweep's
   narrowest text box 166.92 -> 138.92 at 768, the field unchanged).
   REFACTOR TRIGGER: --hrv-space-4, --hrv-icon-md and --hrv-space-3 place the
   glyph and set both insets, the field's and the suggestion rows' below, so a
   change to any of them moves all three; re-run measurements/shell_gate.py
   (G5, G9). */
.hrv-search__form{display:flex;align-items:center;position:relative}
.hrv-search__icon{position:absolute;inset-inline-start:var(--hrv-space-4);top:0;bottom:0;
  margin-block:auto;width:var(--hrv-icon-md);height:var(--hrv-icon-md);
  pointer-events:none;color:var(--hrv-ink-secondary)}
.hrv-search .hrv-field{padding-inline-start:calc(var(--hrv-space-4) + var(--hrv-icon-md) + var(--hrv-space-3))}

/* SUGGESTIONS. No price is shown, and that is a ruling rather than an
   omission: section 7 requires a stale value to carry its reason (P6), and a
   dropdown has nowhere to put one. A figure that cannot be qualified should
   not be rendered on a surface a newcomer will read as live. */
.hrv-suggest{position:absolute;top:calc(100% + var(--hrv-space-2));left:0;right:0;
  z-index:var(--hrv-z-menu);background:var(--hrv-surface-raised);
  border:1px solid var(--hrv-border-control);border-radius:var(--hrv-radius-lg);
/* NO box-shadow. DELIBERATE, and the measurement is why. R-9, D-WAD-012 c2.
   --hrv-elev-menu used to sit here. In LIGHT it darkened the page immediately
   outside this border to #D4D4D6, dragging border-control down to 2.31 at the
   bottom edge and 2.65 at the left -- under D-WAD-009's 3:1 overlay floor. A
   darker border cannot answer that: the shadow darkens the ground with it, so
   the fix chases its own target. Measured alternatives, 2026-09-15:
     shadow dropped      3.28 light / 3.38 dark, FLAT, existing token   <- this
     shadow at half      2.77 bottom / 2.98 left -- misses by 0.02
     fill carries it     needs a #777777-#919191 menu, darker than every
                         surface token in the system. No such token exists.
   Dropping it needs NO MINT, which is why D-WAD-009's --hrv-border-overlay was
   never minted. Same manoeuvre as .hrv-skip:focus, render-confirmed there too.
   SECTION 2'S NOTE ON --hrv-elev-menu WAS CORRECTED ON 2026-09-30 (M06-2,
   D-WAD-021 c8): it now records what D-WAD-010 c2 measured -- the dark
   shadow HELPS and the light one is what breaks the border -- and that no
   rule consumes the token. The light theme is this system's hard case. */
  max-height:calc(var(--hrv-row) * 7);overflow-y:auto}
.hrv-suggest[hidden]{display:none}
.hrv-suggest__item{display:flex;align-items:center;gap:var(--hrv-space-3);
  height:var(--hrv-row);padding:0 var(--hrv-space-4);
  text-decoration:none;color:var(--hrv-ink-primary)}
/* THE ROWS START UNDER THE TYPED TEXT (2026-10-02, M07-2, D-WAD-022). In the
   shell's search the field's text starts 40 px in, past the glyph, so a
   suggestion row takes the same inset and its code begins where the query
   does. ARGUED: the eye moves straight down from the query to what completes
   it. Without this rule the codes start 28 px left of the typed text
   (MEASURED: at 320 the code at 65, the field's text at 93; the design
   session's MU24, measurements/shell_gate.py's C97). ITS COST: every row
   gives 28 px of company name. At the stated capacity of five destinations
   the list is as narrow as its field, 117.73 px at 768 on the shell rig, so a
   row's text box is 63.73 (less the list's 1 px edges and the row's 40 and
   12 of padding): narrower than a long code such as RELIANCE, 80.25 px at
   16/600, which is then cut. The owner judged the trade on the preview (M07
   spec, Flagged concern 3).
   REFACTOR TRIGGER: this inset is .hrv-search .hrv-field's own calc above;
   change both or neither (G9.align reads them together). */
.hrv-search .hrv-suggest__item{padding-inline-start:calc(var(--hrv-space-4) + var(--hrv-icon-md) + var(--hrv-space-3))}
.hrv-suggest__item:hover,.hrv-suggest__item[aria-selected="true"]{
  background:var(--hrv-surface-overlay)}
/* R-2, 2026-09-15, D-WAD-011. THE FILL ABOVE CANNOT CARRY THE STATE ALONE.
   surface-overlay on surface-raised measures 1.11 light / 1.06 dark, OKLab dE
   .036 / .026 -- at or below one JND, on a 44px row. aria-selected is the
   state that tells a keyboard user which suggestion Enter will take, so this
   is WCAG 1.4.11: state information required to operate the component was not
   identifiable. No surface token can fix it -- overlay is the only step above
   raised and it is 1.1:1 away -- and minting a selected-surface would be a new
   token for a problem a shape already solves.
   box-shadow:inset, NOT border-left: an inset shadow paints inside the border
   box, so the rows do not shift when selection moves. 5.18 light / 3.47 dark
   as a graphic object.
   THE EDGE IS DELIBERATELY NOT GIVEN TO :hover. Hover is a pointer state; if
   both carried the edge, hovering one row while another is aria-selected would
   show two, and the cue that means "Enter takes this" would stop meaning it.
   CONSTRAINT: this selector owns the item's box-shadow. A second box-shadow
   here REPLACES this one and silently removes the indicator. */
.hrv-suggest__item[aria-selected="true"]{
  box-shadow:inset 3px 0 0 var(--hrv-action-primary)}
.hrv-suggest__code{flex:none;font-size:var(--hrv-data-size);
  font-weight:var(--hrv-weight-strong)}
.hrv-suggest__name{min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;color:var(--hrv-ink-primary);font-size:var(--hrv-data-size)}
.hrv-suggest__ex{margin-left:auto;flex:none}

/* IDENTITY. min-inline-size is ruling 4 -- the reserved guest width, PER
   RUNG (D-WAD-021 c4): 243 px from 1024, where Sign in and Create account
   render 242.55 px together, and 86 px at 768-1023 (the 1023 block), where
   Sign in alone renders 85.70. MEASURED from the copy at the landed data
   rung, 16/600. Each is rounded up to the whole pixel, ARGUED, which leaves
   margins of 0.45 and 0.30 px. It was 216 px at every rung, measured before
   the type ladder: signing in moved the bar 26.54 px at 1024 with four
   destinations, and at 768 the tablet search had 102.63 px where it now has
   232.63 (the M06 shell rig, four destinations; 102.62 and 192.92 on the bar
   sweep's page, the 1023 block). Below 768 the identity yields its row (the
   767 block).
   position:relative MAKES IT THE ACCOUNT MENU'S CONTAINING BLOCK from 768
   (2026-10-02, M07-2, D-WAD-022). The menu is placed out of flow, so it
   moves no part of the bar and the reservation stays the guest's: opening
   it moves nothing (measurements/shell_gate.py G8.still).
   REFACTOR TRIGGER: these numbers are measured, not derived. Re-measure both
   if any of these moves: the CTA copy, the data rung, a button's padding or
   edge, the identity's gap (--hrv-space-3), the font binaries, or a control
   added to either state. measurements/shell_gate.py's G4 reds a change made
   here at its next run; specimens/SHELL_RIG.html carries its own copy and
   controls, so a copy or control change is re-measured by putting it there. */
.hrv-identity{display:flex;align-items:center;gap:var(--hrv-space-3);
  margin-left:auto;flex:none;justify-content:flex-end;min-inline-size:243px;
  position:relative}
/* ICON BUTTON, and the ACCOUNT CONTROL built from it (D-WAD-021 c3). The
   member's identity is <button type="button" class="hrv-icon-btn
   hrv-icon-btn--account" aria-label="Account"> holding the 32 px .hrv-avatar,
   its initials aria-hidden: a button, because it opens the account menu, so
   it does not navigate. The menu landed on 2026-10-02 (M07-2, D-WAD-022; the
   ACCOUNT MENU below): the control carries aria-expanded (SHELL_SPEC s8 c7)
   and aria-controls naming the copy that follows it (in SHELL_SPEC from
   M07-3). The 1023 block's shed exempts it, so it renders from 768 up.
   font-family:inherit, AS .hrv-btn AND .hrv-field HAVE IT: a <button> takes
   the user agent's font, and the avatar's initials are text. Without it
   Chrome painted them in Arial; with it, in Public Sans SemiBold. FORCED.
   Every width, box and overflow is the same either way.
   text-decoration:none, AS .hrv-btn HAS IT (2026-10-02, D-WAD-022): an
   <a class="hrv-icon-btn"> takes no link underline (shell_gate.py G10).
   REFACTOR TRIGGER: anything that sets a font on .hrv-icon-btn or
   .hrv-avatar. measurements/shell_gate.py's G2.member-font reads the face
   Chrome painted, not the computed family. */
.hrv-icon-btn{display:inline-flex;align-items:center;justify-content:center;
  width:var(--hrv-tap-min);height:var(--hrv-tap-min);flex:none;
  border:1px solid transparent;border-radius:var(--hrv-radius-md);
  background:transparent;color:var(--hrv-ink-secondary);cursor:pointer;font-family:inherit;
  text-decoration:none}
.hrv-icon-btn:hover{background:var(--hrv-surface-overlay);color:var(--hrv-ink-primary)}
.hrv-avatar{width:32px;height:32px;border-radius:50%;flex:none;
  background:var(--hrv-action-primary);color:var(--hrv-on-action);
  display:grid;place-items:center;
  font-size:var(--hrv-label-size);font-weight:var(--hrv-weight-strong)}
/* THE ACCOUNT MENU (2026-10-02, M07-2, D-WAD-022; M06's hand-off, D-WAD-021
   c3 and c8). The member's account control opens it from 768, and the tab
   bar's Account tab below 768. It is drawn twice, as the destinations are:
   one copy is the identity's last child, after the control, the other the
   tab bar's last child, after the tab; each carries its own id, and each
   control's aria-controls names its own copy. Its rows are <a href>s, and an
   action such as Sign out is a <button> in a <form>. Which rows it carries
   is the product's; the rig's are stand-ins, and only Sign out is forced by
   what the menu is for. No row takes a hue (P1): Sign out is not red.
   A DISCLOSURE, NOT role="menu" AND NOT A DIALOG. Ruling 3 holds: the
   visitor opens it, it covers only what lies under it while open, blocks
   nothing and puts no layer behind it, and Escape, a pointer press outside,
   focus leaving it or a second press closes it. That behaviour is the
   platform's script, which SHELL_SPEC s5 and s8 state from M07-3; this file
   owns its form, open and closed.
   IT HANGS 6 px UNDER THE CONTROL from 768, its end edge on the control's,
   and RISES 6 px above the tab bar's visible edge below 768 (the 767
   block): --hrv-space-2, the suggestion list's own gap, so the two menus
   hang alike. ARGUED.
   ITS HEIGHT IS BOUNDED by the viewport and its rows scroll inside it, the
   suggestion list's form. FORCED by SC 1.4.10 and WIDTH_RULE W-2's 400%
   test, 320 by 180 CSS px: unbounded, at 1280 by 180 it ran from 56 to 218
   px where it may fill 56 to 174, inside a bar no scroll reaches (the
   design session's MU21). 100vh first, for a browser without
   dvh. Its width is its longest row's, capped at the viewport less 24 px
   (ARGUED).
   ITS EDGE IS AN OVERLAY BOUNDARY (D-WAD-009): 1 px border-control, opaque,
   no shadow (section 2's elevation note), at 3.283 / 3.425 / 3.084 against
   base, raised and overlay in light and 3.381 / 3.263 / 3.07 in dark, the
   shell's own edge. FORCED. It sits at --hrv-z-menu inside the shell's or
   the tab bar's stacking order, which outranks the page: with the shell at
   z-index 0 a page-stuck table header painted over it at 768 (MU09).
   MEASURED.
   ITS ROWS are full width and at least 44 px tall (the touch floor;
   FORCED), the data rung at 400 in ink-primary: the rung ARGUED, as the
   suggestion name's; the ink MEASURED, Lc 97.52 and 15.22:1 at the least
   against the 92 they need, where ink-muted read 74.59 (MU07). 6 px above
   and below and 12 px at each side make a one-line row the floor and keep
   air round a wrapped one. The panel's 8 px of padding keeps a highlighted
   row's square corner within 0.06 px of the panel's 9 px inner curve at
   rest; scrolled, a row's corner passes it by up to 3.73 px, and the panel's
   clip trims both. A row's hover is advisory, overlay on raised at 1.11 in
   light and 1.06 in dark (backlog I-074); the keyboard state is the ring.
   A ROW'S RING IS DRAWN 2 px INSIDE IT (outline-offset:-4px). At the row's
   edge it would meet the menu's border-control edge at 1.51:1 in light and
   1.06:1 in dark, and drawn outward it crossed the menu's edge. ARGUED, on
   the design skill's 3:1 against adjacent colours.
   SIGN OUT IS SET APART BY SPACE, NOT A RULE. .hrv-menu__sep is an <hr>, 0
   tall and borderless, with 6 px above and below, so the groups stand 12 px
   apart where rows touch; the <hr> keeps the separator's meaning for
   assistive technology. ARGUED, on Johnson ch 2, Proximity (printed 16-17),
   and section 8: separation is space, not rules.
   ITS ROWS TAKE NO LANDING MARGIN: the landing comment after .hrv-scroll.
   REFACTOR TRIGGER: measurements/shell_gate.py's G7 and G8 read all of this
   by render -- the sweep at every width, the keyboard walk, the paint and
   the painted fonts at ten widths, the short screens at eight viewports --
   and the ink census prices the rows' words; a new kind of row, a theme
   row, or a menu anywhere else re-runs both. */
.hrv-menu{position:absolute;top:calc(100% + var(--hrv-space-2));inset-inline-end:0;
  z-index:var(--hrv-z-menu);inline-size:max-content;
  max-inline-size:calc(100vw - 2 * var(--hrv-space-4));
  max-block-size:calc(100vh - var(--hrv-shell-h) - var(--hrv-space-2));
  max-block-size:calc(100dvh - var(--hrv-shell-h) - var(--hrv-space-2));
  overflow-y:auto;padding:var(--hrv-space-3) 0;background:var(--hrv-surface-raised);
  border:1px solid var(--hrv-border-control);border-radius:var(--hrv-radius-lg)}
.hrv-menu[hidden]{display:none}
.hrv-menu__item{display:flex;align-items:center;inline-size:100%;
  min-block-size:var(--hrv-row);margin:0;padding:var(--hrv-space-2) var(--hrv-space-4);
  border:0;background:transparent;cursor:pointer;text-align:start;text-decoration:none;
  font-family:inherit;font-size:var(--hrv-data-size);line-height:var(--hrv-data-line);
  font-weight:var(--hrv-weight-regular);color:var(--hrv-ink-primary);scroll-margin-top:0}
.hrv-menu__item:hover{background:var(--hrv-surface-overlay)}
.hrv-menu__item:focus-visible{outline-offset:-4px}
.hrv-menu__sep{block-size:0;margin:var(--hrv-space-2) 0;border:0}

/* BOTTOM TAB BAR. Below 768 only. IT REPLACES v1'S OVERFLOW PANEL, and that
   panel is DELETED rather than hidden -- .hrv-shell__overflow, .hrv-panel,
   .hrv-panel__link and .hrv-panel__actions are gone from this file, so nothing
   can be built from them by mistake. If you are looking for them because a
   specimen references them, the specimen is the thing that is wrong. A-07,
   which does, is not on disk since the 2026-09-22 conversion
   (reference/ARTIFACTS.md; backlog I-042). The 2026-09-08 preview, by its
   own header, renders v2's collapse and is stale on values only (backlog
   I-007). Both are the specimen milestone's (M05).

   IT DOES NOT TOUCH --hrv-sticky-top. That token is a distance from the
   viewport's TOP edge; the tab bar is a BOTTOM inset and nothing stacks
   beneath the shell [2026-09-27: nothing of the shell's own; a module's own
   row may, under section 4's sticky contract, clause (4)]. See the
   corrected refactor trigger in section 4.

   Links are real <a> in the DOM AT EVERY WIDTH, toggled with [hidden] and
   never conditionally rendered: a crawler and a screen reader should both find
   the navigation without executing an interaction. Ruling 3 holds here too --
   the bar covers nothing, blocks nothing and interrupts no read.

   ICON PLUS TEXT, never icon alone -- Johnson ch 9 printed pp. 147-149,
   D-WAD-008 c3. The 44 px touch floor is met by the tab's full height.

   THE MEMBER'S LAST TAB IS A <button type="button"> reading Account
   (2026-10-02, M07-2, D-WAD-022): it opens the account menu, whose phone
   copy follows it as the bar's last child, so it does not navigate; the
   destinations stay real <a>s. Its icon slot holds the curated `user`
   glyph, never the avatar's initials, which outgrow the 20 px slot at the
   label rung ("WM" 27.97 px; FORCED). So .hrv-tabbar__link resets what a
   <button> brings -- margin, padding, border, background -- and takes the
   page's family: a <button> takes the user agent's font, and the tab's label
   painted in Arial and clipped 26 px at 320 (the design session's probe).
   FORCED. */
.hrv-tabbar{display:none}
.hrv-tabbar__link{flex:1 1 0;min-width:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:2px;
  height:var(--hrv-tabbar-h);text-decoration:none;
  color:var(--hrv-ink-secondary);margin:0;padding:0;border:0;background:transparent;
  font-family:inherit;cursor:pointer}
.hrv-tabbar__icon{width:20px;height:20px;flex:none}
.hrv-tabbar__label{max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:var(--hrv-label-size);
  line-height:var(--hrv-label-line);font-weight:var(--hrv-weight-strong)}
.hrv-tabbar__link[aria-current="page"]{color:var(--hrv-ink-primary)}
/* THE ICON TAKES NO COLOUR OF ITS OWN. It inherits ink-primary from the link
   above, which is the whole point: one state, one ink.
   A rule here previously set it to action-primary -- the static indigo used as
   INK, at Lc -25.4 in dark, the weakest value measured anywhere in the shell.
   R-1 (D-WAD-011) overrode it inside the 767 block and the render confirmed
   16.18 dark, so the OUTPUT was right. The rule itself survived as dead code,
   DEAD ONLY BY SOURCE ORDER against an equal-specificity override -- reorder
   the file and the defect returns silently. Deleted 2026-09-15, D-WAD-012 c1.
   NOW the file has no INK use of the static indigo anywhere. That claim was
   made on 09-15 before it was true; it is true as of this edit. */

/* 768-1023: THE NAV STAYS INLINE. The wordmark pays instead. B4's evidence
   says hiding costs most at exactly these widths, so the nav is the last
   thing to go, not the first. v1 had this backwards. */
@media (max-width:1023px){
  .hrv-brand__word{display:none}
  .hrv-identity{min-inline-size:86px}

  /* R-7, 2026-09-15, D-WAD-011. SHELL_SPEC s4 has said since v2 that identity
     drops to "Sign in OR avatar" at this rung. THIS BLOCK NEVER IMPLEMENTED
     IT -- for five days the only rule here was the wordmark, so the bar
     carried its full desktop identity cluster at tablet width, the search
     field absorbed the entire deficit and RENDERED 26px WIDE, and the bar
     still overflowed by 22px. Found by rendering; reading had passed it.
     A 26px search field breaches this file's own "search NEVER collapses to
     an icon" rule by being NARROWER than an icon while still nominally
     present, which is worse than the outcome the rule forbids.
     WHAT SHEDS, INSIDE .hrv-identity ONLY (2026-09-30, D-WAD-021 c2): the
     CTA, and on the measuring pages Help and the notification button,
     which are not shell anatomy. The rule R-7 wrote carried no shell scope,
     so it also hid every primary, quiet and icon button in the page, at
     every width below 1024 (backlog I-049). FORCED: the identity sheds; the
     page does not.
     WHAT STAYS: Sign in (.hrv-btn) AND the account control
     (.hrv-icon-btn--account, holding .hrv-avatar). Those are the two
     identity STATES, not two controls -- signed-out shows one, signed-in the
     other. The unscoped rule hid the account control too, drawn with the
     language's one 44 px icon button, so a signed-in visitor had no account
     affordance at tablet width until 2026-09-30. B5 reserves the guest width
     in BOTH states; it does not license deleting the signed-in one.
     RE-MEASURED 2026-09-30 at the per-rung reservation, 86 px here: 256
     integer widths from 768 to 1023, both themes, maxBarOverflowPx 0 and
     maxDocOverflowPx 0, narrowest .hrv-search 192.92 px at 768 on the bar
     sweep's page, its text box 166.92 (102.62 on 2026-09-22, under the
     216 px reservation). SHELL_SPEC s11's check that reads "At every width
     from 768 to 1023 the bar does not overflow" is discharged, and
     measurements/shell_gate.py gates the anatomy's bar at every width.
     Record: measurements/M06-2_BAR_SWEEP_LANDED_2026-09-30.json
     [2026-10-02, M07-2: the field is unchanged at each of the 256 widths in
     both themes, and its text box reads 138.92 at 768 since M07, the
     glyph's inset 40 px where 12 was (D-WAD-022).] */
  .hrv-identity .hrv-btn--primary,
  .hrv-identity .hrv-btn--quiet,
  .hrv-identity .hrv-icon-btn:not(.hrv-icon-btn--account){display:none}
}

/* <768: destinations move to the bottom bar. The top bar keeps brand and
   search; identity becomes the FOURTH TAB, so .hrv-identity yields its row in
   the bar without the affordance being lost -- which is the whole reason no
   ruling is needed to defend it. */
@media (max-width:767px){
  .hrv-nav{display:none}
  .hrv-identity{display:none}
  .hrv-search{max-width:none}
  .hrv-tabbar{position:fixed;left:0;right:0;bottom:0;display:flex;
    z-index:var(--hrv-z-shell);background:var(--hrv-surface-raised);
    border-top:1px solid var(--hrv-border-control);
    padding-bottom:env(safe-area-inset-bottom)}
  /* THE PAGE'S END AND THE BOTTOM LANDING -- 2026-09-27, D-WAD-020. The ROOT
     reserves the bar's footprint: its tabs (--hrv-tabbar-h), its 1px top
     edge and the bottom inset its own padding reads. As padding-bottom, the
     page's last line can be scrolled clear of the bar; as
     scroll-padding-bottom, a control reached with Tab lands above it. That
     is WCAG technique C43's own form, which pads html.
     ON THE ROOT, NOT ON <main>: a page footer (the theme control lives in
     one, SHELL_SPEC s5) follows <main> as its own landmark, and a reserve on
     <main> would leave the footer under the bar.
     FORCED: the bar renders 57px, and 91 at a 34px inset; backlog I-009's
     1px is its edge, inside the 57. Before this rule, at 320, 360 and 767
     and at both insets, the last line showed 0 of 28px scrolled to the end,
     and a control reached with Tab under the bar 0 of 44px. The bar is
     position:fixed, so this band scrolls nothing when its own links take
     focus: MEASURED, 0px.
     No `screen` qualifier, like the bar's own rule, so a narrow printed page
     may end in 57px of blank; print is backlog I-057's.
     REFACTOR TRIGGER: if the bar's height, its edge or its inset term moves,
     move this sum with it. measurements/frame_gate.py gates the reserve
     against the bar's rendered height at both insets. */
  html{padding-bottom:calc(var(--hrv-tabbar-h) + 1px + env(safe-area-inset-bottom, 0px));
    scroll-padding-bottom:calc(var(--hrv-tabbar-h) + 1px + env(safe-area-inset-bottom, 0px))}

  /* R-1, 2026-09-15, D-WAD-011. THE CURRENT TAB TAKES THE NAV'S GRAMMAR.
     Until today this state was carried by ink promotions alone -- label to
     ink-primary (dE .117 dark), icon to action-primary (dE .342). Both need a
     COMPARISON ACROSS THE BAR to read, on the surface with the least screen
     and the most hurry, and both are colour, so both degrade under CVD.
     This is a PRESENT/ABSENT SHAPE, mirroring .hrv-nav__link[aria-current]
     ::after on the desktop bar: one state, one signal shape, both surfaces.
     It is immune to the CVD collapse by construction -- there is no pair to
     tell apart, only presence or absence.
     The icon then follows the label to ink-primary, which removes the LAST
     use of the static indigo as INK anywhere in this file (3.47 -> 16.18 in
     dark) and leaves action-primary doing only fills and graphic marks, which
     is what the desk ruled it is for. */
  .hrv-tabbar__link{position:relative}
  .hrv-tabbar__link[aria-current]::before{
    content:"";position:absolute;top:0;left:50%;transform:translateX(-50%);
    width:32px;height:2px;background:var(--hrv-action-primary)}
  .hrv-tabbar__link[aria-current] .hrv-tabbar__icon{color:var(--hrv-ink-primary)}
  /* THE ACCOUNT MENU RISES FROM THE TAB BAR (2026-10-02, D-WAD-022): its
     bottom edge 6 px above the bar's visible 1 px edge, its end edge on the
     page's content edge (16 px in; 12 at 480 and below, section 13), its
     height the viewport less both bars, their two 1 px edges, the bottom
     inset and both gaps. The bar, position:fixed, is its containing block,
     so focus in the menu scrolls nothing. */
  .hrv-tabbar .hrv-menu{top:auto;bottom:calc(100% + 1px + var(--hrv-space-2));
    inset-inline-end:var(--hrv-space-5);
    max-block-size:calc(100vh - var(--hrv-shell-h) - var(--hrv-tabbar-h) - 2px - env(safe-area-inset-bottom, 0px) - 2 * var(--hrv-space-2));
    max-block-size:calc(100dvh - var(--hrv-shell-h) - var(--hrv-tabbar-h) - 2px - env(safe-area-inset-bottom, 0px) - 2 * var(--hrv-space-2))}
}

/* THE CONTENT REGION is <main id="main"> (2026-09-27, D-WAD-020): the page's
   one main landmark, the skip link's target (SHELL_SPEC s8) and the tier's
   home (ruling 2). It takes NO CLASS, because no declaration needs one, and
   a class nothing reads would be a name with nothing behind it (D-WAD-004
   c5). Each rule that waited for it sits on the element CSS attaches it to:
   the bottom reserve on the root, in the 767 block above; the landing
   margins on whatever can be scrolled to, after .hrv-scroll above and in
   section 10; a module's sums on its own content (section 4's sticky
   contract, clause 4). Section 14 item 7, which asked for this, is closed.
   A hook the build needs -- a focus rule for <main tabindex="-1">, say -- is
   minted then, on need. */


/* ===========================================================================
   10. DATA TABLE -- the primitive the watchlist, screener and trade log share
   =========================================================================== */
/* WIDTH:AUTO, NOT 100%. Governed by ../WIDTH_RULE.md rule W-3.
   A table stretched to fill its container pushes columns apart, lengthens
   every row traverse, and buys ZERO additional information: the visual span
   is ~10 characters and DOES NOT GROW WITH THE DISPLAY (Legge 2007,
   Psychophysics of Reading, ISBN 0-8058-4328-0; visual span as a sensory
   bottleneck, Legge/Mansfield/Chung 2001). Cost with no benefit.
   `width:100%` on a data table is a DEFECT, not a style choice.
   REFACTOR TRIGGER: if a table is ever given width:100% to "fill the space",
   that is W-3 being violated -- fix the container, not the table. */
.hrv-table{width:auto;border-collapse:collapse;
  font-size:var(--hrv-data-size);line-height:var(--hrv-data-line)}
.hrv-table th{
  position:sticky;top:0;z-index:1;
  height:var(--hrv-row);padding:0 var(--hrv-cell-pad-x);
  background:var(--hrv-surface-overlay);text-align:left;white-space:nowrap;
  font-size:var(--hrv-label-size);line-height:var(--hrv-label-line);
  font-weight:var(--hrv-weight-strong);letter-spacing:.04em;text-transform:uppercase;
  color:var(--hrv-ink-secondary);
  border-bottom:1px solid var(--hrv-border-subtle);
}
/* Sortability is an affordance and must be claimed, not assumed. A header is
   only a control if it says so -- bare <th> gets no pointer and no hover. */
.hrv-table th[aria-sort]{cursor:pointer}
.hrv-table th[aria-sort]:hover{color:var(--hrv-ink-primary)}
/* Sticky header offset: the header parks under what is stuck above it in its
   own scroller. :root carries --hrv-sticky-top (section 4's sticky contract,
   clause 1) and .hrv-scroll resets it to 0 (clause 2); a page re-declares it
   only for a module's own row, on the content after the row (clause 4). The
   fallback of 0 predates the token and is inert while :root declares it. */
.hrv-table th{top:var(--hrv-sticky-top,0)}
/* THE TABLE'S LANDING -- clause (3) in section 4; 2026-09-27, D-WAD-020.
   A ROW lands just below its stuck header, in any scroller: the header parks
   at --hrv-sticky-top and is --hrv-row tall, so a body element's margin is
   that sum, floored at the page's band, plus the 1px edge. FORCED: before
   this, a row link reached with Shift+Tab under its stuck header showed 0
   of 19px, in a page-scrolled table and in a .hrv-scroll panel alike.
   THE HEADER'S OWN CONTENTS take no margin where the header is stuck to the
   page, because a margin there moves the page when its sort button takes
   focus: MEASURED, 1px with the landing margin (state Cmax). Inside ANY
   .hrv-scroll they take the page's band plus 1px, flat, so the page can
   still reveal a panel's header that sits under a bar: with 0 there,
   Shift+Tab back up through a panel's table left its sort button 0 of 44px
   visible (state Cmax2). MEASURED. The value is flat and reads nothing
   inside the panel, so a panel's own band cannot shrink it; one that read
   it would give about 9px to a module table's header controls under a
   module row inside a panel (state Cmax3; worked, not measured).
   ITS PRICE is the one movement the frame gate allows: a vertically
   scrolling panel, scrolled, scrolls back by up to the page's band plus 1px
   when its header's control takes focus. MEASURED: 57px, the bound, on
   :root's band. The control is never hidden (SC 2.4.11) at the cost of
   moving the panel: one margin cannot be 0 for the panel and the band for
   the page at once. A panel that scrolls only sideways, the watchlist's
   shape, does not move.
   REFACTOR TRIGGER: a header that stops sticking, a header that is not
   --hrv-row tall, or a module row inside a .hrv-scroll (outside section 4's
   clause 4; write its rule then) re-opens this, and any change here re-runs
   measurements/frame_gate.py. */
.hrv-table tbody *{scroll-margin-top:calc(max(var(--hrv-sticky-top) + var(--hrv-row), var(--hrv-sticky-page)) + 1px)}
.hrv-table thead,.hrv-table thead *{scroll-margin-top:0px}
.hrv-scroll .hrv-table thead,.hrv-scroll .hrv-table thead *{scroll-margin-top:calc(var(--hrv-sticky-page) + 1px)}
.hrv-table td{height:var(--hrv-row);padding:0 var(--hrv-cell-pad-x);
  white-space:nowrap;vertical-align:middle;
  border-bottom:1px solid var(--hrv-border-subtle)}
.hrv-table tbody tr:last-child td{border-bottom:0}
.hrv-table tbody tr:hover{background:var(--hrv-surface-overlay)}

/* STICKY KEY COLUMN -- WIDTH_RULE.md W-3a. This is the row-tracking
   mitigation, NOT polish. A long horizontal traverse loses the row for the
   same reason a long prose line loses the line: the return sweep lands
   inaccurately. Row identity has to travel with the eye.
   WCAG 2.2 SC 1.4.10 EXCEPTS data tables from reflow precisely because they
   need two-dimensional layout -- so horizontal scroll here is SANCTIONED,
   provided it stays INSIDE the table and never becomes page-level scroll.
   The cell needs an opaque background or the scrolled columns show through.
   REQUIRED whenever a table can exceed its container. If you cannot ship
   this, cap the table instead of letting it scroll. */
.hrv-table th:first-child,
.hrv-table td:first-child{position:sticky;left:0;
  box-shadow:1px 0 0 var(--hrv-border-subtle)}
.hrv-table th:first-child{z-index:3}
.hrv-table td:first-child{z-index:2;background:var(--hrv-surface-base)}
.hrv-table tbody tr:hover td:first-child{background:var(--hrv-surface-overlay)}
.hrv-table .hrv-t-right{text-align:right}
/* THE SORT ARROW takes its header's ink (D-WAD-019; M09 spec R10). Its indigo
   carried none of P1's four meanings, and in dark it read 52.52 against 75. Its
   PRESENCE says which column is sorted (D-WAD-011: a shape carries a state);
   its glyph and size are unchanged. */
.hrv-table th[aria-sort="ascending"]::after{content:" \2191";color:inherit}
.hrv-table th[aria-sort="descending"]::after{content:" \2193";color:inherit}
.hrv-t-key{font-weight:var(--hrv-weight-strong)}


/* ===========================================================================
   11. STATE -- every tool surface must express all of these
   ===========================================================================
   A Terminal user knows what a blank panel means. A first-time investor does
   not. States are part of the module, not an afterthought.

   GUEST GATING sits at the SAVE boundary, NEVER the READ boundary. Guests are
   first-class and every read surface must stay indexable.                  */
.hrv-state{padding:var(--hrv-space-8) var(--hrv-space-5);text-align:center;
  color:var(--hrv-ink-primary);font-size:var(--hrv-data-size);line-height:var(--hrv-data-line)}
.hrv-state__title{display:block;color:var(--hrv-ink-primary);
  font-size:var(--hrv-title-size);line-height:var(--hrv-title-line);
  font-weight:var(--hrv-weight-strong);margin-bottom:var(--hrv-space-2)}
.hrv-skeleton{height:12px;border-radius:var(--hrv-radius-sm);
  background:var(--hrv-surface-overlay)}
.hrv-gate{background:var(--hrv-surface-overlay);
  border:1px dashed var(--hrv-border-default);border-radius:var(--hrv-radius-lg);
  padding:var(--hrv-space-6);text-align:center}
.hrv-live{display:inline-flex;align-items:center;gap:var(--hrv-space-2);
  font-size:var(--hrv-label-size);line-height:var(--hrv-label-line);
  font-weight:var(--hrv-weight-strong);color:var(--hrv-ink-secondary)}
/* THE LIVE DOT takes the tag's own ink (D-WAD-019; M09 spec R11). P1 is a hard
   law: hue carries gain, loss, action and attention, and "live" is none of
   them. The word "Live" beside it is unchanged. */
.hrv-live__dot{width:7px;height:7px;border-radius:50%;background:currentColor}


/* ===========================================================================
   12. CHART SURFACE
   ===========================================================================
   P1: price is series-primary. NEVER gain-ink -- a price series is not a gain,
   and colouring it green makes every chart read as a win. Overlays (moving
   averages, bands) take ink-muted; volume takes border-default. NO OVERLAY MAY
   OUTRANK THE PRICE.

   CHART TEXT RIDES NO viewBox SCALING (2026-09-24; M02 spec R16, backlog
   I-018). .hrv-canvas svg{width:100%} overrides an SVG's width attribute, so
   an SVG that carries a viewBox SCALES its whole coordinate system to the
   canvas, text included, and font-size then counts user units, not px. A
   15px .hrv-axis on a 400-unit viewBox painted 9.83px at a 320 viewport and
   11.32 at 360, while getComputedStyle went on reporting 15px. So an SVG in
   a canvas that carries text carries NO viewBox, and no text in it sits
   under a scaling transform: its user units are CSS px, the chart lays its
   geometry out at its rendered width, and .hrv-axis paints at the label rung
   it is priced at (D-WAD-018). A viewBox may scale only geometry that
   carries no text. collateral_probe.py gates this on the rig's chart at 320,
   360, 767, 1280 and 2560: each axis node's CTM scale times its computed
   size must equal that size within 0.5px.                                  */
.hrv-canvas{background:var(--hrv-surface-raised);
  border:1px solid var(--hrv-border-subtle);border-radius:var(--hrv-radius-lg);
  padding:var(--hrv-space-4)}
.hrv-canvas svg{width:100%}
.hrv-series{stroke:var(--hrv-series-primary);stroke-width:2;fill:none;
  stroke-linejoin:round;stroke-linecap:round}
.hrv-series--overlay{stroke:var(--hrv-ink-muted);stroke-width:1.5;stroke-dasharray:4 3}
.hrv-series--volume{fill:var(--hrv-border-default);stroke:none}
.hrv-gridline{stroke:var(--hrv-grid-line);stroke-width:1}
.hrv-axis{fill:var(--hrv-axis-text);font-size:var(--hrv-label-size);font-weight:var(--hrv-weight-strong)}


/* ===========================================================================
   13. BREAKPOINTS
   ===========================================================================
   Kit ships [1280, 1024, 768] against a floor of 320 -- NO RUNG ACROSS THE
   ENTIRE PHONE RANGE. Correct for a Terminal, which is a desktop window.
   Wrong here: this surface is public, indexed, and majority-mobile, so the
   phone range is the primary case. 480 added.                              */
@media (max-width:480px){
  .hrv-container{padding:0 var(--hrv-space-4)}
  /* the account menu's end edge stays on the content edge (D-WAD-022) */
  .hrv-tabbar .hrv-menu{inset-inline-end:var(--hrv-space-4)}
  .hrv-hide-sm{display:none}
  .hrv-table th,.hrv-table td{padding:0 var(--hrv-space-3)}
}

/* Coarse pointers get the floor enforced regardless of tier. */
@media (pointer:coarse){
  .hrv-btn--compact{min-height:var(--hrv-tap-min)}
}


/* ===========================================================================
   14. OPEN -- carry these into the refactor, do not rediscover them
   ===========================================================================
   1. body 16 vs 18. **CLOSED 2026-09-19 by D-WAD-016, landed 2026-09-22 (M01-2)
      -- comfortable `body` is 18/28 and compact is 16/24.** It read: "the
      measurement separates 14 from 16 and NOT 16 from 18. At ~58 cm on a
      laptop, 16 px sits at 0.163 deg, below CPS 0.20. If sustained prose reads
      poorly on desktop, raise the rung." The rung was raised. Kept closed
      rather than deleted because s14's own instruction is to carry these
      forward rather than rediscover them, and a reader who finds the question
      elsewhere needs to find its answer here.
   2. space is the least-argued token here and still is.
      CORRECTED 2026-09-08: this item read "SPACING CARRIES HALF THE DENSITY
      AXIS". That claim is RETRACTED -- section 4 retracted it already, and
      leaving it asserted here is how a retraction gets undone. The values
      refute it: the two tiers differ at THREE of ten steps and are identical
      at the other seven, so spacing is close to INERT on the density axis.
      What is true is narrower and still worth doing: spacing is the one row
      set by default rather than by decision, and it needs a derivation
      against a rendered tool surface. DEFERRED BY D-WAD-006's sequence.
   3. No series palette beyond series-primary. Multi-series charts (strategy
      comparison, sector overlay) will need 4-6 categorical colours that are
      distinguishable to the ~8% of men with red-green deficiency. Not solved,
      and DEFERRED -- charting is step 4 of the D-WAD-006 sequence.
   4. CLOSED 2026-09-08 by the shell. --hrv-elev-menu is minted, ONE level,
      and no scrim: nothing in the shell is modal. See section 2. A genuinely
      modal surface will force a second depth; that is not this step's job.
   5. ANSWERED 2026-09-08 by the shell, and only in part. [data-tier] is
      PER-REGION: it goes on the content region -- <main id="main">, or a
      region inside it (D-WAD-020, 2026-09-27) -- and NEVER on <body> or any
      ancestor of the shell, because D-WAD-005 s1 fixes the shell at one tier.
      measurements/frame_gate.py proves at 1280 and 360 that a compact <main>
      leaves the shell and the tab bar unchanged. A stray [data-tier] on <body>
      stays the page's to avoid; no stylesheet can stop an attribute (I-012).
      STILL OPEN: whether a region's tier is route-derived or user-selectable
      is a product decision and remains one.
   6. NEW, opened by the shell; RE-MEASURED 2026-09-30 (D-WAD-021 c4). The
      identity slot's reserved width is two numbers, one per rung -- 243 px
      from 1024, 86 px at 768-1023 -- each measured from the guest CTA copy
      at the landed data rung, not derived. measurements/shell_gate.py's G4
      reds a rung change at its next run; a copy change reaches no gate until
      it is put in specimens/SHELL_RIG.html. If the CTA labels or the rung
      move, re-measure -- see the trigger on .hrv-identity.
   7. CLOSED 2026-09-27 by D-WAD-020 (M03). Opened 2026-09-13 by the tab bar,
      it asked what the content region is, and where three rules land that
      had nowhere to: the tab bar's bottom inset below 768, the [data-tier]
      attribute item 5 puts "on the content region", and a sticky offset a
      module sets inside <main> (SHELL_SPEC s6). It asked for the selector to
      be minted deliberately, with its own argument, and not as a side effect
      of whichever rule needed it first.
      THE ANSWER is the element, not a class. The content region is
      <main id="main">, and it takes no class, because no declaration needs
      one. The reserve sits on the root (9B's 767 block), the landing margins
      on whatever can be scrolled to (9B after .hrv-scroll, and section 10),
      a module's sums on its own content (section 4's sticky contract, clause
      4), and the tier on <main> or a region inside it (item 5). It was
      answered in the language, step 1's artifact, on the day step 3 opened
      (D-WAD-006 s7), so the build inherits the answer instead of inventing
      it.
   8. NEW 2026-09-13, opened by D-WAD-008 c4. CHANGE SALIENCE has a RULE and
      no MEANS. A user-caused change to a displayed figure must be
      recognisable at rest as a change, carried without motion and without
      weight. Nothing is minted for it because the means is a component
      decision and there is no component yet (D-WAD-004 c5). The first
      surface that recomputes a figure from a user input -- a screener, a
      scenario, a period switch -- is where this gets built, and the builder
      should arrive at it already ruled rather than rediscovering Johnson.
   =========================================================================== */
