/* =============================================================================
   width-contract.css  ·  THE WIDTH CONTRACT (shared by all three shells)
   =============================================================================
   ONE named width vocabulary for the whole of service_ui. Before this file the
   site had no width system: fifteen unrelated centring caps, each declared in a
   different file with `margin: 0 auto` (1400, 1240, 1200 x7, 1180, 1160, 1120,
   1080, 1060, 1003, 880, 580), and the largest `min-width` media query in the
   entire public CSS was 1361px (site-chrome.css:356, and it only changes nav
   link size). Every monitor from 1400px to 5120px rendered identical pixels,
   which on a 3440 ultrawide is a 2225px gutter, 65% of the screen.

   The inconsistency is the defect. The empty gutters are its symptom. So the
   deliverable is not "a bigger number". It is one contract every surface
   consumes, after which a width question has exactly one place to be answered.

   WHY A NEW FILE, AND WHY IT IS LINKED FIRST
   -----------------------------------------------------------------------------
   The three shells have three different cascades, and that is load-bearing:

     base.html          design-system.css -> workspace-tokens.css ->
     (logged-in app)    responsive.css -> Bootstrap -> inline <style>.
                        site-tokens.css and site-chrome.css DO NOT load here.

     public_base.html   fonts.css -> site-tokens.css -> site-base.css ->
     (marketing)        site-chrome.css.
                        design-system.css and responsive.css never load here.

     landing.html       standalone. Links site-tokens.css + site-chrome.css via
     (components/       components/landing/head_meta.html, then inlines ~29
      landing/)         partials each carrying its own <style>.

   Promoting site-tokens.css onto the app chain through the empty
   `{% block site_tokens_css %}` hook at base.html:80 would have imported FIXED
   `--space-1..7` onto a chain where responsive.css:90-97 supplies them as fluid
   clamps, plus a `--z-header` collision and a font-stack dependency: a large
   blast radius for a five-token payload. The docblock at base.html:74-79 exists
   to warn about exactly that.

   This file instead declares ONLY `--w-*` names, which existed nowhere in the
   tree before it. Grep for `--w-` across static/css/ and templates/ and the
   only hits are this file and its consumers. That makes it zero-collision on
   all three chains simultaneously, and that property is true only because the
   file is small and new. Keep it that way: nothing that is not a width belongs
   in here.

   Linked first on every chain so it is the foundation the other sheets read,
   never the other way round. Three `?v=` sites, and they are bumped together.

   THE THREE CLASSES OF SURFACE
   -----------------------------------------------------------------------------
   Every surface in service_ui is exactly one of these. Pick the class first,
   then the token follows.

     FILL (Class A)          width:100%, no max-width, padding `--w-gutter`.
                             The site header, the section bands (already
                             full-bleed), the hero film, the workspace chrome.
                             These go edge to edge on any monitor.

     FLUID-CAPPED (Class B)  width:100%, max-width `--w-shell` or `--w-wide`,
                             centred, padding `--w-pad`. The `.w-shell` and
                             `.w-wide` utilities below are exactly this.
                             `--w-shell` is the text-led cap and takes almost
                             everything: `.container`, `.ind-wrap`,
                             `.inst-wrap`, `.act9-wrap`, `.wim__inner`, acts
                             6/7/8, user-choice, act5, `.rd-shell`,
                             workspace-home, `.messages-wrapper`, auth,
                             profile, subscription.
                             `--w-wide` is the composition-led cap and is for
                             surfaces with no reading line to protect: the act8
                             partner wall is a pure logo grid, so it takes this.

     MEASURE-LOCKED (Class C) Cap derived from CHARACTERS, not pixels, and it
                             NEVER GROWS. `--w-measure` is its token.
                             `.rd-shell--doc` and its `.rd-head`, every `ch`
                             measure in site-reading.css, `.wim__copy` (34rem),
                             the act9 FAQ (880 is a measure, not a container
                             number), the act4 terminal (1080, its own
                             monospace column), and `--type-u4/u5/u6`, because
                             16/14/13px body copy does not grow with the
                             monitor.

   WHERE 1440 AND 1920 COME FROM (they are derived, not chosen)
   -----------------------------------------------------------------------------
   --w-shell: 1440. The site's designed text column maximum is 544px
     (head_styles.html:672, `34rem`). Two of those columns side by side, plus
     the section gap at its `clamp(44px, 7vw, 96px)` ceiling:
         544 + 96 + 544                      = 1184
     plus the island's own inner padding at the `--w-pad` ceiling, both sides:
         1184 + (2 x 48)                     = 1280
     which leaves the media column 160px of headroom before the cap:
         1280 + 160                          = 1440

   --w-wide: 1920. A 4-up card grid at the repo's 380px card (about 40ch) with
     24px gaps:
         (4 x 380) + (3 x 24)                = 1592
     plus 2 x 48 padding                     = 1688
     which leaves room for a fifth track rather than stranding the grid at
     exactly four:
         1688 + 232                          = 1920

   Neither number is new to this repo: responsive.css:180 already reaches for
   1440 and chat.css:3507 for 1920.

   What that buys, content width and gutter per side:

     vw    --w-shell content  gutter      --w-wide content  gutter
     1440       1344          0   (0%)        1344          0   (0%)
     1920       1344          240 (25%)       1824          0   (0%)
     2560       1344          560 (43.8%)     1824          320 (25%)
     3440       1344          1000 (58.1%)    1824          760 (44.2%)

   Note what the table also says: widening containers ALONE does not fix the
   ultrawide gutter. At 3440, moving a 1200px island to 1440 takes the gutter
   from 65% to 58%. Seven points of sixty-five. The other fifty-eight have to
   come from surfaces that genuinely fill, from grids that gain columns, and
   from a type and space scale that keeps growing past 1600. An `auto-fit` grid
   at `minmax(320px, 1fr)` / 24px gap goes from 3 tracks at `--w-shell` to 5 at
   `--w-wide` with no media query at all, so part of the fix is removing an
   artificial cap rather than adding machinery.

   WHAT ACTUALLY SHIPPED, AND WHERE IT DIFFERS FROM THE PLAN        (S11, final)
   -----------------------------------------------------------------------------
   This docblock is the specification, so it records the built system rather than
   the proposed one. Five things came out different, every one of them because a
   number was measured on the running app instead of reasoned about.

   1. THE HEADER CAP IS --w-shell, NOT --w-wide. The plan proposed --w-wide, on
      the reasoning that chrome should meet the widest content on the page. It
      should meet the content the visitor is actually reading, and there is
      exactly ONE --w-wide element on the whole site: `.a8p-inner`, the act8
      partner logo wall. A dozen sections sit at --w-shell. At 2560 (layout
      width 2545) the two candidate edges are 552.5px and 312.5px from the
      viewport, so anchoring to --w-wide would have aligned the header with one
      logo wall and disagreed with everything else by 240px.
      See site-chrome.css:94-128.

   2. THE RAILS ARE CONTENT-ANCHORED ABOVE THEIR GATES, viewport-pinned below.
      `.mrail` (landing) anchors against --w-wide, because it indexes a page
      whose widest island is the partner wall; `.irail` and `#instrail` anchor
      against --w-shell + --w-pad, because /individual and /institutional have
      no wide tier. Below the gate each formula's `max()` returns the old
      viewport clamp unchanged, so the change is a provable no-op across the
      entire current user population rather than a re-tuning of live geometry.
      The two always-on-label breakpoints (1600 on /individual, 1700 on
      /institutional, both derived against the old 1200 column) collapsed into
      one re-derived number, because they were always the same question.
      See head_styles.html:1486-1519, individual.css:1062-1099,
      institutional.css:1150-1178.

   3. THE HERO SCALES WITH THE TIER BUT KEEPS AN EDGE ANCHOR, and it is the one
      surface on the site that does. Its copy column grows with the >=1600 tier
      (`width: clamp(580px, 36.25vw, 714px)`) and its left inset reuses the
      header's own >=1600 gutter formula verbatim, but it does NOT chase the
      header's CONTENT edge. Matching that edge would put the headline at
      x 992..1687 at 3440, straight over the film's subject, and would need the
      scrim held opaque to about 1750px to stay WCAG-legible. Erasing the
      photography to fix an alignment is the wrong trade, so the resulting 928px
      disagreement between the brand mark and the headline at 3440 is real, is
      visible, and is reported rather than papered over. Note also: never
      position this element with `transform`. scrub-engine.js:394 writes
      `style.transform` inline every animation frame.
      See head_styles.html:102-145.

   4. THE APP CHAIN GOT CONTAINERS WITHOUT A TYPE TIER. `base.html`'s inline
      `.container`, `components/header.html`'s `.nav-container` (a SIXTEENTH
      centring cap the plan's fifteen-file audit missed, because it lives in a
      template and not in static/css/) and the `/workspace` home islands all
      moved onto --w-shell. The >=1600 second-segment token block did NOT follow
      them, and that asymmetry is deliberate rather than an omission: that block
      re-declares `--type-d*`, `--type-u*`, `--section-pad-*` and `--sh-px`,
      and NONE of those tokens exist on the app chain. site-tokens.css does not
      load there (see the cascade note above), so there is nothing to
      re-declare. Widening the app's islands without a type tier is the right
      answer for a workspace, where the content is chat turns and tiles rather
      than display type, but it does mean the two chains do not behave
      identically above 1600 and a reader should not expect them to.
      See base.html:170-186, components/header.html:97-103,
      workspace-home.css:43-69.

   5. --ws-measure IS 58ch, AFTER A TWO-CORPUS SWEEP. That token
      (workspace-tokens.css:163) is the app chain's reading measure and is
      consumed in exactly one place, `.pm-prose`, which is carried by chat's
      `.message-text` and by both editor previews. It shipped at 68ch on a 16px
      Inter element, which is 686.4px and renders 82 characters: a live WCAG SC
      1.4.8 breach on three surfaces from the day it shipped. The correction is
      58ch, and the reason it is not 66ch is the second corpus: 66ch clears the
      ceiling on analyst prose (78) and renders 84 on short common words. These
      three surfaces display text nobody reviews before it appears, so the value
      has to hold against the density bound, not against a comfortable sample.
      A sweep that used one corpus recommended a value that was still a breach.

   AND THE ONE THING THE WHOLE EXERCISE KEPT PROVING. Seven live WCAG SC 1.4.8
   breaches were found across these packages, on `/faq`, `/privacy-policy`,
   `/contact`, `/pitch-deck`, `/terms-of-service`, the three workspace prose
   surfaces, and finally the shared footer plus the landing, `/individual` and
   `/institutional`. Every single one had the same root cause: a length in `ch`
   declared on an element whose font-size is not the font-size of the text it
   governs, or a `px` cap on a wrapper around smaller type. `ch` is the advance
   of the "0" glyph AT THE FONT-SIZE OF THE ELEMENT THE LENGTH IS USED ON. Never
   reason about a `ch` value; declare it on the element that carries the face and
   sweep it with test/e2e/width_audit.py's tripwire. Two committed gates now
   enforce this file: that tripwire for the reading measures, and
   test/test_width_contract_ratchet.py for the containers.

   THE SUPPORT CEILING IS 3440, DELIBERATELY
   -----------------------------------------------------------------------------
   Above 3440 every token here is already at its clamp ceiling, so a 5120 screen
   renders as 3440 does with larger gutters. That is a decision, not an
   oversight. If someone later wants 5120 to be a first-class width, that is a
   new tier with its own measured derivation, not a number nudged in this file.

   NEVER `100vw`. USE `%`.
   -----------------------------------------------------------------------------
   `100vw` includes the classic scrollbar; `%` does not. On Windows, where the
   scrollbar is 15px and not an overlay, any width built on `100vw` is 15px
   wider than the viewport and produces a horizontal scroller on every page that
   uses it. design-system.css:1225 already carries this house rule
   in a comment above `.r-bg-cover` that reads "Never use 100vw for width, use
   this instead". Every formula in this contract and in everything that
   consumes it uses `%`, which resolves against the containing block's inline
   size and excludes the scrollbar.

   (That sentence used to quote the rule verbatim, comment delimiters and all.
   The nested delimiter closed this docblock sixty lines early, the `:root`
   block below never parsed, and all six tokens read back as absent on every
   surface. The harness caught it because it records tokens alongside
   geometry. Do not paste a CSS comment into a CSS comment.)

   The measurement harness (test/e2e/width_audit.py) asserts left/right gutter
   symmetry within 1px and `scrollWidth === clientWidth` at eight widths for
   exactly this reason, and it deliberately runs chromium WITHOUT
   `--hide-scrollbars` so the scrollbar is real and the bug can manifest.

   PROTECTED: DO NOT WIDEN
   -----------------------------------------------------------------------------
   site-reading.css IN FULL. `/terms-of-service` and `/privacy-policy` ARE
   CORRECT AS THEY ARE, and their large gutters on a wide monitor are the right
   answer, not a bug.

   `.rd-shell--doc`'s `--measure: 68ch` came from a measured sweep of RENDERED
   characters per line against WCAG SC 1.4.8's 80-character ceiling:

       66ch -> 77 chars      68ch -> 78 chars  (chosen)
       70ch -> 81 chars OVER 72ch -> 83 chars OVER

   The docblock at site-reading.css:274-281 explicitly evaluated widening on
   large viewports and REJECTED it, and that change was itself the fix for a
   recorded client complaint. Widening re-opens the defect it closed. This is
   the highest-ranked risk in the whole overhaul precisely because widening
   those pages looks correct to anyone who has not read that docblock.

   `width: 90%` is not the alternative, either: at 3440 that is a 3096px
   paragraph, roughly 330 characters per line. And fifteen different percentages
   are exactly as inconsistent as fifteen different pixel caps. The unit is not
   the disease.

   Worth generalising rather than protecting in isolation: site-reading.css:
   315-330 already replaced a magic 1160 with `--rd-group = measure + gap +
   rail`, a DERIVED width. This contract is that idea applied to the other
   fourteen surfaces, not a new idea imposed on the codebase.

   DO NOT RESURRECT THE PREVIOUS ATTEMPT
   -----------------------------------------------------------------------------
   About 1300 lines of an earlier ultrawide attempt already exist in the tree,
   and more of it existed until recently:

     responsive.css:1218-2077   a full 1920 / 1440-1919 / 1024-1439 ladder
                                targeting `.hero-quantum`, `.bento-grid`,
                                `.stats-container`, classes no template
                                renders. Still on disk; not deleted, because
                                that same range carries the only desktop-width
                                styling for the `.quantum-footer` family
                                (quantum-footer.css has max-width 767 and 480
                                queries and nothing above), and a live
                                `.stat-label` rule, rendered by
                                templates/workspace/chat_view.html on a
                                base.html-chain page. It is dead in parts, not
                                in whole.

                                CORRECTED 2026-08-25. This note used to give a
                                THIRD reason, that the range carried the only
                                `html,body{overflow-x:clip}` covering
                                workspace.html / profile.html / error.html.
                                That reason was retracted to the client on
                                2026-08-22 and it was wrong on two counts, both
                                re-verified before this edit. First,
                                design-system.css:538-542 declares the same
                                `html, body { overflow-x: clip; width: 100%;
                                max-width: 100% }` and is linked at
                                base.html:94, AHEAD of responsive.css at
                                base.html:102, so every page on that chain,
                                workspace.html:1 and profile.html:1 included,
                                already has the rule from there. Second,
                                error.html:1 extends public_base.html, which
                                links site-base.css (public_base.html:181) and
                                never responsive.css, so error.html was never
                                on this chain to be covered by it; its
                                `overflow-x: clip` comes from site-base.css:67.
                                The VERDICT is unchanged, the range still may
                                not be deleted, but it survives on the two
                                reasons above and not on that one. Left
                                standing, it is this repo's own documented
                                failure mode: a file claiming a control it does
                                not have.

     landing-components.css     791 lines including its own 1920 tier, linked by
                                no template at all. Deleted in package S1a.

   Both encode the OLD model: independent container variables
   (responsive.css:104-108) bolted into hand-written tiers. Resurrecting either
   ships a second, incompatible width system alongside this one, which is the
   exact disease this file cures. Their value is as evidence that someone tried
   this before and it never shipped, and that belongs in this docblock, not in
   the cascade.
   ============================================================================= */

:root {
  /* Page-edge gutter for FILL surfaces. Class A pads with this; it never caps. */
  --w-gutter: clamp(20px, 5vw, 80px);

  /* Inner padding of a capped island. Both utilities below use it, and its 48px
     ceiling is the 2 x 48 that appears in the --w-shell derivation above. */
  --w-pad: clamp(20px, 4vw, 48px);

  /* MEASURE-LOCKED, frozen. Characters, not pixels. See PROTECTED above:
     68ch is a measured value with a WCAG sweep behind it, and 70 was over. */
  --w-measure: 68ch;

  /* FLUID-CAPPED, text-led. The default cap for almost everything. */
  --w-shell: 1440px;

  /* FLUID-CAPPED, composition-led. Only for surfaces with no reading line to
     protect, such as a pure logo grid. */
  --w-wide: 1920px;

  /* FILL, declared rather than omitted, so a Class A surface says out loud that
     it fills instead of merely not saying anything. `%`, never `100vw`. */
  --w-full: 100%;
}

.w-shell {
  width: 100%;
  max-width: var(--w-shell);
  margin-inline: auto;
  padding-inline: var(--w-pad);
}

.w-wide {
  width: 100%;
  max-width: var(--w-wide);
  margin-inline: auto;
  padding-inline: var(--w-pad);
}
