/* ============================================================================
   utilities.css — THE single utility vocabulary for GCS. LAST in the cascade.

   ---------------------------------------------------------------------------
   1. WHY LOAD ORDER IS THE WHOLE POINT
   ---------------------------------------------------------------------------
   These began life inside components.css, which loads BEFORE the shell sheet,
   and that was wrong: a utility written `.shell .u-mt-0` is (0,2,0), exactly the
   same specificity as `.shell .acts{margin-top:40px}` in ledger.css — so it TIED
   and LOST to the later sheet, and the declaration silently did nothing.

   Fourteen agents collapsing inline styles hit this independently and correctly
   refused to swap in those spots, leaving declarations inline rather than
   shipping a class that does nothing. They were right, and the honest fix is not
   more specificity — it is putting utilities where utilities belong: after
   everything they are meant to override.

   The contract, verified in all four base templates:
       fonts.css -> tokens.css -> components.css -> shell -> utilities.css
   (desk_client_base, desk_admin_base, ledger_client_base, ledger_admin_base.
   desk_client_base also links assistant.css after this file; every selector in
   it is `.askb__*`, a single widget block, so it never touches `.u-*`.)

   A utility must be the last word on the one property it sets. That is what
   makes it safe to replace an inline style, which was previously the only thing
   in this codebase that always won.

   ---------------------------------------------------------------------------
   2. MEASURED BASIS — this vocabulary was derived, not imagined
   ---------------------------------------------------------------------------
   Scripted extraction of every `style=""` attribute across all 137 templates
   (2026-07-27, HEAD 6d1b89e2):

       1,170 inline style attributes in 115 of 137 templates
       2,268 CSS declarations inside them
          34 attributes contain Jinja and are therefore genuinely dynamic

   The declarations, by property — this is what the vocabulary below covers:

       650  margin family   269 margin / 257 margin-top / 98 margin-bottom / 26 margin-left
       181  color            15 distinct values, all already tokens
       171  font-size        51 distinct values, ZERO utilities existed for it
       139  gap              34 distinct values
       139  padding family  116 padding / 13 padding-top / 10 padding-left
       117  max-width        36 distinct values, 64ch alone used 34 times
       115  flex/align       83 display / 60 align-items / 55 justify-content / 21 wrap / 16 direction
        79  grid-template-columns
        62  line-height      16 distinct values
        57  border family    37 border / 20 border-top
        50  width            10 of which are Jinja-interpolated bar widths
        46  text-align
        44  background

   The single loudest finding: utilities.css shipped 54 utilities and NOT ONE set
   font-size, which is why /client/settings alone serves 133 inline styles, 32 of
   them literal font-size:13px / 14px. font-size is now covered first.

   ---------------------------------------------------------------------------
   3. NAMING — memorise these five rules and you can predict every class name
   ---------------------------------------------------------------------------
   a. Every class is `u-` + property abbreviation + `-` + value.
   b. Every rule is written TWICE, `.shell .u-x, .u-x`, so it wins inside a shell
      (0,2,0) and still works on a page that has no `.shell` wrapper (0,1,0).
      Never write a bare `.u-x` on its own — inside a shell it will lose.
   c. Spacing/size numbers ARE THE PIXEL VALUE. `u-mt-16` is margin-top:16px.
      There is no t-shirt scale to translate.
   d. Type, colour and leading are SEMANTIC, not numeric: `u-fs-dense`,
      `u-ink-2`, `u-lh-relaxed`. The name says what the text IS. This mirrors
      tokens.css, which chose semantic type names for exactly this reason.
   e. Nothing is abbreviated twice. `u-mb-12` not `u-mb12`.

   ---------------------------------------------------------------------------
   4. TOKENS ONLY — and how one vocabulary spans two token families
   ---------------------------------------------------------------------------
   Two token families are live in this repo and both must keep working:

       shell family   desk.css + ledger.css   --ink-0..3  --brass-*  --hairline
       black-label    black-label.css         --bl-ink-50..500  --bl-gold-*  --bl-hairline

   Measured: they are the SAME LADDER under two names, and black-label.css
   declares zero `--ink-N`, so a var() fallback chain resolves correctly on both:

       --ink-0 #F4EFE4  ==  --bl-ink-50  #F4EFE6      --brass-500 #C9A96E
       --ink-1 #E7E1D5  ==  --bl-ink-100 #E8E2D6      --bl-gold-500 #C9A96E  (identical)
       --ink-2 #B6AFA3  ==  --bl-ink-300 #A8A29A      --hairline  #26232B
       --ink-3 #8E8779  ==  --bl-ink-500 #8A8479      --bl-hairline #242428

   So every colour and type utility is written:

       color: var(--ink-2, var(--bl-ink-300, #B6AFA3));
              ^ shell page  ^ black-label page  ^ last-resort literal

   The final literal is a SAFETY VALUE equal to the token it mirrors, never a new
   design decision — the same pattern this file already used for `var(--space-2, 4px)`.
   Change the token and every page moves; the literal only ever fires on a page
   that links no stylesheet at all.

   FORWARD-REFERENCED TOKENS. Some names below do not exist yet in any sheet
   (--w-sm, --lh-loose, --mono). They are written as var(--name, <current value>)
   so the utility renders correctly today and adopts the token the moment
   fix-design adds it. These are listed in the handoff.

   ---------------------------------------------------------------------------
   5. DECLARATION ORDER INSIDE THIS FILE IS LOAD-BEARING
   ---------------------------------------------------------------------------
   Utilities are all the same specificity, so within this file LATER WINS. Two
   consequences you must not break when adding to it:

     * `margin` shorthand (u-m-0, u-mx-auto) is declared BEFORE the longhands
       (u-mt-*, u-mb-*, u-ml-*). That is what makes the composition below work:
           style="margin:16px 0 0"   ->  class="u-m-0 u-mt-16"
       u-m-0 zeroes all four sides, u-mt-16 then re-sets the top. Reverse the
       order in this file and u-m-0 would silently erase the margin-top.
     * `padding` shorthand is declared BEFORE u-pt-* / u-pl-* for the same reason.
     * Composite row helpers (u-between, u-row-c) are declared BEFORE the atomic
       alignment utilities, so `class="u-between u-items-baseline"` reads as
       written: baseline wins over the center that u-between bakes in.

   ---------------------------------------------------------------------------
   6. COVERAGE, AND WHAT DELIBERATELY HAS NO UTILITY
   ---------------------------------------------------------------------------
   Measured against all 2,268 inline declarations, this vocabulary replaces
   2,205 of them — 98.5% of every declaration that is not Jinja-computed. The
   remainder was left out ON PURPOSE. Do not invent classes for it:

     * 30 Jinja-interpolated values — `width: {{ lead.score }}%` and the other
       progress/score bars. A computed value is DATA, not style, and it belongs
       in the attribute. Leave every one of these exactly where it is.
     * Absolute-position offsets (top/left/right, `position: sticky`,
       `transform: rotate(180deg)`, `opacity: .65`, `visibility: hidden`) —
       33 declarations, each one placing a specific decorative element in a
       specific box. A `u-top-9` class is not a system, it is an inline style
       with extra steps.
     * `font-size` in `em` — a deliberately RELATIVE size (`.7em` on a superscript
       symbol) that must track whatever it sits inside. A fixed step would break it.

   If you need something genuinely missing, ADD IT HERE in the right section with
   a comment saying what you measured — do not open a second vocabulary, and do
   not fall back to an inline style. That is the entire point of this file.

   ---------------------------------------------------------------------------
   7. CHECK THE PAGE ACTUALLY LOADS THIS FILE BEFORE YOU SWAP ANYTHING
   ---------------------------------------------------------------------------
   Measured by walking the {% extends %} chain of all 137 templates:

       89 templates inherit utilities.css (via desk_client_base, desk_admin_base
          or ledger_admin_base) — 81 of them carry inline styles, 751 attributes.
          These are swappable TODAY.
       48 templates do NOT inherit it. 34 of those carry inline styles, 419
          attributes — roughly a third of the whole problem.

   Every one of those 34 is a STANDALONE public/Black-Label page (intake,
   pricing, checkout, activation, the login and MFA screens, proposal_public,
   setup_review, roi, build_result...) that extends nothing and links
   black-label.css directly. `class="u-ink-2"` on those pages does NOTHING until
   the sheet is linked. This is the same trap as csrf_hidden: standalone pages
   inherit no base, so anything the base provides is simply absent.

   Before swapping a standalone page, add — AFTER black-label.css, never before,
   or the shell wins and the utility silently loses:
       <link rel="stylesheet" href="{{ static_asset('utilities.css') }}" />
   The colour and type utilities already resolve against --bl-* tokens (note 4),
   so they are correct on those pages the moment the link exists.

   NOTE: bl/ledger_client_base.html also links this file but is extended by ZERO
   templates — the client desk moved to desk_client_base.html. It is flagged
   orphaned by tests/test_codebase_sanitation.py. Do not migrate pages onto it.
   ========================================================================= */


/* ===========================================================================
   DISPLAY & FLOW
   =========================================================================== */
.shell .u-row,        .u-row{ display: flex; }
.shell .u-row-c,      .u-row-c{ display: flex; align-items: center; }
.shell .u-between,    .u-between{ display: flex; align-items: center; justify-content: space-between; }
.shell .u-grid,       .u-grid{ display: grid; }
.shell .u-block,      .u-block{ display: block; }
.shell .u-iflex,      .u-iflex{ display: inline-flex; }
.shell .u-inline,     .u-inline{ display: inline; }
.shell .u-iblock,     .u-iblock{ display: inline-block; }
.shell .u-hidden,     .u-hidden{ display: none; }

/* ===========================================================================
   FLEX & GRID ALIGNMENT
   Declared AFTER the composite row helpers above so `u-between u-items-baseline`
   resolves to baseline (see header note 5).
   =========================================================================== */
.shell .u-col,             .u-col{ flex-direction: column; }
.shell .u-wrap,            .u-wrap{ flex-wrap: wrap; }
.shell .u-nowrap-flex,     .u-nowrap-flex{ flex-wrap: nowrap; }
.shell .u-items-start,     .u-items-start{ align-items: flex-start; }
.shell .u-items-center,    .u-items-center{ align-items: center; }
.shell .u-items-baseline,  .u-items-baseline{ align-items: baseline; }
.shell .u-items-end,       .u-items-end{ align-items: flex-end; }
.shell .u-justify-start,   .u-justify-start{ justify-content: flex-start; }
.shell .u-justify-center,  .u-justify-center{ justify-content: center; }
.shell .u-justify-between, .u-justify-between{ justify-content: space-between; }
.shell .u-justify-end,     .u-justify-end{ justify-content: flex-end; }
.shell .u-self-start,      .u-self-start{ align-self: flex-start; }
.shell .u-self-end,        .u-self-end{ align-self: flex-end; }
.shell .u-min0,            .u-min0{ min-width: 0; }
.shell .u-grow,            .u-grow{ flex: 1 1 auto; }
.shell .u-noshrink,        .u-noshrink{ flex: 0 0 auto; }
/* `flex: 0 1 <basis>` — the "column that may shrink but not grow" idiom, the
   only flex shorthand measured more than twice (200px x3, plus 160/180/240). */
.shell .u-flexcol-160,     .u-flexcol-160{ flex: 0 1 160px; }
.shell .u-flexcol-200,     .u-flexcol-200{ flex: 0 1 200px; }
.shell .u-flexcol-240,     .u-flexcol-240{ flex: 0 1 240px; }

/* ===========================================================================
   GRID TEMPLATES
   The fixed-count utilities use repeat(N, minmax(0,1fr)) rather than the
   `1fr 1fr` written inline 20 times: identical layout, but minmax(0,…) stops a
   long unbroken string blowing the column out. The auto-fit utilities wrap the
   floor in min(100%, …) so a 280px minimum does not overflow a 320px phone —
   two templates already discovered this independently.
   =========================================================================== */
.shell .u-cols-2,      .u-cols-2{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
.shell .u-cols-3,      .u-cols-3{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
.shell .u-cols-4,      .u-cols-4{ grid-template-columns: repeat(4, minmax(0, 1fr)); }
.shell .u-autofit-150, .u-autofit-150{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); }
.shell .u-autofit-160, .u-autofit-160{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); }
.shell .u-autofit-200, .u-autofit-200{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
.shell .u-autofit-220, .u-autofit-220{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); }
.shell .u-autofit-240, .u-autofit-240{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.shell .u-autofit-260, .u-autofit-260{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.shell .u-autofit-280, .u-autofit-280{ grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.shell .u-col-full,    .u-col-full{ grid-column: 1 / -1; }

/* ===========================================================================
   GAP — the 2px ladder from tokens.css
   =========================================================================== */
.shell .u-gap-1,  .u-gap-1{ gap: 1px; }              /* hairline grid: cells separated by the rule itself */
.shell .u-gap-4,  .u-gap-4{ gap: var(--space-2, 4px); }
.shell .u-gap-6,  .u-gap-6{ gap: var(--space-3, 6px); }
.shell .u-gap-8,  .u-gap-8{ gap: var(--space-4, 8px); }
.shell .u-gap-10, .u-gap-10{ gap: var(--space-5, 10px); }
.shell .u-gap-12, .u-gap-12{ gap: var(--space-6, 12px); }
.shell .u-gap-14, .u-gap-14{ gap: var(--space-7, 14px); }
.shell .u-gap-16, .u-gap-16{ gap: var(--space-8, 16px); }
.shell .u-gap-18, .u-gap-18{ gap: var(--space-9, 18px); }
.shell .u-gap-20, .u-gap-20{ gap: var(--space-10, 20px); }
.shell .u-gap-24, .u-gap-24{ gap: var(--space-12, 24px); }
.shell .u-gap-28, .u-gap-28{ gap: var(--space-14, 28px); }
/* Column-only gap: `gap: 0 Npx`. A wrapping definition row wants horizontal air
   and zero vertical, because the rows already carry their own leading. */
.shell .u-gapx-12, .u-gapx-12{ gap: 0 var(--space-6, 12px); }
.shell .u-gapx-18, .u-gapx-18{ gap: 0 var(--space-9, 18px); }
.shell .u-gapx-24, .u-gapx-24{ gap: 0 var(--space-12, 24px); }
/* Asymmetric row/column gap, `gap: <row> <col>`. */
.shell .u-gap-8-10,  .u-gap-8-10{ gap: var(--space-4, 8px) var(--space-5, 10px); }
.shell .u-gap-8-12,  .u-gap-8-12{ gap: var(--space-4, 8px) var(--space-6, 12px); }
.shell .u-gap-8-18,  .u-gap-8-18{ gap: var(--space-4, 8px) var(--space-9, 18px); }
.shell .u-gap-10-14, .u-gap-10-14{ gap: var(--space-5, 10px) var(--space-7, 14px); }

/* ===========================================================================
   MARGIN — SHORTHAND FIRST (see header note 5)
   =========================================================================== */
.shell .u-m-0,     .u-m-0{ margin: 0; }
.shell .u-mx-auto, .u-mx-auto{ margin-left: auto; margin-right: auto; }
/* `margin: 0 0 Npx` — the stacked-block idiom. Kept because 66 call sites
   already use u-stack-14/16/18; the equivalent pair form `u-m-0 u-mb-N` is
   equally correct and is what you should reach for at a size not listed here. */
.shell .u-stack-6,  .u-stack-6{ margin: 0 0 var(--space-3, 6px); }
.shell .u-stack-8,  .u-stack-8{ margin: 0 0 var(--space-4, 8px); }
.shell .u-stack-10, .u-stack-10{ margin: 0 0 var(--space-5, 10px); }
.shell .u-stack-12, .u-stack-12{ margin: 0 0 var(--space-6, 12px); }
.shell .u-stack-14, .u-stack-14{ margin: 0 0 var(--space-7, 14px); }
.shell .u-stack-16, .u-stack-16{ margin: 0 0 var(--space-8, 16px); }
.shell .u-stack-18, .u-stack-18{ margin: 0 0 var(--space-9, 18px); }
.shell .u-stack-20, .u-stack-20{ margin: 0 0 var(--space-10, 20px); }
.shell .u-stack-24, .u-stack-24{ margin: 0 0 var(--space-12, 24px); }

/* MARGIN LONGHAND — must stay after the shorthand block above.
   The ladder is 2/4/6/8/10/12/14/16/18/20/22/24/28/32/40/48 from tokens.css.
   Measured stragglers snap to the nearest rung: 3px -> u-mt-2, 26px -> u-mt-24,
   30px -> u-mt-32. Half-steps are what made cards drift page to page; the whole
   reason tokens.css exists is that there was nothing to line up to. */
.shell .u-mt-0,    .u-mt-0{ margin-top: 0; }
.shell .u-mt-2,    .u-mt-2{ margin-top: var(--space-1, 2px); }
.shell .u-mt-4,    .u-mt-4{ margin-top: var(--space-2, 4px); }
.shell .u-mt-6,    .u-mt-6{ margin-top: var(--space-3, 6px); }
.shell .u-mt-8,    .u-mt-8{ margin-top: var(--space-4, 8px); }
.shell .u-mt-10,   .u-mt-10{ margin-top: var(--space-5, 10px); }
.shell .u-mt-12,   .u-mt-12{ margin-top: var(--space-6, 12px); }
.shell .u-mt-14,   .u-mt-14{ margin-top: var(--space-7, 14px); }
.shell .u-mt-16,   .u-mt-16{ margin-top: var(--space-8, 16px); }
.shell .u-mt-18,   .u-mt-18{ margin-top: var(--space-9, 18px); }
.shell .u-mt-20,   .u-mt-20{ margin-top: var(--space-10, 20px); }
.shell .u-mt-22,   .u-mt-22{ margin-top: var(--space-11, 22px); }
.shell .u-mt-24,   .u-mt-24{ margin-top: var(--space-12, 24px); }
.shell .u-mt-28,   .u-mt-28{ margin-top: var(--space-14, 28px); }
.shell .u-mt-32,   .u-mt-32{ margin-top: var(--space-16, 32px); }
.shell .u-mt-40,   .u-mt-40{ margin-top: var(--space-20, 40px); }
.shell .u-mt-48,   .u-mt-48{ margin-top: var(--space-24, 48px); }
.shell .u-mt-auto, .u-mt-auto{ margin-top: auto; }
.shell .u-mb-0,    .u-mb-0{ margin-bottom: 0; }
.shell .u-mb-4,    .u-mb-4{ margin-bottom: var(--space-2, 4px); }
.shell .u-mb-6,    .u-mb-6{ margin-bottom: var(--space-3, 6px); }
.shell .u-mb-8,    .u-mb-8{ margin-bottom: var(--space-4, 8px); }
.shell .u-mb-10,   .u-mb-10{ margin-bottom: var(--space-5, 10px); }
.shell .u-mb-12,   .u-mb-12{ margin-bottom: var(--space-6, 12px); }
.shell .u-mb-14,   .u-mb-14{ margin-bottom: var(--space-7, 14px); }
.shell .u-mb-16,   .u-mb-16{ margin-bottom: var(--space-8, 16px); }
.shell .u-mb-18,   .u-mb-18{ margin-bottom: var(--space-9, 18px); }
.shell .u-mb-20,   .u-mb-20{ margin-bottom: var(--space-10, 20px); }
.shell .u-mb-22,   .u-mb-22{ margin-bottom: var(--space-11, 22px); }
.shell .u-mb-24,   .u-mb-24{ margin-bottom: var(--space-12, 24px); }
.shell .u-mb-32,   .u-mb-32{ margin-bottom: var(--space-16, 32px); }
.shell .u-ml-0,    .u-ml-0{ margin-left: 0; }
.shell .u-ml-8,    .u-ml-8{ margin-left: var(--space-4, 8px); }
.shell .u-ml-12,   .u-ml-12{ margin-left: var(--space-6, 12px); }
.shell .u-ml-32,   .u-ml-32{ margin-left: var(--space-16, 32px); }  /* hanging indent under a marker/icon */
.shell .u-ml-auto, .u-ml-auto{ margin-left: auto; }
.shell .u-mr-8,    .u-mr-8{ margin-right: var(--space-4, 8px); }
.shell .u-mr-12,   .u-mr-12{ margin-right: var(--space-6, 12px); }

/* ===========================================================================
   PADDING — SHORTHAND FIRST, same rule as margin
   =========================================================================== */
.shell .u-p-0,  .u-p-0{ padding: 0; }
.shell .u-p-8,  .u-p-8{ padding: var(--space-4, 8px); }
.shell .u-p-12, .u-p-12{ padding: var(--space-6, 12px); }
.shell .u-p-14, .u-p-14{ padding: var(--space-7, 14px); }
.shell .u-p-16, .u-p-16{ padding: var(--space-8, 16px); }
.shell .u-p-20, .u-p-20{ padding: var(--space-10, 20px); }
.shell .u-p-24, .u-p-24{ padding: var(--space-12, 24px); }
/* Named padding idioms, each absorbing a measured cluster. */
.shell .u-p-surface, .u-p-surface{ padding: var(--p-surface, clamp(22px, 2.4vw, 32px)); }   /* card/panel inset */
.shell .u-p-cell,    .u-p-cell{ padding: var(--space-6, 12px) var(--space-1, 2px); }  /* table cell: vertical air, no horizontal inset */
.shell .u-p-field,   .u-p-field{ padding: var(--space-6, 12px) var(--space-7, 14px); } /* input / control box */
.shell .u-p-band,    .u-p-band{ padding: var(--space-8, 16px) var(--space-9, 18px); }  /* inset notice band */
.shell .u-p-section, .u-p-section{ padding: var(--p-section, clamp(40px, 6vw, 96px)) 0; }   /* full-bleed marketing section */
.shell .u-p-gutter,  .u-p-gutter{ padding: 0 var(--space-12, 24px); }     /* page side gutter */
/* Disclosure summaries: a <details> summary is the one place a pointer cursor
   and a padded hit area are structural rather than decorative — without them the
   control does not read as clickable, and the section it hides looks missing. */
.shell .u-pad-16-28, .u-pad-16-28{ padding: var(--space-8, 16px) var(--space-14, 28px); }
.shell .u-pad-6-28,  .u-pad-6-28{ padding: var(--space-3, 6px) var(--space-14, 28px); }
/* PADDING LONGHAND — after the shorthand. */
.shell .u-pt-0,  .u-pt-0{ padding-top: 0; }
.shell .u-pt-14, .u-pt-14{ padding-top: var(--space-7, 14px); }
.shell .u-pt-18, .u-pt-18{ padding-top: var(--space-9, 18px); }
.shell .u-pt-20, .u-pt-20{ padding-top: var(--space-10, 20px); }
.shell .u-pb-0,  .u-pb-0{ padding-bottom: 0; }
.shell .u-pb-14, .u-pb-14{ padding-bottom: var(--space-7, 14px); }
.shell .u-pb-20, .u-pb-20{ padding-bottom: var(--space-10, 20px); }
.shell .u-pl-0,  .u-pl-0{ padding-left: 0; }
.shell .u-pl-18, .u-pl-18{ padding-left: var(--space-9, 18px); }  /* list indent */
.shell .u-pl-20, .u-pl-20{ padding-left: var(--space-10, 20px); }

/* ===========================================================================
   SIZE — width, max-width, height
   MEASURE (max-width in ch) is the readable-line-length idiom and the single
   most repeated inline declaration in the codebase: `max-width:64ch` appears 34
   times. ch is intrinsic to the typeface, so these are not literals.
   The px containers forward-reference --w-* tokens that do not exist yet.
   =========================================================================== */
.shell .u-w-full,  .u-w-full{ width: 100%; }
.shell .u-w-auto,  .u-w-auto{ width: auto; }
.shell .u-h-full,  .u-h-full{ height: 100%; }
.shell .u-minh-screen, .u-minh-screen{ min-height: 100vh; }
/* Fixed square: avatar, monogram, icon well. Measured as width+height written
   as a pair, never independently — so it ships as one class. */
.shell .u-size-44, .u-size-44{ width: 44px; height: 44px; flex: 0 0 44px; }
.shell .u-size-56, .u-size-56{ width: 56px; height: 56px; flex: 0 0 56px; }
.shell .u-measure-34, .u-measure-34{ max-width: 34ch; }
.shell .u-measure-48, .u-measure-48{ max-width: 48ch; }
.shell .u-measure-54, .u-measure-54{ max-width: 54ch; }
.shell .u-measure-60, .u-measure-60{ max-width: 60ch; }
.shell .u-measure-62, .u-measure-62{ max-width: 62ch; }
.shell .u-measure-64, .u-measure-64{ max-width: 64ch; }
.shell .u-measure-66, .u-measure-66{ max-width: 66ch; }
.shell .u-measure-70, .u-measure-70{ max-width: 70ch; }
.shell .u-measure-72, .u-measure-72{ max-width: 72ch; }
.shell .u-measure-74, .u-measure-74{ max-width: 74ch; }
.shell .u-measure-80, .u-measure-80{ max-width: 80ch; }
.shell .u-maxw-full, .u-maxw-full{ max-width: 100%; }
.shell .u-maxw-xs,   .u-maxw-xs{ max-width: var(--w-xs, 360px); }
.shell .u-maxw-sm,   .u-maxw-sm{ max-width: var(--w-sm, 480px); }
.shell .u-maxw-md,   .u-maxw-md{ max-width: var(--w-md, 640px); }
.shell .u-maxw-lg,   .u-maxw-lg{ max-width: var(--w-lg, 760px); }
.shell .u-maxw-xl,   .u-maxw-xl{ max-width: var(--w-xl, 1080px); }
/* A form column that is 500px on a laptop and edge-to-edge on a phone. */
.shell .u-maxw-form, .u-maxw-form{ width: min(var(--w-form, 500px), 100%); }

/* ===========================================================================
   TYPE — family
   =========================================================================== */
.shell .u-serif, .u-serif{ font-family: var(--serif, var(--bl-serif, Georgia, "Times New Roman", serif)); }
.shell .u-mono,  .u-mono{ font-family: var(--mono, var(--bl-mono, ui-monospace, SFMono-Regular, Menlo, monospace)); }

/* ===========================================================================
   TYPE — SIZE. The gap that produced this whole file: 171 inline font-sizes
   across 51 distinct values, and not one utility for them.

   Every step below is a tokens.css step. Measured literals snap to the nearest,
   which is the point of having a scale — half-pixel neighbours read as blur,
   never as emphasis:
       10 / 10.5 / 11 / 11.5      -> u-fs-cap      (11px)
       12 / 12.5                  -> u-fs-meta     (12.5px)
       13 / 13.5                  -> u-fs-note     (13px)
       14 / 14.5                  -> u-fs-dense    (14px)   <- desk workhorse
       15                         -> u-fs-row      (15px)
       16 / 1.05rem               -> u-fs-body     (16px)
       17                         -> u-fs-lede     (17px)
       18 / 19 / 20 / 1.2rem      -> u-fs-title    (19px)
       22 / 24 / 1.35–1.5rem      -> u-fs-section  (24px)
       30 / 1.8rem / clamp(1.4–2.1rem) -> u-fs-outcome (fluid)
   =========================================================================== */
.shell .u-fs-cap,       .u-fs-cap{ font-size: var(--fs-cap, var(--bl-fs-tiny, 0.6875rem)); }
.shell .u-fs-meta,      .u-fs-meta{ font-size: var(--fs-meta, var(--bl-fs-label, 0.78125rem)); }
.shell .u-fs-note,      .u-fs-note{ font-size: var(--fs-note, var(--bl-fs-meta, 0.8125rem)); }
.shell .u-fs-dense,     .u-fs-dense{ font-size: var(--fs-dense, var(--bl-fs-btn, 0.875rem)); }
.shell .u-fs-row,       .u-fs-row{ font-size: var(--fs-row, var(--bl-fs-row, 0.9375rem)); }
.shell .u-fs-fine,      .u-fs-fine{ font-size: var(--fs-fine, var(--bl-fs-row, 0.9375rem)); }
.shell .u-fs-body,      .u-fs-body{ font-size: var(--fs-body, var(--bl-fs-body, 1rem)); }
.shell .u-fs-lede,      .u-fs-lede{ font-size: var(--fs-lede, 1.0625rem); }
.shell .u-fs-title,     .u-fs-title{ font-size: var(--fs-title, var(--bl-fs-h3, 1.1875rem)); }
.shell .u-fs-section,   .u-fs-section{ font-size: var(--fs-section, var(--bl-fs-h2, 1.5rem)); }
.shell .u-fs-outcome,   .u-fs-outcome{ font-size: var(--fs-outcome, clamp(1.7rem, 2.7vw, 2.5rem)); }
.shell .u-fs-h2,        .u-fs-h2{ font-size: var(--fs-h2-serif, var(--bl-fs-sect, clamp(1.9rem, 3.3vw, 3rem))); }
.shell .u-fs-metric,    .u-fs-metric{ font-size: var(--fs-metric, clamp(2.3rem, 3.4vw, 3.6rem)); }
.shell .u-fs-metric-lg, .u-fs-metric-lg{ font-size: var(--fs-metric-lg, clamp(2.8rem, 4vw, 4.2rem)); }
.shell .u-fs-display,   .u-fs-display{ font-size: var(--fs-display, var(--bl-fs-display, clamp(3.3rem, 7.6vw, 6.9rem))); }
/* NOT a size utility — `font: inherit` resets family, size, weight and leading
   together. It is the one correct fix for a <button>/<input>/<select> that must
   read as the body text around it, which is how all 4 measured uses read. */
.shell .u-font-inherit, .u-font-inherit{ font: inherit; }

/* ===========================================================================
   TYPE — weight, leading, case, wrapping, alignment
   =========================================================================== */
.shell .u-medium,  .u-medium{ font-weight: 500; }
.shell .u-semi,    .u-semi{ font-weight: 600; }
.shell .u-upright, .u-upright{ font-style: normal; }   /* cancels an inherited italic */
.shell .u-italic,  .u-italic{ font-style: italic; }

.shell .u-lh-tight,   .u-lh-tight{ line-height: var(--lh-tight, 1.15); }
.shell .u-lh-snug,    .u-lh-snug{ line-height: var(--lh-snug, 1.35); }
.shell .u-lh-normal,  .u-lh-normal{ line-height: var(--lh-normal, 1.6); }
.shell .u-lh-relaxed, .u-lh-relaxed{ line-height: var(--lh-relaxed, 1.65); }
.shell .u-lh-loose,   .u-lh-loose{ line-height: var(--lh-loose, 1.9); }
/* Alias: 8 call sites already say u-lh-16. Same rule as u-lh-normal. */
.shell .u-lh-16,      .u-lh-16{ line-height: var(--lh-normal, 1.6); }

/* Eyebrow caps. Measured as text-transform + letter-spacing written separately
   in every case; they are one idiom and belong in one class. */
.shell .u-caps,      .u-caps{ text-transform: uppercase; letter-spacing: var(--track-caps, 0.08em); }
.shell .u-caps-wide, .u-caps-wide{ text-transform: uppercase; letter-spacing: var(--track-caps-wide, 0.28em); }
.shell .u-tighten,   .u-tighten{ letter-spacing: var(--track-tight, -0.01em); }   /* large display type only */

.shell .u-center, .u-center{ text-align: center; }
.shell .u-left,   .u-left{ text-align: left; }
.shell .u-right,  .u-right{ text-align: right; }

.shell .u-prewrap,   .u-prewrap{ white-space: pre-wrap; }
.shell .u-preline,   .u-preline{ white-space: pre-line; }
.shell .u-nowrap,    .u-nowrap{ white-space: nowrap; }
/* Long unbroken tokens — URLs, ids, API keys — must break rather than push the
   card wider than the viewport. Measured as overflow-wrap and word-break used
   as a pair, so it ships as a pair. */
.shell .u-breakword, .u-breakword{ overflow-wrap: anywhere; word-break: break-word; }

.shell .u-list-none, .u-list-none{ list-style: none; }
.shell .u-list-disc, .u-list-disc{ list-style: disc; }
.shell .u-list-num,  .u-list-num{ list-style: decimal; }
/* `u-list-bare` — the fully unstyled list — is a COMPOSITE and lives at the
   bottom of this file with the others, because it sets margin and padding and
   must therefore come after the margin and padding utilities. */

.shell .u-underline,   .u-underline{ text-decoration: underline; }
.shell .u-nounderline, .u-nounderline{ text-decoration: none; }

/* ===========================================================================
   COLOUR — INK
   The 4-step ladder, resolving on both token families (header note 4).
   u-ink-0 brightest / u-ink-3 quietest. Nothing outside this ladder.
   =========================================================================== */
.shell .u-ink-0,       .u-ink-0{ color: var(--ink-0, var(--bl-ink-50,  #F4EFE4)); }
.shell .u-ink-1,       .u-ink-1{ color: var(--ink-1, var(--bl-ink-100, #E7E1D5)); }
.shell .u-ink-2,       .u-ink-2{ color: var(--ink-2, var(--bl-ink-300, #B6AFA3)); }
.shell .u-ink-3,       .u-ink-3{ color: var(--ink-3, var(--bl-ink-500, #8E8779)); }
.shell .u-ink-inherit, .u-ink-inherit{ color: inherit; }
/* Brass is the one accent. --brass-500 and --bl-gold-500 are the same hex. */
.shell .u-accent,      .u-accent{ color: var(--brass-500, var(--bl-gold-500, #C9A96E)); }
.shell .u-accent-soft, .u-accent-soft{ color: var(--brass-300, var(--bl-gold-400, #DFC791)); }
.shell .u-accent-deep, .u-accent-deep{ color: var(--brass-400, var(--bl-gold-500, #D2B378)); }
/* Status. Use for the WORD that carries the state, never as decoration. */
.shell .u-ink-ok,     .u-ink-ok{ color: var(--ok, var(--bl-emerald, #8CD9AD)); }
.shell .u-ink-warn,   .u-ink-warn{ color: var(--warn, var(--bl-amber, #D9A441)); }
.shell .u-ink-danger, .u-ink-danger{ color: var(--danger, var(--bl-red, #E0806C)); }
/* Checkbox / radio / range tint. Kills 9 hardcoded #C9A96E literals. */
.shell .u-accent-control, .u-accent-control{ accent-color: var(--brass-500, var(--bl-gold-500, #C9A96E)); }

/* ===========================================================================
   COLOUR — SURFACE
   desk.css does not define --bg-2 (ledger.css does), so u-bg-2 chains through
   --paper-0, which both shells define, before reaching a literal.
   =========================================================================== */
.shell .u-bg-none, .u-bg-none{ background: transparent; }
.shell .u-bg-0,    .u-bg-0{ background: var(--bg-0, var(--bl-bg, #0A0A0C)); }
.shell .u-bg-1,    .u-bg-1{ background: var(--bg-1, var(--bl-bg, #0E0D10)); }
.shell .u-bg-2,    .u-bg-2{ background: var(--bg-2, var(--paper-0, #111014)); }
.shell .u-bg-raised, .u-bg-raised{ background: var(--paper-1, var(--bl-surface-1, #17151A)); }
.shell .u-bg-accent-soft, .u-bg-accent-soft{ background: var(--brass-a08, rgba(201, 169, 110, 0.08)); }

/* ===========================================================================
   BORDER & RADIUS
   No black-label fallback on radius, deliberately: --bl-radius is 0. Black Label
   is a square design and a utility must not fight it.
   =========================================================================== */
.shell .u-border,        .u-border{ border: 1px solid var(--hairline, var(--bl-hairline, #26232B)); }
.shell .u-border-strong, .u-border-strong{ border: 1px solid var(--hairline-2, var(--bl-hairline-strong, #3B3742)); }
.shell .u-border-accent, .u-border-accent{ border: 1px solid var(--brass-a45, rgba(201, 169, 110, 0.45)); }
.shell .u-border-0,      .u-border-0{ border: 0; }
.shell .u-border-t,      .u-border-t{ border-top: 1px solid var(--hairline, var(--bl-hairline, #26232B)); }
.shell .u-border-b,      .u-border-b{ border-bottom: 1px solid var(--hairline, var(--bl-hairline, #26232B)); }
/* An accent bar down the left edge — pull-quote, highlighted note. */
.shell .u-border-l-accent, .u-border-l-accent{ border-left: 2px solid var(--brass-500, var(--bl-gold-500, #C9A96E)); }
.shell .u-border-l-warn,   .u-border-l-warn{ border-left-color: var(--warn, var(--bl-amber, #D9A441)); }
.shell .u-border-l-danger, .u-border-l-danger{ border-left-color: var(--danger, var(--bl-red, #E0806C)); }
.shell .u-r-control, .u-r-control{ border-radius: var(--r-control, 8px); }
.shell .u-r-surface, .u-r-surface{ border-radius: var(--r-surface, 12px); }
.shell .u-r-panel,   .u-r-panel{ border-radius: var(--r-panel, 16px); }
.shell .u-r-pill,    .u-r-pill{ border-radius: var(--r-pill, 999px); }
.shell .u-r-round,   .u-r-round{ border-radius: 50%; }

/* ===========================================================================
   POSITION, OVERFLOW, INTERACTION
   =========================================================================== */
.shell .u-rel,      .u-rel{ position: relative; }
.shell .u-abs,      .u-abs{ position: absolute; }
.shell .u-clip,     .u-clip{ overflow: hidden; }
.shell .u-scroll-y, .u-scroll-y{ overflow-y: auto; }
.shell .u-pointer,  .u-pointer{ cursor: pointer; }
.shell .u-notallowed, .u-notallowed{ cursor: not-allowed; }
.shell .u-resize-y, .u-resize-y{ resize: vertical; }

/* ===========================================================================
   COMPOSITES — idioms that are genuinely more than one property.
   Each one was measured as a fixed group of declarations appearing together;
   splitting them into atoms would guarantee somebody ships three of the four.

   THEY ARE LAST ON PURPOSE, and that has a consequence you must know: a
   composite sets several properties and, being last, WINS over every atomic
   utility above it. `class="u-list-bare u-mb-12"` renders with NO bottom margin,
   because u-list-bare's `margin:0` is declared later than u-mb-12.
   If you need to vary a property a composite sets, do not fight it — use the
   atoms instead: `u-list-none u-p-0 u-m-0 u-mb-12`.
   =========================================================================== */
/* A fully unstyled list: no marker, no browser indent, no browser margin. */
.shell .u-list-bare, .u-list-bare{ list-style: none; margin: 0; padding: 0; }
/* Screen-reader-only. Measured as position+width+height+overflow+clip written
   out in full, four times. One of them was missing `clip`, so it was a visible
   1px artefact rather than hidden text. */
.shell .u-sr-only, .u-sr-only{
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* A horizontal rule drawn as a filled box rather than a border, so it can sit
   in a grid cell. Measured as height + background written as a pair. */
.shell .u-rule, .u-rule{
  height: 1px;
  background: var(--hairline, var(--bl-hairline, #26232B));
  border: 0;
}
.shell .u-rule-2, .u-rule-2{
  height: 2px;
  background: var(--brass-500, var(--bl-gold-500, #C9A96E));
  border: 0;
}
