/*! FinTruvix customer design system.
    ===========================================================================================================
    SOURCE OF TRUTH for the palette + base UI. Customer Blade uses var(--token) — never a hardcoded hex — so a
    palette change is a one-file edit. partials/theme.blade.php links this with a content-hash ?v=, so any edit
    busts the cache with no hard refresh. Filament (staff) theming is SEPARATE and must not be coupled to this.

    DESIGN LANGUAGE — derived from DESIGN.md (Apple analysis), with three other skills governing the rest:

      • DESIGN.md .................. visual language: palette, type scale, shape, elevation philosophy.
      • ui-ux-pro-max .............. page pattern (Marketplace / Directory), UX + accessibility rules, and the
                                     Laravel/Blade/Livewire stack rules.
      • Web Interface Guidelines ... the compliance gate (focus, motion, forms, typography, anti-patterns).
      • composition-patterns ....... component API design, translated from React to Blade components.

    WHERE THEY DISAGREED, AND WHY THIS RESOLVES IT THE WAY IT DOES:

      1. ui-ux-pro-max's colour search returned gold + purple on a dark canvas and listed "light mode default"
         as an anti-pattern. DESIGN.md mandates ONE accent (Action Blue) on a light-dominant canvas and forbids
         a second. DESIGN.md wins on palette — but note the two agree where it counts: ui-ux-pro-max's PRODUCT
         match for a two-sided marketplace says "Trust: Blue/Green" and "verified badge". The gold came from a
         generic style row, not from the product. Both modes still ship, because this app has a live toggle and
         removing it would be a regression; light is simply the default now.
      2. DESIGN.md lists dark-mode counterparts as a Known Gap. They are DERIVED here, against ui-ux-pro-max's
         dark-contrast rules (primary text >= 4.5:1, secondary >= 3:1, borders visible in BOTH themes).
      3. DESIGN.md's ink-48 (#7a7a7a) is 4.29:1 on white and 3.94:1 on parchment — below the 4.5:1 both
         ui-ux-pro-max and WCAG require. Fine print now uses #6e6e73 (Apple's own systemGray secondary label,
         4.66:1 on the worst light surface). #7a7a7a survives ONLY on disabled controls, which WCAG 1.4.3
         explicitly exempts. Being Apple-accurate is not worth shipping text people cannot read.

    THE ADAPTATION. Apple's system is photography-first: chrome recedes so the PRODUCT can speak, and the one
    shadow in the entire system exists to give a product render weight. FinTruvix has no product photography —
    it sells EVIDENCE: a Trust Score, a track record, a fee, a realised P&L. So the thesis is translated, not
    copied: THE NUMBER IS THE ARTIFACT. Figures get the display treatment, the tabular alignment and the single
    shadow; everything around them stays flat, hairline-bordered and quiet. That is the one deliberate risk in
    this system, and it comes from the brief rather than from decoration.
    =========================================================================================================== */

/* ================================ LIGHT — the default canvas ================================ */
:root {
    color-scheme: light;

    /* Surfaces. Parchment (#f5f5f7) is the signature Apple off-white: just different enough from
       white to create rhythm without a border. */
    --canvas: #ffffff;
    --canvas-alt: #f5f5f7;
    --surface: #ffffff;
    --surface-2: #fafafc;
    --surface-3: #f5f5f7;

    /* Dark bands. These are SECTION SURFACES, not a theme — they stay dark in both modes, because the
       alternation itself is the divider (DESIGN.md: "the color change IS the divider").
       Only `.auth-aside` still uses them; the `.band--*` classes themselves have no consumer left.
       The marketing ledger's chambers moved to --chamber below — see the note there. */
    --band-dark: #272729;
    --band-dark-2: #2a2a2c;
    --band-dark-3: #252527;
    --void: #000000;

    /* THE LEDGER'S CHAMBER. The marketing ledger punctuates itself with a section that is a TONE STEP
       from the page — DESIGN.md's "the color change IS the divider". That principle is kept; what
       changed is that the step is now taken WITHIN the active theme instead of jumping to near-black
       in both. It had to: the chamber tone was authored while DARK was the default, and once light
       became the shipped default the same rule painted three full-bleed near-black slabs across an
       otherwise light page (measured: 2,188px of 6,900px on the home page, the hero among them).
       Because the chamber now follows the theme, everything inside it can use the ORDINARY tokens and
       needs no inversion — which is why `.led--dark` is no longer in the permanently-dark list below.

       THE TONE IS PINNED BY CONTRAST, NOT BY EYE. The row key is 11px --text-muted (#6a6a6e), which
       is the tightest thing the chamber has to carry, so the ground may not go below L .828 or that
       key drops under 4.5:1. The first value tried here (#e9eae4) measured 4.45:1 — it looked fine
       and failed. #ecede7 is 4.57:1 for the key and 4.60:1 for --accent-text, and still a clear
       .084 step down from the canvas. Do not darken it without re-measuring both. */
    --chamber: #ecede7;
    --chamber-line: #d5d6d0;   /* a hairline that stays visible ON that ground */

    /* THE CREDENTIAL. One palette, two consumers: the home page's sample card (HTML) and the passport
       page's real card (SVG). They are the same artifact and must not drift, so neither one carries
       its own colours. The SVG cannot read these directly — a presentation attribute does not resolve
       var() — so it maps them through a <style> block it carries itself, and the PNG/PDF export
       inlines the resolved values onto the clone before serialising. See the passport view. */
    --cred-bg: #ffffff;
    --cred-line: #d8d8dc;
    --cred-text: #1d1d1f;
    --cred-text-2: #47474a;
    --cred-text-3: #6a6a6e;
    --cred-accent: #4d7c0f;
    --cred-on-accent: #ffffff;
    --cred-chip-bg: #f2f2f4;
    --cred-chip-line: #d0d0d6;
    --cred-ghost: #b9b9c0;     /* the un-issued dashed outline */
    --cred-meter: #dcdce0;     /* the empty half of a score meter */
    --cred-divider: #e4e4e8;
    /* Status-pill INK on the credential. The tint behind it is the same hue at 15% and works on
       either ground; the ink does not — the night values are 1.9:1 on a white card. These are the
       darkened pair the P&L tokens already use, for the same reason. */
    --cred-ok-ink: #0b7a3e;
    --cred-warn-ink: #8a5300;
    --cred-err-ink: #b3261e;

    /* Text. One near-black for everything on light, exactly as Apple does — #1d1d1f rather than pure
       black keeps the page photographic rather than printed. */
    --text: #1d1d1f;
    --text-2: #333333;
    --text-muted: #6a6a6e;
    --text-faint: #6a6a6e;
    --text-disabled: #7a7a7a;   /* WCAG 1.4.3 exempts disabled controls — the ONLY place this is allowed */
    --on-dark: #ffffff;
    --on-dark-2: #cccccc;

    /* Hairlines. Apple has no card shadows; separation is a 1px line or a surface change.
       --hairline is DECORATIVE (a card edge) so it may be as light as Apple draws it. --field-border is
       not: WCAG 1.4.11 requires 3:1 for the boundary that identifies a form control, and Apple's own
       #c7c7cc is 1.68:1 on white. #86868b (3.62:1, still an Apple system grey) carries it instead. */
    --hairline: #e0e0e0;
    --divider-soft: #f0f0f0;
    --field-border: #86868b;

    /* THE SINGLE ACCENT — green. Every "click me" signal in the product is this and nothing else.
       Light gets the deep lime (#4d7c0f: 4.99:1 as text on white, 4.59:1 on parchment, and it carries
       white at 4.99:1 as a button fill). Dark gets the lemon (see the dark block) — the same brand at
       the brightness each ground needs, which is why --on-accent is a token rather than a constant.

       NOTE ON THE TWO GREENS. The brand lime sits at hue 86; the profit green (--up) sits at hue 148.
       They are deliberately a different family — yellow-green vs true green — because in a trading UI
       a green NUMBER means profit and a green CONTROL means "press this", and those must not read as
       the same statement. Colour is never the only signal either way: a P&L figure always carries its
       sign, and a control always looks like a control. */
    --accent: #4d7c0f;
    --accent-text: #49760e;
    --accent-focus: #3f6212;
    --on-accent: #ffffff;

    /* P&L. Apple's system has no up/down colours — a trading product cannot do without them, so these
       come from Apple's system green/red rather than an invented pair. Green is darkened from Apple's
       #248a3d, which is 4.04:1 on parchment: a P&L figure appears in small table cells as well as at
       display size, so it has to clear 4.5:1, not merely AA-large. Colour is NEVER the only signal —
       every figure carries a +/- sign (ui-ux-pro-max: "color is not the only indicator"). */
    --up: #0b7a3e;
    --down: #d70015;
    --warn: #ae4e00;

    /* PERFORMANCE-CHART SERIES. Two series, and they are deliberately NOT two hues: a performance curve
       carries trading FinTruvix recorded from a connected broker account and trading the strategist imported
       and staff reviewed, and the first is the point. So this is the emphasis pattern — the verified portion
       in the brand accent, the self-reported portion in a de-emphasis grey — rather than a categorical pair,
       which would give a self-reported claim the same visual weight as an audited one.
       Measured, not assumed: separation under deuteranopia is dE 14.5 light / 30.2 dark against the accent
       (floor 8), and both clear 3:1 on their surface. The grey is intentionally below the chroma floor a
       categorical palette would demand — that is what "de-emphasis" means.
       Colour is never the only signal here either: the legend, the tooltip and the table twin all name the
       provenance of every segment. */
    --chart-verified: var(--accent);
    --chart-imported: #8a8a8f;
    --chart-grid: var(--hairline);
    /* Not a second accent. The P/I/T score trio reads as ONE family, so the Integrity meter — the only
       thing that ever used this — takes the brand hue like its two siblings. Kept as a name because
       several views refer to it by meaning rather than by colour. */
    --info: var(--accent);

    /* Status tints — flat washes, no borders doing decorative work. */
    --ok-text: #1a6b34; --ok-bg: #eaf6ee; --ok-border: #bfe3cb;
    --warn-text: #8a3d00; --warn-bg: #fdf0e6; --warn-border: #f3d5bb;
    --err-text: #b3000f; --err-bg: #fdecee; --err-border: #f5c6cb;

    --meter-track: #e8e8ed;
    --scrim: rgba(0, 0, 0, .48);

    /* ============================ LIQUID GLASS — the material ============================
       Glassmorphism is a PAINT: a tint plus a blur, which over flat ground reads as a slightly
       lighter rectangle. That version was built and rejected. Liquid Glass is a MATERIAL, so it is
       four properties rather than one fill, and all four are tokens so a layout consumes them in
       three declarations and the degradation below can switch the whole system off at the root.

         --glass*        the body tint, per nesting depth
         --glass-blur    what the material does to the ground behind it
         --glass-edge    THICKNESS: a lit top edge, a 1px ring, a shaded bottom edge
         --cast*         the material's own weight

       WHY THERE IS NO REFRACTION HERE, having shipped it in the study: `backdrop-filter: url(#f)`
       needs one SVG displacement map per element SIZE to bend at the rim rather than wobble
       everywhere, it is Chromium-only, and it re-runs a filtered backdrop sample on every scroll
       frame. On a dashboard of twenty cards that is a frame-budget decision, not a finish. The rim
       and the thickness are what carry the material at product scale; refraction is available for a
       single chosen hero surface if it is ever worth the cost.

       EVERY VALUE BELOW IS MEASURED, not chosen — see the contrast contract in the dark block. */
    --field: 6%;                                   /* strength of the ambient wash on --canvas */
    --glass:       color-mix(in srgb, #ffffff 72%, transparent);
    --glass-2:     color-mix(in srgb, #ffffff 84%, transparent);
    --glass-3:     color-mix(in srgb, #ffffff 90%, transparent);
    /* A FIXED overlay floats over content nothing constrains, so it takes the densest tint in the
       system: at 94% its link text measured 4.37:1 over a dark band, at 97% it measures 4.68:1
       against PURE BLACK. Transparency has no upside on a control that must always be readable. */
    --glass-dense: color-mix(in srgb, #ffffff 97%, transparent);

    --glass-blur:    saturate(170%) blur(18px);
    --glass-blur-lg: saturate(180%) blur(26px);

    /* The edge does two jobs at once: it bounds the pane AND catches the light at its top, which is
       what makes it read as a pane rather than a wash. Three insets, no gradient border, no extra
       DOM — so it can be applied to markup that already uses ::before/::after (the ledger spine,
       the trust ring, the row counter all do). */
    --rim-hi:   rgba(255, 255, 255, .85);
    --rim-ring: rgba(20, 23, 15, .10);
    --rim-lo:   rgba(20, 23, 15, .06);
    --glass-edge: inset 0 1px 0 var(--rim-hi), inset 0 0 0 1px var(--rim-ring), inset 0 -1px 0 var(--rim-lo);
    --glass-sheen: radial-gradient(132% 104% at 14% -20%, rgba(255, 255, 255, .55), transparent 58%);
    /* SPECULAR — the reflection that follows the cursor across a pane. Real glass catches the light
       source it is under; a static sheen alone reads as a printed highlight. Default position is far
       off the box so the layer is invisible until the pointer is actually over a pane.

       THE ALPHA IS A CEILING, NOT A TASTE. A white bloom ADDS white: on a light pane that only helps
       dark ink, but on a dark pane it lightens the ground under PALE ink. Measured on the worst dark
       pane (#1d2016), --text-muted fails at alpha 0.09 (4.41:1) and the ceiling is 0.06 — so dark
       ships 0.05 and its reflection is genuinely subtle. That is the mirror of the transparency
       asymmetry: dark tolerates more tint and LESS specular. */
    --spec-x: -9999px;   /* off the box, so the layer is invisible until a pointer arrives */
    --spec-y: -9999px;
    --spec-size: 340px;
    /* THE BLOOM IS A DIFFERENT COLOUR IN EACH THEME, and measurement forced that. On dark it is
       WHITE and brightens (+10.7 levels, clearly read). On light a white bloom has nowhere to go —
       the pane is already #f9faf7, and #fcfcfc -> #ffffff is at most +3 levels, below the ~2-3 level
       just-noticeable difference for a large smooth area, so it was invisible. Light therefore gets a
       faint LIME CAUSTIC that darkens slightly instead: -10.6 levels, brand-coherent, and reading as
       light bent through glass rather than as a grey smudge.
       Both are ceilings, not tastes: white on the worst dark pane fails --text-muted at .09 (ceiling
       .06, ships .05); lime on the worst light pane fails at .12 (ceiling .10, ships .06). */
    --spec-color: rgba(77, 124, 15, .06);

    /* NOTE, and it cost an hour: there is deliberately NO `--glass-spec` template token holding the
       whole gradient. A custom property's var() references are substituted WHERE IT IS DECLARED, not
       where it is used — so a `--glass-spec` declared on :root resolves its var(--spec-x) against
       :root ONCE (to -9999px) and inherits the already-substituted token stream down. The cursor
       position and the dark-chamber alpha override would both be invisible. Each pane rule therefore
       writes the gradient itself, and only the SCALARS travel as tokens. */


    --cast:    0 1px 2px rgba(20, 23, 15, .05), 0 10px 28px -10px rgba(20, 23, 15, .16);
    --cast-lg: 0 2px 6px rgba(20, 23, 15, .06), 0 20px 48px -14px rgba(20, 23, 15, .22);

    /* THE REDIRECTION. Every surface in the product paints from these three, so this is what carries
       the material into ~40 component classes across four layouts without touching one of them —
       and what lets the escape hatches at the foot of this file undo it globally. */
    --surface:   var(--glass);
    --surface-2: var(--glass-2);
    --surface-3: var(--glass-3);
    --hairline:  var(--rim-ring);
    /* The ground the panes sit on. It cannot be pure white: a white pane at 72% over a white ground
       is invisible, so the canvas steps back to a lime-biased off-white and the panes read lighter
       than it. */
    --canvas:     #F6F7F3;
    --canvas-alt: #EDEEE7;

    /* Shape (DESIGN.md rounded scale). No values between these — mixing radii grammars is called out
       explicitly in the Don'ts. */
    --r-none: 0px;
    --r-xs: 5px;
    --r-sm: 8px;
    --r-md: 11px;
    --r-lg: 18px;
    --r-pill: 9999px;

    /* Spacing — 8px base, with Apple's 17px body step. */
    --s-xxs: 4px; --s-xs: 8px; --s-sm: 12px; --s-md: 17px;
    --s-lg: 24px; --s-xl: 32px; --s-xxl: 48px; --s-section: 80px;

    --maxw: 1120px;
    --maxw-wide: 1440px;
    --maxw-read: 720px;

    /* Elevation. There is EXACTLY ONE shadow in this system and it belongs to the artifact — in Apple's
       original, a product render; here, the figure that carries the platform's claim. Never on chrome,
       cards, buttons or text. */
    --shadow-artifact: 0 6px 38px 4px rgba(0, 0, 0, .16);
    /* SOLID, not a 45%-alpha wash. WCAG 1.4.11 asks for 3:1 on the indicator that shows which control
       has focus, and mixing the accent down to 45% composited to 2.11:1 on white and 2.05:1 on
       parchment — the ring was legible enough to look designed and not legible enough to do its job.
       At full strength it is 6.50:1 on the worst light surface and 11.07:1 on the worst dark one. */
    --ring: 0 0 0 3px var(--accent-focus);

    --dur-fast: 150ms;
    --dur: 220ms;
    --ease: cubic-bezier(.4, 0, .2, 1);
}

/* ================================ DARK — derived counterpart ================================
   DESIGN.md documents the daytime system only; this is the derived night one. Every value was checked
   against the worst surface it can land on, not the most flattering. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --canvas: #000000;
    --canvas-alt: #1d1d1f;
    --surface: #1d1d1f;
    --surface-2: #252527;
    --surface-3: #2a2a2c;

    /* The chamber keeps exactly the tone the ledger has always used — on this theme the page was
       already dark, so nothing about the night design changes. */
    --chamber: #272729;
    --chamber-line: #3a3a3c;

    /* The credential's night set: the values the card shipped with, now reached through tokens. */
    --cred-bg: #1D1D1F;
    --cred-line: #38383A;
    --cred-text: #F5F5F7;
    --cred-text-2: #A1A1A6;
    --cred-text-3: #8E8E93;
    --cred-accent: #C7EA46;
    --cred-on-accent: #14210A;
    --cred-chip-bg: #2F2F31;
    --cred-chip-line: #48484A;
    --cred-ghost: #3A3A3C;
    --cred-meter: #4A4A4C;
    --cred-divider: #333335;
    --cred-ok-ink: #6EE7A8;
    --cred-warn-ink: #FFCF70;
    --cred-err-ink: #FF8A80;

    --text: #f5f5f7;
    --text-2: #d2d2d7;
    /* #98989d, not Apple's #8e8e93 — the latter is 4.39:1 on the deepest raised surface (#2a2a2c). */
    --text-muted: #98989d;
    --text-faint: #98989d;
    --text-disabled: #636366;

    --hairline: #38383a;
    --divider-soft: #2a2a2c;
    --field-border: #828287;   /* 4.40:1 on the field fill — same 1.4.11 reasoning as light */

    /* The lemon set. A deep-lime fill would go muddy against near-black, so on dark the accent INVERTS:
       a bright fill carrying dark ink, which is the pairing this project shipped before and the reason
       --on-accent is a token. #C7EA46 is 10.43:1 on the deepest raised surface and carries #14210A at
       12.21:1, so the button is the most legible element on the page rather than the least. */
    --accent: #C7EA46;
    --accent-text: #C7EA46;
    /* Brighter than --accent, not darker. A hover has to CHANGE something: setting this equal to the
       base fill (the obvious-looking choice on an already-bright accent) silently removes hover
       feedback from every button in the product. #DCF57E is a visible step up and still carries the
       ink at 13.91:1. It also has to stay legible as the focus ring, which is derived from it. */
    --accent-focus: #DCF57E;
    --on-accent: #14210A;

    --up: #30d158;
    --down: #ff6961;   /* Apple's #ff453a is 4.20:1 on #2a2a2c; this is its lighter system variant */
    --warn: #ff9f0a;
    --info: var(--accent);

    /* The same grey reads correctly on both grounds (3.4:1 on #1d1d1f, and dE 30.2 from the lemon accent
       under deuteranopia), so it is not re-stated — only the note is, because a reader of the dark block
       should not conclude the pairing was left unchecked here. See the light block for the full rationale. */
    --chart-verified: var(--accent);
    --chart-imported: #8a8a8f;
    --chart-grid: var(--hairline);

    --ok-text: #6ee7a8; --ok-bg: rgba(48, 209, 88, .14); --ok-border: rgba(48, 209, 88, .34);
    --warn-text: #ffcf70; --warn-bg: rgba(255, 159, 10, .14); --warn-border: rgba(255, 159, 10, .34);
    --err-text: #ff8a80; --err-bg: rgba(255, 69, 58, .14); --err-border: rgba(255, 69, 58, .34);

    --meter-track: #38383a;
    --scrim: rgba(0, 0, 0, .66);


    /* ---------------------------- LIQUID GLASS — night ----------------------------
       THE CONTRAST CONTRACT, measured worst-case (scratchpad/verify_site.py). A translucent pane's
       real background is whatever shows through, so every figure is the WORST composite the ambient
       field can produce, never the typical one:

         LIGHT  field 8% -> #e8ece2   pane #f9faf7 .. #fcfcfc
                body 16.03  secondary 12.03  fine print 4.83  link 4.76  profit 5.17  loss 5.13
                warn 4.95  field border 3.45          (floor for this field was 41% — 72% ships)
         DARK   field 10% -> #1f2311  pane #151713 .. #1d2016
                body 15.16  secondary 10.96  fine print 5.75  link 12.02  profit 8.17  loss 5.85
                warn 8.03   field border 4.32          (floor was 35% — 58% ships)

       So there is margin on both, deliberately: the floor is where a nested pane, a busier ground or
       a future darker accent would start failing. GLASS IS ASYMMETRIC between the themes — light
       tolerates mild frost, dark tolerates real glass — because on a light canvas the physics runs
       out of contrast first. That is a property of the material, not a compromise.

       The dark-band scope further down inverts the whole set: a LIGHT pane on the ledger's dark
       chamber composites to #c3c3c3 and takes link text to 2.82:1, a real failure in the SHIPPED
       DEFAULT theme. Inverted it measures 5.58:1. */
    --field: 10%;
    --glass:       color-mix(in srgb, #1C1E19 58%, transparent);
    --glass-2:     color-mix(in srgb, #23261F 70%, transparent);
    --glass-3:     color-mix(in srgb, #292C24 78%, transparent);
    --glass-dense: color-mix(in srgb, #1C1E19 96%, transparent);

    --glass-blur:    saturate(160%) blur(18px);
    --glass-blur-lg: saturate(170%) blur(26px);

    /* Inverted polarity: on dark the lit edge is a pale hairline and the shaded edge is near-black.
       Same two jobs, opposite sign — the --on-accent lesson applied to an edge. */
    --rim-hi:   rgba(255, 255, 255, .14);
    --rim-ring: rgba(255, 255, 255, .12);
    --rim-lo:   rgba(0, 0, 0, .45);
    --glass-edge: inset 0 1px 0 var(--rim-hi), inset 0 0 0 1px var(--rim-ring), inset 0 -1px 0 var(--rim-lo);
    --glass-sheen: radial-gradient(132% 104% at 14% -20%, rgba(255, 255, 255, .07), transparent 58%);
    --spec-color: rgba(255, 255, 255, .05);   /* ceiling .06 — see the light block */

    --cast:    0 1px 2px rgba(0, 0, 0, .40), 0 10px 30px -10px rgba(0, 0, 0, .55);
    --cast-lg: 0 2px 8px rgba(0, 0, 0, .45), 0 22px 54px -14px rgba(0, 0, 0, .62);

    --surface:   var(--glass);
    --surface-2: var(--glass-2);
    --surface-3: var(--glass-3);
    --hairline:  var(--rim-ring);
    --canvas:     #0C0D0B;
    --canvas-alt: #131510;

    --shadow-artifact: 0 8px 42px 5px rgba(0, 0, 0, .62);
}

/* ================================ LEGACY TOKEN BRIDGE ================================
   The previous system ("Lemon Green on Graphite") named its tokens differently, and 58 view files
   still reference those names. These aliases are the MIGRATION MECHANISM, not debt: they re-point
   every existing surface at the new language in one move, and — because they are the only definition
   of those names — they ENFORCE the new rules globally rather than file by file.

     • --grad-* collapse to a flat colour. DESIGN.md bans gradients outright, so every gradient in the
       codebase disappears here instead of needing 12 separate edits.
     • --shadow, --shadow-sm, --shadow-lg, --shadow-primary collapse to none. This system has ONE
       shadow and it belongs to the artifact; card shadows are the single loudest difference between
       the old look and this one.
     • --primary maps to --accent-text, not --accent. Of its 196 uses, 135 are text/border/icon and
       most of the rest are color-mix() tints that sit UNDER that same text — so the text-safe value
       is the correct one. The handful of solid fills that pair with --on-primary live in the layouts,
       which are rewritten to native tokens.

   As each surface is rewritten to native tokens its aliases stop being read. Delete a line here only
   once nothing references it. */
:root, :root[data-theme="dark"] {
    --primary: var(--accent-text);
    --primary-hover: var(--accent-focus);
    --on-primary: var(--on-accent);
    --bg: var(--canvas);
    --border: var(--hairline);
    --border-strong: var(--field-border);
    --gray-200: var(--hairline);
    --danger-600: var(--down);
    /* `--glass` and `--glass-border` were aliases here, and they are DELETED rather than repointed:
       nothing in the tree read either (checked), and this rule is later in source with
       :root[data-theme="dark"] in its selector list — so at (0,2,0) it silently outranked the real
       --glass defined in the dark token block above and reverted the whole night theme to an 82%
       tint of the canvas. The name now belongs to the material, not to the bridge. */

    --grad-primary: var(--accent);
    --grad-accent: var(--accent);
    --grad-hero: var(--canvas-alt);

    --shadow: none;
    --shadow-sm: none;
    --shadow-lg: none;
    --shadow-primary: none;

    --radius-sm: var(--r-xs);
    --radius: var(--r-sm);
    --radius-lg: var(--r-md);
    --radius-xl: var(--r-lg);
    --radius-pill: var(--r-pill);
}

/* ================================ BASE ================================ */

*, *::before, *::after { box-sizing: border-box; }

html {
    scroll-behavior: smooth;
    /* WIG: heading anchors need clearance under the sticky bar. */
    scroll-padding-top: 76px;
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    /* DESIGN.md's own first-choice substitute stack: on Apple platforms this resolves to the REAL SF Pro,
       and everywhere else to the platform UI face (Inter if present). Deliberately no webfont — a CDN
       request would add a third-party dependency, a preconnect and a CLS risk to a build-less stylesheet,
       and buy nothing on the platforms that matter most here. */
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI", Roboto, Inter,
        "Helvetica Neue", Arial, sans-serif;
    /* Apple runs body at 17px, not 16 — the extra pixel is the brand's reading pace. */
    font-size: 17px;
    font-weight: 400;
    line-height: 1.47;
    letter-spacing: -.374px;
    /* THE AMBIENT FIELD. Frosted glass over flat colour is invisible — with nothing behind it to
       tint, a translucent pane reads as a slightly grubby opaque one, which is precisely how the
       glassmorphism attempt failed. So the ground carries a soft wash for the material to work
       against. Deliberately quiet (8% light / 10% dark): the sweep showed a STRONGER field forces a
       DENSER pane to stay readable, so loudness here is paid for in transparency there.
       `fixed` anchors it to the viewport — one paint, not one per scroll step — and the pools use
       ONLY the brand lime (hue 86); a page-wide wash in the profit hue would make the whole product
       read as "up". */
    background-color: var(--canvas);
    background-image:
        radial-gradient(62% 46% at 4% 0%,   color-mix(in srgb, var(--accent) var(--field), transparent) 0%, transparent 68%),
        radial-gradient(52% 40% at 98% 6%,  color-mix(in srgb, var(--accent) calc(var(--field) * .60), transparent) 0%, transparent 66%),
        radial-gradient(74% 54% at 84% 98%, color-mix(in srgb, var(--accent) calc(var(--field) * .48), transparent) 0%, transparent 70%);
    background-attachment: fixed;
    background-repeat: no-repeat;
    color: var(--text);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* WIG: prevents the 300ms double-tap zoom delay on touch. */
a, button, [role="button"], input, select, textarea, summary, label { touch-action: manipulation; }

a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
/* WIG: :focus-visible, never :focus — no ring on a mouse click. */
a:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-xs); }

::selection { background: color-mix(in srgb, var(--accent) 22%, transparent); }

img, svg, video { max-width: 100%; }
img { height: auto; }

/* Display type. Apple's signature is weight 600 with NEGATIVE tracking — never 700, never 500
   (its ladder is 300/400/600/700 with 500 deliberately absent). */
h1, h2, h3, h4, h5, h6 {
    font-weight: 600;
    /* WIG: prevents widows on headings. */
    text-wrap: balance;
    margin: 0 0 var(--s-sm);
}
h1 { font-size: clamp(34px, 5vw, 56px); line-height: 1.07; letter-spacing: -.02em; }
h2 { font-size: clamp(28px, 3.4vw, 40px); line-height: 1.10; letter-spacing: -.015em; }
h3 { font-size: 24px; line-height: 1.2; letter-spacing: -.01em; }
h4 { font-size: 21px; line-height: 1.19; letter-spacing: -.008em; }
p { margin: 0 0 var(--s-md); }
strong, b { font-weight: 600; }

/* Every figure in the product is tabular — a column of scores or prices must align on the decimal.
   WIG asks for this on number columns; here it is the house style, because the number is the artifact. */
.num, .fig, td.num, th.num { font-variant-numeric: tabular-nums; }

code, kbd {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: .88em;
    background: var(--surface-3);
    padding: 2px 6px;
    border-radius: var(--r-xs);
}

hr { border: 0; border-top: 1px solid var(--hairline); margin: var(--s-xl) 0; }

summary { list-style: none; cursor: pointer; }
summary::-webkit-details-marker { display: none; }

/* Screen-reader-only, and the skip link WIG requires for keyboard users. */
.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;
}
.skip-link {
    position: absolute; left: var(--s-sm); top: -100px; z-index: 200;
    padding: 10px 18px; border-radius: var(--r-sm);
    background: var(--accent); color: var(--on-accent); font-size: 14px; font-weight: 600;
    transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: var(--s-sm); text-decoration: none; }

/* ================================ TYPE UTILITIES ================================ */

.eyebrow, .kicker, .eyebrow-inline {
    display: block;
    font-size: 12px; font-weight: 600; line-height: 1;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: var(--s-sm);
}
.lead { font-size: clamp(19px, 2vw, 28px); font-weight: 400; line-height: 1.28; letter-spacing: 0; color: var(--text-2); }
.lead-airy { font-size: clamp(18px, 1.8vw, 24px); font-weight: 300; line-height: 1.5; letter-spacing: 0; color: var(--text-2); }
.tagline { font-size: 21px; font-weight: 600; line-height: 1.19; letter-spacing: -.008em; }
.muted { color: var(--text-muted); font-size: 15px; line-height: 1.47; }
.fine { font-size: 12px; line-height: 1.4; color: var(--text-muted); }
.legal { font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.measure { max-width: var(--maxw-read); }

/* ================================ BANDS — the section rhythm ================================
   Apple's page is a stack of full-bleed tiles where the SURFACE CHANGE is the divider: no borders, no
   shadows, no decorative rules between sections. `.band` is that unit. Never round it — tiles are
   rectangular and touch edge to edge. */
.band {
    padding: var(--s-section) var(--s-lg);
    /* Transparent, not --canvas: an opaque section would cover the ambient field it exists to show.
       The alternation that used to be this element's job is now carried by the panes themselves —
       every entry is a card with its own lit edge, which is a stronger divider than a tone change. */
    background: transparent;
    color: var(--text);
}
.band--alt { background: color-mix(in srgb, var(--canvas-alt) 62%, transparent); }
.band--dark { background: var(--band-dark); color: var(--on-dark); }
.band--dark-2 { background: var(--band-dark-2); color: var(--on-dark); }
.band--dark-3 { background: var(--band-dark-3); color: var(--on-dark); }
.band--void { background: var(--void); color: var(--on-dark); }
.band--tight { padding-top: var(--s-xxl); padding-bottom: var(--s-xxl); }

/* Inside a permanently-dark tile the whole token set inverts — text ladder, accent, hairline, field
   border and the P&L pair — because the tile is dark in BOTH themes and would otherwise inherit the
   light theme's values on the shipped default. Any NEW permanently-dark surface class must be added
   here too.

   `.led--dark` LEFT this list: the ledger's chambers now follow the theme (see --chamber), so the
   page's own tokens are already right inside them and inverting would be wrong in both directions.
   `.auth-aside` JOINED it — it is the one surface left that really is dark in both themes, and it
   had never been added, so on the shipped LIGHT theme it was resolving the light palette on a
   #272729 tile: --up at 2.75:1 for the live dot, the deep-lime --accent CLAUDE.md says goes muddy on
   near-black, a DARK --hairline that could not be seen at all, and inverted rim polarity.

   THE SECOND HALF OF THIS BLOCK IS NOT OPTIONAL, and it is the part that is easy to get wrong.
   A custom property is substituted WHERE IT IS DECLARED, not where it is used — so a token that is
   var()-derived on :root keeps its :root-resolved value in here even though this rule overrides the
   very inputs it was built from. Overriding --rim-* alone left --glass-edge painting the light rim
   on a dark tile; overriding --accent alone left --ring, --info and --chart-verified light. Each one
   below is therefore RE-DECLARED as its formula, so it recomputes from this scope's inputs. (This is
   the same trap the --glass-spec note records: never build a template property on an ancestor and
   expect a descendant's override to reach it.) */
.band--dark, .band--dark-2, .band--dark-3, .band--void, .auth-aside,
/* design-v2: the hero sits on video and each step card on a full-bleed render, so both are ground
   this page does not control. They are scoped so they cannot reach a page that has not been ported. */
:where(body.v2) .hero, :where(body.v2) .howstep { --text: var(--on-dark); --text-2: var(--on-dark-2); --text-muted: #a1a1a6; --text-faint: #a1a1a6; --accent: #C7EA46; --accent-text: #C7EA46; --accent-focus: #DCF57E; --on-accent: #14210A; --hairline: #3a3a3c; --surface: #2f2f31; --surface-2: #333335; --surface-3: #333335; --meter-track: #3a3a3c; --field-border: #858589; --up: #30d158; --down: #ff6961;
    --glass: color-mix(in srgb, #1C1E19 58%, transparent); --glass-2: color-mix(in srgb, #23261F 70%, transparent);
    --glass-3: color-mix(in srgb, #292C24 78%, transparent); --glass-dense: color-mix(in srgb, #1C1E19 96%, transparent);
    --rim-hi: rgba(255, 255, 255, .14); --rim-ring: rgba(255, 255, 255, .12); --rim-lo: rgba(0, 0, 0, .45);
    --glass-sheen: radial-gradient(132% 104% at 14% -20%, rgba(255, 255, 255, .07), transparent 58%);
    --spec-color: rgba(255, 255, 255, .05);
    --surface: var(--glass); --surface-2: var(--glass-2); --surface-3: var(--glass-3);
    --divider-soft: #2a2a2c;
    /* Recomputed from this scope's --rim-*, --accent-focus, --accent and --hairline. */
    --glass-edge: inset 0 1px 0 var(--rim-hi), inset 0 0 0 1px var(--rim-ring), inset 0 -1px 0 var(--rim-lo);
    --ring: 0 0 0 3px var(--accent-focus);
    --info: var(--accent);
    --chart-verified: var(--accent);
    --chart-grid: var(--hairline);
    /* A shadow is cast BY the tile's own depth, so on a dark tile it takes the night values whatever
       theme the page is on — otherwise an identical surface has different depth in the two themes. */
    --cast:    0 1px 2px rgba(0, 0, 0, .40), 0 10px 30px -10px rgba(0, 0, 0, .55);
    --cast-lg: 0 2px 8px rgba(0, 0, 0, .45), 0 22px 54px -14px rgba(0, 0, 0, .62);
    --shadow-artifact: 0 8px 42px 5px rgba(0, 0, 0, .62);
    --ok-text: #6ee7a8; --ok-bg: rgba(48, 209, 88, .16); --ok-border: rgba(48, 209, 88, .34);
    --warn-text: #ffcf70; --warn-bg: rgba(255, 159, 10, .16); --warn-border: rgba(255, 159, 10, .34);
    --err-text: #ff8a80; --err-bg: rgba(255, 69, 58, .16); --err-border: rgba(255, 69, 58, .34); }

/* `.band-inner` is accepted as a synonym of `.band__inner` — the marketing pages already use it. */
.band__inner, .band-inner { max-width: var(--maxw); margin: 0 auto; }
.band__inner--wide { max-width: var(--maxw-wide); }
.band__inner--read { max-width: var(--maxw-read); }
.band__inner--center, .band--center { text-align: center; }

@media (max-width: 640px) {
    .band { padding: var(--s-xxl) var(--s-md); }
}

/* ================================ BUTTONS ================================
   Two grammars and no others: a PILL for actions (the pill radius IS the action signal), and a compact
   RECT for utility chrome. Press state is transform: scale(.95) everywhere — the system-wide
   micro-interaction. No shadows, no gradients. */

button, .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 11px 22px;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--on-accent);
    font-family: inherit; font-size: 17px; font-weight: 400; line-height: 1.2;
    letter-spacing: -.374px;
    text-align: center; text-decoration: none; cursor: pointer;
    /* WIG: never `transition: all` — list the properties. */
    transition: transform var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
        border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
button:hover, .btn:hover { background: var(--accent-focus); color: var(--on-accent); text-decoration: none; }
button:active, .btn:active { transform: scale(.95); }
button:focus-visible, .btn:focus-visible { outline: none; box-shadow: var(--ring); }
button:disabled, .btn:disabled, button[aria-disabled="true"] {
    background: var(--surface-3); color: var(--text-disabled);
    cursor: not-allowed; transform: none;
}

/* Ghost pill — the second CTA when two sit together ("Learn more" beside "Buy"). */
button.secondary, .btn.secondary {
    background: transparent; color: var(--accent-text); border-color: currentColor;
}
button.secondary:hover, .btn.secondary:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent-text); }

/* Quiet action — no fill, but it still has to READ as a control. It carries --field-border, not
   --hairline, for the reason stated up with the tokens: WCAG 1.4.11 wants 3:1 on the boundary that
   IDENTIFIES a control, and a decorative card hairline does not clear it.

   This was `border-color: transparent`, which gave a ghost NO fill, NO edge and NO accent — leaving
   --text-2, the body-copy grey. A ghost sitting BESIDE a filled primary (a Cancel next to a Save) got
   away with it by borrowing the pair's affordance; a ghost standing ALONE had nothing to borrow from
   and rendered as a heading in both themes. "Test connection" on the broker Servers card was reported
   that way, with two more invisible controls on the same card.

   border-COLOR only: the base `button` rule already reserves `1px solid transparent`, so every one of
   these repaints in place and nothing reflows. */
button.ghost, .btn.ghost {
    background: transparent; color: var(--text-2); border-color: var(--field-border); font-weight: 400;
}
button.ghost:hover, .btn.ghost:hover { background: var(--surface-3); color: var(--text); }

/* A submit that has to read as a LINK, not an action. Some state changes cannot honestly be an anchor — a
   GET that mutates is prefetchable, so "clear my cookie choice" has to be a POST — but it belongs among the
   links around it rather than wearing the accent pill. A FULL reset, because the bare `button` selector
   above is global and aggressive; inheriting font and colour is what makes it disappear into its context. */
button.linklike {
    display: inline; width: auto; padding: 0; gap: 0;
    background: none; border: 0; border-radius: 0; box-shadow: none; transform: none;
    color: inherit; font: inherit; letter-spacing: inherit; text-align: left; text-decoration: none;
    cursor: pointer;
}
button.linklike:hover { background: none; text-decoration: underline; }
button.linklike:active { transform: none; }
button.linklike:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-xs); }

/* Utility rect — nav actions, table row actions. 8px radius, never a pill: the pill means "action". */
button.mini, .btn.mini, button.util, .btn.util {
    padding: 8px 15px; font-size: 14px; line-height: 1.29; letter-spacing: -.224px;
    border-radius: var(--r-sm);
    background: var(--surface-2); color: var(--text); border-color: var(--hairline);
}
button.mini:hover, .btn.mini:hover, button.util:hover, .btn.util:hover {
    background: var(--surface-3); color: var(--text); border-color: var(--field-border);
}
button.small, .btn.small { padding: 9px 18px; font-size: 15px; }
button.large, .btn.large { padding: 14px 28px; font-size: 18px; font-weight: 300; }

button.link, .btn.link {
    background: none; border: 0; padding: 0; color: var(--accent-text); font-size: inherit;
}
button.link:hover, .btn.link:hover { background: none; color: var(--accent-text); text-decoration: underline; }

button.danger, .btn.danger { background: var(--down); color: #ffffff; }
button.danger:hover, .btn.danger:hover { background: var(--down); filter: brightness(.92); }

/* Icon-only control. 44x44 exactly — the platform minimum, and what ui-ux-pro-max requires for touch.
   Every one of these needs an aria-label; a bare icon button has no accessible name. */
.icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0;
    border-radius: var(--r-pill);
    background: transparent; color: var(--text-2); border: 1px solid transparent;
}
.icon-btn:hover { background: var(--surface-3); color: var(--text); }
.icon-btn:active { transform: scale(.95); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.icon-btn svg { width: 20px; height: 20px; display: block; }

/* Theme toggle. The icon shows the state you are ABOUT TO ENTER, which is the convention people read
   fastest: on light you see a moon ("go dark"), on dark a sun. Both glyphs ship and CSS picks one, so
   the swap costs no JS and cannot desync from the attribute that drives the palette. */
.icon-btn .i-sun { display: none; }
:root[data-theme="dark"] .icon-btn .i-moon { display: none; }
:root[data-theme="dark"] .icon-btn .i-sun { display: block; }

/* ================================ FORMS ================================
   ONE FIELD RADIUS: every data-entry control is var(--r-md), full stop. Anything a person types into,
   picks from, or drops a file onto shares the same corner — a single-line input, a textarea, a native
   select, the searchable combobox, the file drop zone and the read-only boxes that imitate a field.
   No pills, no card radius, no off-ladder literals. A form is read as one column, so a control that
   disagrees with the one above it reads as broken rather than as emphasis, and every deviation this
   system shipped was eventually reported as a defect: a pill search box beside an 11px select, an 8px
   textarea under an 11px input, an 18px file drop among 11px fields.

   The radius is therefore NOT a per-control decision. If a new control needs to stand out, it does so
   with the accent, the label or its position — never by changing its corners.

   ENUMERATE EVERY TYPE. This selector list is an allow-list, and a type missing from it gets no radius,
   no border and no padding — it renders as a raw browser control among styled ones, which is worse than
   a mismatched radius and is invisible until someone opens that one page. `datetime-local` (the signal
   publish form's "Expires at") was missing and rendered bare among four styled number inputs.
   Adding a new input type to a Blade view means adding it here.

   `file` is deliberately NOT in this list and is styled separately down in FILE DROP. An attribute
   selector is (0,1,1) and a class is (0,1,0), so `input[type="file"]` here would OUT-SPECIFY
   `.filedrop-native` — the visually-hidden real input behind every <x-file-drop> — and hand it back
   width:100%, padding and a border. Source order would not save it; specificity wins.

   Labels are always visible — a placeholder is not a label, and ui-ux-pro-max flags placeholder-only
   as an anti-pattern. */

input[type="text"], input[type="email"], input[type="password"], input[type="number"], input[type="date"],
input[type="search"], input[type="tel"], input[type="url"], input[type="datetime-local"],
input[type="time"], input[type="month"], input[type="week"], select, textarea {
    width: 100%;
    padding: 12px 16px;
    border: 1px solid var(--field-border);
    border-radius: var(--r-md);
    background: var(--surface);
    color: var(--text);
    font-family: inherit; font-size: 17px; line-height: 1.4; letter-spacing: -.374px;
    transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
/* Multi-line is still a field: same corner as the single-line input it sits under. Only the height
   and the resize affordance differ. */
textarea { min-height: 96px; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--text-muted); opacity: 1; }
input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline: none; border-color: var(--accent-focus); box-shadow: var(--ring);
}
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--down); }
input:disabled, select:disabled, textarea:disabled {
    background: var(--surface-3); color: var(--text-disabled); cursor: not-allowed;
}

/* Native select needs explicit colours or Windows dark mode paints it black-on-black (WIG). */
select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    padding-right: 40px;
    background-color: var(--surface);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 14px center; background-size: 11px 8px;
}
:root[data-theme="dark"] select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2398989d' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
select option { background: var(--surface); color: var(--text); }

.field { margin: 0 0 var(--s-lg); max-width: 460px; }
.field > label, .label {
    display: block;
    font-size: 14px; font-weight: 600; line-height: 1.29; letter-spacing: -.224px;
    color: var(--text); margin: 0 0 7px;
}
.field--wide { max-width: none; }
.req { color: var(--down); margin-left: 2px; }
.hint { display: block; font-size: 13px; line-height: 1.45; color: var(--text-muted); margin-top: 6px; }
.err { display: block; font-size: 13px; line-height: 1.45; color: var(--err-text); margin-top: 6px; font-weight: 500; }
.check { display: flex; gap: 10px; align-items: flex-start; font-size: 15px; color: var(--text-2); cursor: pointer; }
.check input[type="checkbox"], .check input[type="radio"] { width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--accent); flex-shrink: 0; }
.form-actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: var(--s-lg); }
.opts { display: inline-flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* A FIELD AND ITS INLINE ACTION, on one line — the "readonly link + Copy" row, a search box with a
   button, a value with an Apply. This exists because the pattern was hand-rolled four times with
   `style="display:flex;gap:8px"` and drifted into three different button variants (.ghost, .secondary
   .small, .mini), so the same row shipped with a pill at one end and an 11px field at the other in
   three places.

   THE RULE: a button that shares a ROW with a field takes the FIELD radius, not the pill. The pill is
   the action signal and it is right for a standalone CTA sitting on its own; hard against a field at an
   8px gap it just reads as two shapes that failed to line up. This narrows the button grammar in one
   specific context rather than weakening it — .form-actions below a form is untouched, and every
   standalone button in the product is still a pill.

   Placed after the button rules on purpose: `.input-row > button` and `button.mini` are both (0,1,1),
   so ORDER is what makes this win. Moving it above BUTTONS silently stops it working. */
.input-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.input-row > input, .input-row > select { flex: 1; min-width: 220px; width: auto; }
.input-row > button, .input-row > .btn,
.ft-share__link > button, .ft-share__link > .btn { border-radius: var(--r-md); flex-shrink: 0; }

/* Password reveal. */
.pw-field { position: relative; }
.pw-field > input { padding-right: 48px; }
.pw-toggle {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--r-sm);
    background: none; color: var(--text-muted);
    display: inline-flex; align-items: center; justify-content: center;
}
.pw-toggle:hover { background: var(--surface-3); color: var(--text); transform: translateY(-50%); }
.pw-toggle:active { transform: translateY(-50%) scale(.95); }
.pw-toggle:focus-visible { outline: none; box-shadow: var(--ring); }
.pw-toggle svg { width: 18px; height: 18px; }
.pw-toggle .eye-off { display: none; }
.pw-field.show .pw-toggle .eye { display: none; }
.pw-field.show .pw-toggle .eye-off { display: inline; }

/* ================================ MESSAGES ================================
   Flat washes with a hairline. No shadow, no gradient. Each carries a role so assistive tech announces
   it (WIG: async updates need aria-live; the markup supplies role="status" / role="alert"). */
.status, .banner, .errors {
    padding: 14px 17px;
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    background: var(--surface-2);
    color: var(--text-2);
    font-size: 15px; line-height: 1.47;
    margin: 0 0 var(--s-md);
}
.status, .banner.ok { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok-text); }
.banner { background: var(--warn-bg); border-color: var(--warn-border); color: var(--warn-text); }
.banner.info { background: var(--surface-2); border-color: var(--hairline); color: var(--text-2); }
.errors { background: var(--err-bg); border-color: var(--err-border); color: var(--err-text); }
.errors ul { margin: 0; padding-left: 20px; }

/* ================================ THE RISK CAVEAT ================================
   partials/risk-note is included from BOTH marketing and public layouts, so its baseline lives here
   rather than in either one. It reads as what it is — a footnote set off from the page above it, at
   fine-print size, on a measure short enough to scan.

   The ledger variant BELOW used to live in layouts/marketing.blade.php, and keeping it there was a
   bug rather than a style choice. Livewire's wire:navigate MERGES the <head> and leaves the previous
   page's inline <style> in place, so following the header's Contact link from the marketing home page
   left the marketing rules applying to a layouts.public page: measured in a real browser, the caveat
   went from padding-left 0 + a top rule to padding-left 129px + a ::before left spine, so the small
   print sat 129px out of alignment with every other line on /contact while a direct page load looked
   correct. A variant of a SHARED partial therefore has to be a MODIFIER ON THE PARTIAL, defined once
   here, opted into at the call site — never an override in a layout, which can only be as reliable as
   whichever stylesheet happened to survive the last client-side navigation. */
.risk-note {
    margin: var(--s-xxl) 0 0;
    padding-top: var(--s-md);
    border-top: 1px solid var(--hairline);
}
.risk-note p {
    margin: 0; max-width: 78ch;
    font-size: 12px; line-height: 1.5; color: var(--text-muted);
}

/* Ledger variant — opted into by passing ftRiskLedger => true to the partial. The marketing pages
   hang every entry off one continuous spine, so there the small print JOINS the spine instead of
   visibly stopping it, and the top rule is dropped because the spine already does that work.
   Both selectors are a single class, so this block MUST stay after the baseline to win the tie.

   The rail/gutter geometry is deliberately identical to `main > section.led` in layouts/marketing —
   the caveat has to line up with the ledger rows above it. Change one and change the other; there is
   no shared token for it, because the spine belongs to that layout and this is its one guest. */
.risk-note--ledger {
    --rail: clamp(44px, 6vw, 84px);
    --gut: max(var(--s-md), calc((100% - 1180px) / 2));
    position: relative; margin: 0; border-top: 0;
    padding: var(--s-lg) var(--gut) var(--s-lg)
             calc(var(--gut) + var(--rail) + clamp(12px, 2vw, 28px));
}
.risk-note--ledger::before {
    content: ""; position: absolute; top: 0; bottom: 0;
    left: calc(var(--gut) + var(--rail) - 1px); width: 1px; background: var(--hairline);
}
@media (max-width: 640px) {
    .risk-note--ledger { --rail: 0px; padding-left: calc(var(--gut) + 14px); }
    .risk-note--ledger::before { left: var(--gut); }
}

/* ================================ BADGES ================================ */
.badge {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 11px;
    border-radius: var(--r-pill);
    border: 1px solid var(--hairline);
    background: var(--surface-2);
    color: var(--text-2);
    font-size: 12px; font-weight: 600; line-height: 1.4; letter-spacing: -.06px;
    white-space: nowrap;
}
.badge.ok { background: var(--ok-bg); border-color: var(--ok-border); color: var(--ok-text); }
.badge.warn { background: var(--warn-bg); border-color: var(--warn-border); color: var(--warn-text); }
.badge.down { background: var(--err-bg); border-color: var(--err-border); color: var(--err-text); }
.badge.accent { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent-text); }

/* ================================ SURFACES ================================
   A card is a hairline and a radius. Never a shadow — elevation in this system comes from a surface
   change, and the one shadow that exists is reserved for the artifact. */
.card, .panel {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-lg);
    padding: var(--s-lg);
}
.panel { margin: 0 0 var(--s-lg); }
.panel > h2:first-child, .card > h2:first-child, .panel > h3:first-child, .card > h3:first-child { margin-top: 0; }

/* The one shadow. Reserved for the figure that carries the claim — a Trust Score, a passport
   credential, a headline P&L. If it is used anywhere else, that is a bug. */
.artifact { box-shadow: var(--shadow-artifact); }

/* ================================ THE FIGURE ================================
   The centrepiece of the adaptation: where Apple gives a product render the display treatment, this
   gives it to a number. */
.figure {
    display: block;
    font-size: clamp(40px, 6vw, 56px);
    font-weight: 600; line-height: 1.07; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.figure--md { font-size: clamp(28px, 3.4vw, 40px); line-height: 1.1; }
.figure--sm { font-size: 24px; line-height: 1.2; }
.figure__unit { font-size: .42em; font-weight: 400; color: var(--text-muted); letter-spacing: 0; margin-left: 3px; }
.figure__label {
    display: block; margin-top: 8px;
    font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted);
}
.figure--up { color: var(--up); }
.figure--down { color: var(--down); }
/* Colour is never the only signal — the sign is part of the value (ui-ux-pro-max). */
.figure--na { color: var(--text-muted); }

/* Meter — a quiet track with a solid fill. No gradient. */
.meter { height: 4px; border-radius: var(--r-pill); background: var(--meter-track); overflow: hidden; margin-top: 12px; }
.meter > span { display: block; height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.meter > span.up { background: var(--up); }
.meter > span.neutral { background: var(--text-muted); }

/* ================================ TABLES ================================
   Wide tables scroll INSIDE their own container at every width — a table never scrolls the page. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
table.list {
    width: 100%; border-collapse: collapse;
    font-size: 15px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-lg);
    overflow: hidden;
}
table.list th, table.list td { text-align: left; padding: 14px 17px; border-bottom: 1px solid var(--divider-soft); }
table.list thead th {
    font-size: 12px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
    color: var(--text-muted); background: var(--surface-2);
}
table.list tbody tr:last-child td { border-bottom: 0; }
table.list tbody tr { transition: background-color var(--dur-fast) var(--ease); }
table.list tbody tr:hover { background: var(--surface-2); }
table.list td.num, table.list th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ================================ EMPTY STATE ================================
   One implementation, used everywhere. An empty screen is an invitation to act, not an apology. */
.empty {
    border: 1px dashed var(--hairline);
    border-radius: var(--r-lg);
    background: var(--surface-2);
    padding: var(--s-xxl) var(--s-lg);
    text-align: center;
}
.empty__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; margin-bottom: var(--s-md);
    border-radius: var(--r-pill); background: var(--surface-3); color: var(--text-muted);
}
.empty__icon svg { width: 22px; height: 22px; }
.empty__title { font-size: 17px; font-weight: 600; color: var(--text); margin: 0 0 6px; }
.empty__body { font-size: 15px; color: var(--text-muted); margin: 0 auto var(--s-md); max-width: 44ch; }

/* ================================ BRAND ================================ */
.brand-lockup { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; color: var(--text); }
.brand-lockup:hover { text-decoration: none; }
.brand-mark {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 28px; height: 28px; border-radius: var(--r-sm);
    background: var(--accent); color: var(--on-accent);
    font-weight: 600; font-size: 15px;
}
.brand-glyph { color: var(--on-accent); }
.brand .brand-word { color: var(--text); font-size: 19px; font-weight: 600; letter-spacing: -.02em; }
.brand .brand-word span { color: var(--text); }
/* Both staff-uploaded variants are emitted; CSS picks one so a theme switch needs no reload.
   "light"/"dark" name the BACKGROUND the asset is drawn on, matching BrandingService. */
.brand-logo { height: 28px; width: auto; max-width: 190px; object-fit: contain; display: block; }
.brand-logo--dark { display: none; }
:root[data-theme="dark"] .brand-logo--light { display: none; }
:root[data-theme="dark"] .brand-logo--dark { display: block; }
/* Inside a dark band the surface is dark whatever the theme, so the dark-background asset wins.
   THIS LIST MUST MATCH THE TOKEN LIST ABOVE — they answer the same question ("is this surface dark
   regardless of theme?") and two lists that disagree is how `.auth-aside` came to render the
   LIGHT-background logo on a #272729 tile on every auth page in the shipped default theme. It is the
   only `<x-brand-lockup>` site sitting on a permanently-dark surface, and it was in neither list.
   Latent until staff upload two genuinely different variants — BrandingService::logoLightUrl() falls
   back to the dark path, so a single-variant upload makes both srcs identical. `.led--dark` is
   deliberately NOT here: those chambers follow the theme now, so :root[data-theme] already picks. */
.band--dark .brand-logo--light, .band--dark-2 .brand-logo--light,
.band--dark-3 .brand-logo--light, .band--void .brand-logo--light,
.auth-aside .brand-logo--light { display: none; }
.band--dark .brand-logo--dark, .band--dark-2 .brand-logo--dark,
.band--dark-3 .brand-logo--dark, .band--void .brand-logo--dark,
.auth-aside .brand-logo--dark { display: block; }

/* ================================ INLINE HELP ================================ */
.help { position: relative; display: inline-flex; vertical-align: middle; margin-left: 5px; line-height: 0; }
.help-btn {
    all: unset; box-sizing: border-box; cursor: help;
    width: 16px; height: 16px; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--r-pill); color: var(--text-muted); border: 1px solid var(--field-border);
    transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.help-btn:hover, .help-btn:focus-visible { color: var(--accent-text); border-color: var(--accent-text); outline: none; }
.help-pop {
    position: absolute; bottom: calc(100% + 9px); left: 50%; transform: translateX(-50%);
    width: max-content; max-width: 260px;
    background: var(--text); color: var(--canvas);
    border-radius: var(--r-sm); padding: 10px 13px;
    font-size: 13px; font-weight: 400; line-height: 1.45; letter-spacing: 0; text-align: left; text-transform: none;
    opacity: 0; visibility: hidden;
    transition: opacity var(--dur-fast) var(--ease), visibility var(--dur-fast);
    z-index: 70;
}
.help:hover .help-pop, .help:focus-within .help-pop { opacity: 1; visibility: visible; }
@media (max-width: 600px) { .help-pop { max-width: 200px; left: auto; right: -4px; transform: none; } }
.field:has(> .help) > label,
h1:has(+ .help), h2:has(+ .help), h3:has(+ .help) { display: inline-flex; align-items: center; gap: .3em; vertical-align: middle; }
.field:has(> .help) > .help + * { margin-top: 6px; }

/* ================================ DISCLOSURE CARD ================================ */
.disclosure {
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg);
    padding: var(--s-md) var(--s-lg); margin-bottom: 10px; font-size: 15px;
}
.disclosure .dt { font-weight: 600; }

/* ================================ COUNTRY / CURRENCY COMBOBOX ================================ */
[x-cloak] { display: none !important; }
.country-select { position: relative; }
.cs-menu {
    position: absolute; z-index: 60; left: 0; right: 0; top: calc(100% + 6px);
    max-height: 264px; overflow-y: auto; overscroll-behavior: contain;
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: 6px;
}
.cs-opt {
    padding: 10px 12px; border-radius: var(--r-sm); font-size: 15px; cursor: pointer; color: var(--text-2);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cs-opt:hover, .cs-opt.active, .cs-opt.sel { background: var(--surface-3); color: var(--text); }
.cs-empty { padding: 12px; font-size: 14px; color: var(--text-muted); }

/* ================================ FILE DROP ================================
   A file drop is a FIELD, so it takes the field radius — not the card radius it used to carry. At
   --r-lg it wore the same corner as .card/.panel/.filterbar, which made a document upload read as a
   container that happened to be in the form rather than as the control its label belongs to, and put
   a 7px step between it and the inputs stacked either side of it in the KYC and KYB steps.
   The dashed border is what says "drop here"; the corner does not need to say it as well. */
.filedrop { position: relative; }
.filedrop-native { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* The bare fallback: <x-file-drop> with no wire:model renders a plain native input, which matches no
   selector in FORMS (see the note there for why `file` cannot join that list). Styled here instead,
   and :not() keeps it off the hidden input above. Padding is lighter than a text field's because the
   browser draws its own "Choose file" button inside the box. */
input[type="file"]:not(.filedrop-native) {
    width: 100%; padding: 9px 12px;
    border: 1px solid var(--field-border); border-radius: var(--r-md);
    background: var(--surface); color: var(--text);
    font-family: inherit; font-size: 15px; line-height: 1.4;
}
input[type="file"]:not(.filedrop-native):focus-visible { outline: none; border-color: var(--accent-focus); box-shadow: var(--ring); }
.filedrop-zone {
    display: flex; align-items: center; justify-content: center; text-align: center; gap: 14px;
    min-height: 120px; padding: var(--s-lg);
    border: 1.5px dashed var(--field-border); border-radius: var(--r-md);
    background: var(--surface-2); color: var(--text-2); cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.filedrop-zone:hover { border-color: var(--accent); }
.filedrop:focus-within .filedrop-zone { border-color: var(--accent-focus); box-shadow: var(--ring); }
.filedrop.dragover .filedrop-zone { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.filedrop.hasfile .filedrop-zone { justify-content: flex-start; text-align: left; min-height: 0; padding: var(--s-md); }
.fd-empty { display: flex; flex-direction: column; align-items: center; gap: 9px; pointer-events: none; }
.fd-ico { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: var(--r-pill); background: var(--surface-3); color: var(--text-muted); flex-shrink: 0; }
.fd-ico.ok { background: var(--ok-bg); color: var(--ok-text); }
.fd-ico svg { width: 22px; height: 22px; }
.fd-title { font-size: 15px; color: var(--text); }
.fd-title strong, .fd-browse { color: var(--accent-text); font-weight: 600; }
.fd-hint { font-size: 13px; color: var(--text-muted); }
.fd-file { display: flex; align-items: center; gap: 14px; width: 100%; }
.fd-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.fd-name { font-size: 15px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fd-size { font-size: 13px; color: var(--text-muted); }
.fd-remove { flex-shrink: 0; }
.fd-uploading { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-muted); margin-top: 9px; }
/* The browser's own size refusal (ftFileDrop). Reuses .err for colour + type; .toobig tints the zone so the
   rejection is visible at the drop target, not only in the sentence under it. */
.fd-err { margin: 9px 0 0; }
.filedrop.toobig .filedrop-zone { border-color: var(--err-text); background: var(--err-bg); }
.fd-spin { width: 13px; height: 13px; border: 2px solid var(--hairline); border-top-color: var(--accent); border-radius: var(--r-pill); animation: ft-spin .7s linear infinite; }

/* ================================ SEARCHABLE SERVER COMBOBOX ================================ */
.ft-combo { position: relative; }
.ft-combo-picked { margin: 6px 0 0; font-size: 13px; color: var(--text-2); }
.ft-combo-picked strong { color: var(--text); font-weight: 600; }
.ft-combo-menu {
    position: absolute; z-index: 40; left: 0; right: 0; margin-top: 6px;
    max-height: 300px; overflow-y: auto; overscroll-behavior: contain;
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: 6px;
}
.ft-combo-group {
    margin: 8px 8px 4px; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted);
}
.ft-combo-group:not(:first-child) { border-top: 1px solid var(--divider-soft); padding-top: 10px; }
/* An option is a <button>, so the base button rule has to be undone or every row renders as a blue pill. */
.ft-combo-opt {
    display: block; width: 100%; text-align: left; padding: 9px 11px; border: 0; border-radius: var(--r-sm);
    background: transparent; color: var(--text); font-size: 15px; font-weight: 400; line-height: 1.35;
    letter-spacing: -.224px; cursor: pointer; transform: none;
}
.ft-combo-opt:hover { background: var(--surface-3); color: var(--text); transform: none; }
.ft-combo-opt.is-active { background: var(--surface-3); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
.ft-combo-opt:focus { outline: none; }
.ft-combo-opt:focus-visible { outline: none; box-shadow: var(--ring); }
.ft-combo-opt.is-picked { background: var(--surface-3); font-weight: 600; }
.ft-combo-opt.is-picked::after { content: " ✓"; color: var(--accent-text); }
.ft-combo-empty, .ft-combo-more { margin: 9px; font-size: 13px; color: var(--text-muted); line-height: 1.45; }
.ft-combo-more { border-top: 1px solid var(--divider-soft); padding-top: 9px; }

/* ================================ PAGINATION ================================ */
.ft-pager { display: flex; align-items: center; justify-content: space-between; gap: var(--s-md); flex-wrap: wrap; margin-top: var(--s-xl); }
.ft-pager__count { margin: 0; font-size: 14px; color: var(--text-muted); }
.ft-pager__count strong { color: var(--text-2); font-weight: 600; font-variant-numeric: tabular-nums; }
.ft-pager__list { display: flex; align-items: center; gap: 6px; list-style: none; margin: 0; padding: 0; flex-wrap: wrap; }
.ft-pager__btn {
    display: inline-flex; align-items: center; justify-content: center; min-width: 44px; height: 44px;
    padding: 0 14px; border-radius: var(--r-sm); border: 1px solid var(--hairline);
    background: var(--surface); color: var(--text-2);
    font-size: 15px; font-weight: 400; line-height: 1; cursor: pointer; font-variant-numeric: tabular-nums;
}
.ft-pager__btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--field-border); }
.ft-pager__btn:focus-visible { outline: none; box-shadow: var(--ring); }
.ft-pager__btn.is-current { border-color: var(--accent); color: var(--accent-text); background: color-mix(in srgb, var(--accent) 8%, transparent); font-weight: 600; }
.ft-pager__btn.is-disabled { opacity: .4; cursor: not-allowed; }
.ft-pager__btn:disabled { opacity: .55; cursor: progress; }
.ft-pager__gap { padding: 0 2px; color: var(--text-muted); }
@media (max-width: 560px) {
    .ft-pager { justify-content: center; }
    .ft-pager__num, .ft-pager__gap { display: none; }
    .ft-pager__list { width: 100%; justify-content: space-between; }
}

/* ================================ SHARE TRAY ================================ */
.ft-share { background: var(--surface-2); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: var(--s-md) var(--s-lg); margin: -4px 0 var(--s-sm); }
.ft-share__hd { display: flex; gap: 11px; align-items: flex-start; }
.ft-share__hd .ic { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; color: var(--accent-text); flex-shrink: 0; margin-top: 2px; }
.ft-share__hd strong { display: block; font-size: 15px; letter-spacing: -.01em; }
.ft-share__hd p { margin: 3px 0 0; font-size: 14px; line-height: 1.47; color: var(--text-muted); }
.ft-share__link { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: var(--s-sm); }
.ft-share__link input { flex: 1; min-width: 210px; padding: 9px 12px; font-size: 14px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.ft-share__native { display: none; }
:root.can-share .ft-share__native { display: inline-flex; }
.ft-share__msg { margin: 11px 0 0; padding: 11px 13px; background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-sm); font-size: 14px; line-height: 1.55; color: var(--text-2); }
.ft-share__msg span { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); font-weight: 600; margin-bottom: 4px; }
.ft-share__targets { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--s-sm); }
.ft-share__btn {
    display: inline-flex; align-items: center; gap: 7px; padding: 9px 15px; font-size: 14px; font-weight: 400;
    border-radius: var(--r-pill); background: var(--surface); color: var(--text);
    border: 1px solid var(--hairline); text-decoration: none; line-height: 1.2;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.ft-share__btn:hover { border-color: var(--accent); color: var(--accent-text); text-decoration: none; }
.ft-share__btn:active { transform: scale(.95); }
.ft-share__btn:focus-visible { outline: none; box-shadow: var(--ring); }
.ft-share__btn .g { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; flex-shrink: 0; }
.ft-share__note { margin: 11px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.ft-share-note { margin: -4px 0 var(--s-sm); font-size: 13px; line-height: 1.5; color: var(--text-muted); }

/* ================================ NUMBERED STEPS ================================ */
.ft-steps { list-style: none; margin: var(--s-sm) 0 0; padding: 0; counter-reset: ft-step; display: grid; gap: var(--s-sm); }
.ft-steps > li { counter-increment: ft-step; position: relative; padding-left: 38px; color: var(--text-2); line-height: 1.55; font-size: 15px; }
.ft-steps > li::before {
    content: counter(ft-step); position: absolute; left: 0; top: 0; width: 26px; height: 26px;
    display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-pill);
    background: var(--accent); color: var(--on-accent); font-size: 13px; font-weight: 600; line-height: 1;
}
.ft-steps > li strong { color: var(--text); }
.ft-bot-handle { display: inline-block; background: var(--surface-3); border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 3px 9px; font-weight: 600; color: var(--text); }
.ft-why { margin: var(--s-sm) 0 0; padding: 12px 14px; background: var(--surface-2); border: 1px solid var(--hairline); border-left: 2px solid var(--accent); border-radius: var(--r-sm); font-size: 14px; line-height: 1.55; color: var(--text-muted); }
.ft-why strong { color: var(--text-2); }

/* ================================ MOTION ================================
   150–300ms, transform/opacity only, never `transition: all`, and every one of these is neutralised
   under prefers-reduced-motion. Entrances are html.js-gated so a no-JS client sees visible content. */
@keyframes ft-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes ft-spin { to { transform: rotate(360deg); } }
@keyframes ft-pulse { 0% { transform: scale(.8); opacity: .5; } 70% { transform: scale(1.7); opacity: 0; } 100% { opacity: 0; } }

html.js .reveal { opacity: 0; transform: translateY(12px); transition: opacity 300ms var(--ease), transform 300ms var(--ease); }
html.js .reveal.in { opacity: 1; transform: none; }
html.js .ft-in { opacity: 0; animation: ft-rise 300ms var(--ease) both; animation-delay: var(--d, 0s); }
.ft-spin-slow, .ft-float, .ft-float2, .ft-dash, .ft-shimmer, .ft-arc { /* retired decorative motion — kept as no-ops so old markup degrades cleanly */ }

/* ================================ SCROLL MOTION TOOLKIT ================================
   Four primitives the marketing pages compose. All four share the same contract:

     • the element is FULLY VISIBLE and complete without JavaScript — motion is added, never depended on
     • only transform / opacity / stroke-dashoffset animate (compositor-friendly, no layout thrash)
     • `prefers-reduced-motion: reduce` lands every one of them in its FINISHED state instantly

   The trigger is `.in`, applied by the shared observer in partials/theme.blade.php. Nothing here polls
   or measures layout during render.
   ------------------------------------------------------------------------------------------------- */

/* 1. SEQUENCE — children arrive in order once the group is in view.
      The stagger is pure CSS (nth-child -> --i), so a view never has to hand-number its own children
      and inserting a row in the middle cannot silently break the rhythm. */
html.js .ft-seq > * { opacity: 0; transform: translateY(14px); }
html.js .ft-seq.in > * {
    opacity: 1; transform: none;
    transition: opacity 420ms var(--ease), transform 420ms var(--ease);
    transition-delay: calc(var(--i, 0) * 70ms);
}
.ft-seq > *:nth-child(1) { --i: 0 } .ft-seq > *:nth-child(2) { --i: 1 }
.ft-seq > *:nth-child(3) { --i: 2 } .ft-seq > *:nth-child(4) { --i: 3 }
.ft-seq > *:nth-child(5) { --i: 4 } .ft-seq > *:nth-child(6) { --i: 5 }
.ft-seq > *:nth-child(7) { --i: 6 } .ft-seq > *:nth-child(8) { --i: 7 }
.ft-seq > *:nth-child(9) { --i: 8 } .ft-seq > *:nth-child(n+10) { --i: 9 }

/* 2. DRAW — an SVG stroke writes itself on.
      The path carries pathLength="1", which renormalises its geometry so a dasharray of 1 covers it
      exactly. That means NO JavaScript measurement of getTotalLength(), and it stays correct when the
      viewBox is scaled responsively — the usual implementation of this effect breaks on both counts. */
html.js .ft-draw { stroke-dasharray: 1; stroke-dashoffset: 1; }
html.js .in .ft-draw, html.js .ft-draw.in {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset var(--dur, 1400ms) cubic-bezier(.32, .72, .3, 1);
    transition-delay: var(--d, 0s);
}

/* 3. TRACE — something travels ALONG that stroke (a trade moving from a strategist to a follower).
      offset-path follows the same geometry the line drew, so the dot cannot drift off the wire. */
html.js .ft-trace { opacity: 0; }
html.js .in .ft-trace {
    opacity: 1;
    animation: ft-travel var(--dur-travel, 1200ms) cubic-bezier(.4, 0, .3, 1) var(--d, 0s) both;
}
@keyframes ft-travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }

/* 4. SEND — a short dash travelling the length of a stroke, for showing an order MOVING along a wire
      to a broker. (Named ft-send, not ft-pulse: `@keyframes ft-pulse` above already belongs to the
      live-dot ring, and a class and a keyframe sharing a name is legal but reads as a bug.) Built from stroke-dasharray rather than offset-path because
      dasharray is universally supported and behaves identically on every SVG geometry; the dash length
      is the visible pulse and the gap must exceed the path length so only one pulse is ever on the wire.
      `pathLength="1"` normalises the geometry, so the numbers below are fractions of the path, not
      user units — the same trick .ft-draw uses. */
html.js .ft-send { stroke-dasharray: 0.12 1; stroke-dashoffset: 1.12; opacity: 0; }
html.js .in .ft-send {
    opacity: 1;
    animation: ft-send-run var(--dur-send, 2600ms) linear var(--d, 0s) infinite;
}
/* The travel happens in the FIRST ~38% of the cycle and the rest is a hold, off. That gap is what makes
   a second hop possible: give hop two a delay of ~36% of the same duration and its pulse leaves just as
   hop one arrives, then both rest until the cycle restarts. Sharing one duration is what keeps the two
   hops in phase for ever — two different durations drift apart within a few seconds.

   The hold is also the honest choice: a continuous stream of pulses would read as live traffic, and
   there is none behind this drawing. One wave, a pause, one wave. */
@keyframes ft-send-run {
    0%   { stroke-dashoffset: 1.12; opacity: 0; }
    4%   { opacity: 1; }
    34%  { opacity: 1; }
    38%  { stroke-dashoffset: 0; opacity: 0; }
    100% { stroke-dashoffset: 0; opacity: 0; }
}

/* 5. SCRUB — a value tied to scroll position rather than to a timer.
      `--p` (0..1) is written by the observer while the element is on screen; CSS reads it. Used for the
      passport that accumulates as the page moves, where the visitor's own scrolling IS the timeline. */
html.js .ft-scrub { --p: 0; }

@media (prefers-reduced-motion: reduce) {
    html.js .ft-seq > *, html.js .ft-seq.in > * { opacity: 1 !important; transform: none !important; transition: none !important; }
    html.js .ft-draw, html.js .in .ft-draw, html.js .ft-draw.in { stroke-dashoffset: 0 !important; transition: none !important; }
    html.js .ft-trace, html.js .in .ft-trace { opacity: 1 !important; animation: none !important; offset-distance: 100% !important; }
    /* A pulse is a LOOP, so there is no finished state to land in — it simply does not run, and the
       wires it travelled are already drawn and legible on their own. */
    html.js .ft-pulse, html.js .in .ft-send { animation: none !important; opacity: 0 !important; }
}

.live-dot { position: relative; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--up); flex-shrink: 0; }
.live-dot::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--up); animation: ft-pulse 2.6s ease-out infinite; }

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    html.js .reveal { opacity: 1 !important; transform: none !important; transition: none; }
    html.js .ft-in { opacity: 1 !important; animation: none !important; }
    .live-dot::after { animation: none !important; }
    *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}


/* ==========================================================================================
   LIQUID GLASS — the applied material
   The tokens live in the two blocks at the top of this file; this section is the small amount of
   CSS that CONSUMES them, plus the escape hatches that switch the whole system off at the root.

   The material deliberately uses NO pseudo-element and adds NO markup: the edge is three insets
   and the sheen is a background-image. That is what lets it be applied to components that already
   spend their ::before/::after elsewhere — the ledger spine, the row counter, the trust ring's
   inner disc and the stretched card link all do.
   ========================================================================================== */

.glass {
    background-color: var(--glass);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
            var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    box-shadow: var(--glass-edge), var(--cast);
    border: 0;
    border-radius: var(--r-lg);
}
/* Raised: the surfaces meant to read as sitting ON the page rather than in it. */
.glass--raised {
    -webkit-backdrop-filter: var(--glass-blur-lg);
    backdrop-filter: var(--glass-blur-lg);
    box-shadow: var(--glass-edge), var(--cast-lg);
}
/* Flush: a pane inside a pane. A second cast shadow inside a shadowed parent reads as dirt, and
   two identical tints give no edge — hence the denser --glass-2 rather than a heavier border. */
.glass--flush {
    background-color: var(--glass-2);
    box-shadow: var(--glass-edge);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}

/* The hover lift — a pane that is a link gains weight rather than changing colour — lives on each
   layout's own card class (see `.card:hover` in layouts.public), not on a `.glass-lift` helper here.
   The helper was written first, never referenced by any view, and its ring had no radius to follow. */

/* FIXED OVERLAYS take the densest tint AND the blur. Their own component CSS only sets a surface
   colour, so the blur has to be applied here; and they float over content nothing constrains, which
   is why the tint is 97%/96% rather than 72%/58% — measured against pure black and pure white. */
.ftx-consent, .ftx-panel, .ftx-mini, .ftx-bell-panel, .ftx-toast,
.drop-menu-box, .cs-menu, .ft-combo-menu, .ft-share-tray {
    --surface: var(--glass-dense);
    --surface-2: var(--glass-dense);
    --surface-3: var(--glass-dense);
    -webkit-backdrop-filter: var(--glass-blur-lg);
    backdrop-filter: var(--glass-blur-lg);
    box-shadow: var(--glass-edge), var(--cast-lg);
    /* A RADIUS FLOOR, because the ring follows it. A floating overlay always wants corners, and this
       rule cannot verify that each component declared some — a component that forgets would otherwise
       get a hard square box. Anything with its own larger radius keeps it (own rule, more specific);
       the full-bleed exception is overridden immediately below. */
    border-radius: var(--r-md);
}
/* `.ftx-consent__panel` is NOT in that list: it is a transparent inner wrapper with no radius, and
   --glass-edge contains an `inset 0 0 0 1px` ring which FOLLOWS the element's radius — so on a
   radius-0 wrapper it draws a hard square box around content that had no border at all. The same
   mistake put a square box around the whole sign-in form (guest layout `.card`) and around the
   high-water-mark chart. THE RULE: give the material only to something that is already a pane, i.e.
   has its own background AND its own radius. */

/* A FULL-BLEED BAR has no corners, so a ring's left and right sides run down the viewport edges as
   two stray hairlines. It takes the lit TOP edge only — the same treatment the sticky topbar gets. */
.ftx-consent--bar { border-radius: 0; box-shadow: inset 0 1px 0 var(--rim-hi), var(--cast-lg); }

/* ------------------------------------------------------------------------------------------
   ESCAPE HATCHES. Three of them, and each returns the product to the OPAQUE look it shipped
   before — never to a washed-out half-glass. They are token-only, which is why they reach every
   component in four layouts without naming one: a layout consumes `backdrop-filter:
   var(--glass-blur)`, so setting that token to `none` here switches the blur off everywhere.

   `--glass-edge`/`--cast` resolve to a transparent shadow rather than `none`, because
   `box-shadow: none, none` is invalid and would drop the whole declaration instead of clearing it.
   ------------------------------------------------------------------------------------------ */

/* 1. No backdrop-filter at all: a translucent pane with no blur is simply see-through. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    :root {
        --glass: #ffffff; --glass-2: #fafafc; --glass-3: #f5f5f7; --glass-dense: #ffffff;
        --hairline: #e0e0e0; --canvas: #ffffff; --canvas-alt: #f5f5f7;
        --glass-blur: none; --glass-blur-lg: none; --glass-sheen: none; --spec-color: transparent;
        --glass-edge: 0 0 0 0 transparent; --cast: 0 0 0 0 transparent; --cast-lg: 0 0 0 0 transparent;
    }
    :root[data-theme="dark"] {
        --glass: #1d1d1f; --glass-2: #252527; --glass-3: #2a2a2c; --glass-dense: #1d1d1f;
        --hairline: #38383a; --canvas: #000000; --canvas-alt: #1d1d1f;
        --glass-blur: none; --glass-blur-lg: none; --glass-sheen: none; --spec-color: transparent;
        --glass-edge: 0 0 0 0 transparent; --cast: 0 0 0 0 transparent; --cast-lg: 0 0 0 0 transparent;
    }
    body { background-image: none; }
    .band { background: var(--canvas); }
    .band--alt { background: var(--canvas-alt); }
}

/* 2. The OS has been asked to reduce transparency — a real accessibility preference, and
      transparency is this material's entire premise. Honoured by removing it, not softening it. */
@media (prefers-reduced-transparency: reduce) {
    :root {
        --glass: #ffffff; --glass-2: #fafafc; --glass-3: #f5f5f7; --glass-dense: #ffffff;
        --hairline: #e0e0e0; --canvas: #ffffff; --canvas-alt: #f5f5f7;
        --glass-blur: none; --glass-blur-lg: none; --glass-sheen: none; --spec-color: transparent;
    }
    :root[data-theme="dark"] {
        --glass: #1d1d1f; --glass-2: #252527; --glass-3: #2a2a2c; --glass-dense: #1d1d1f;
        --hairline: #38383a; --canvas: #000000; --canvas-alt: #1d1d1f;
        --glass-blur: none; --glass-blur-lg: none; --glass-sheen: none; --spec-color: transparent;
    }
    body { background-image: none; }
    .band { background: var(--canvas); }
    .band--alt { background: var(--canvas-alt); }
}

/* 3. More contrast requested: opaque panes, a real border, and the sheen goes — an 85%-white inset
      is decoration, and decoration is the first thing to drop when someone asks for legibility. */
@media (prefers-contrast: more) {
    :root {
        --glass: #ffffff; --glass-2: #ffffff; --glass-3: #f5f5f7; --glass-dense: #ffffff;
        --hairline: #6e6e73; --rim-hi: transparent; --rim-lo: transparent; --rim-ring: #6e6e73;
        --canvas: #ffffff; --glass-sheen: none; --spec-color: transparent;
    }
    :root[data-theme="dark"] {
        --glass: #1d1d1f; --glass-2: #1d1d1f; --glass-3: #2a2a2c; --glass-dense: #1d1d1f;
        --hairline: #98989d; --rim-hi: transparent; --rim-lo: transparent; --rim-ring: #98989d;
        --canvas: #000000; --glass-sheen: none; --spec-color: transparent;
    }
    body { background-image: none; }
}

/* ---------------------------------------------------------------------------------------------------
   PERFORMANCE CHART  (resources/views/components/performance-chart.blade.php)

   A strategy's realised-P&L curve, its monthly result strip and the table twin. Token-only, build-less,
   no chart library — the same posture as partials/sparkline and the marketing page's hand-authored SVGs.

   IT IS A FIGURE, NOT A PANE. No backdrop-filter, no --glass-edge, no sheen, no cast: a drawing sits ON a
   pane. GlassPaneOnlyTest fails a rule here that reintroduces any of them, and it has caught this exact
   mistake twice before (.hwm and .spark-wrap).
   --------------------------------------------------------------------------------------------------- */
.ftp { margin: 0; display: grid; gap: 14px; }
.ftp-cap { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ftp-title { font-weight: 590; letter-spacing: -.01em; }
/* Proportional figures, not tabular: a large standalone number set in equal-width digits reads loose. */
.ftp-net { font-size: 19px; font-weight: 600; letter-spacing: -.015em; }
.ftp-net.is-up { color: var(--up); }
.ftp-net.is-down { color: var(--down); }
.ftp-empty { color: var(--text-muted); font-size: 15px; margin: 0; }

.ftp-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 10px; }
.ftp-kpi { display: grid; gap: 2px; }
.ftp-kpi .k { font-size: 12px; color: var(--text-muted); }
.ftp-kpi .v { font-size: 17px; font-weight: 560; font-variant-numeric: tabular-nums; }

.ftp-plot { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; gap: 4px 10px; }
.ftp-yaxis {
    grid-row: 1; display: flex; flex-direction: column; justify-content: space-between;
    font-size: 11px; color: var(--text-faint); text-align: right;
    font-variant-numeric: tabular-nums; padding: 2px 0;
}
.ftp-zero-label { color: var(--text-muted); }
.ftp-svg { grid-row: 1; grid-column: 2; width: 100%; height: auto; display: block; }
.ftp-xaxis {
    grid-row: 2; grid-column: 2; display: flex; justify-content: space-between;
    font-size: 11px; color: var(--text-faint);
}
.ftp-svg circle[r="10"] { cursor: help; }

.ftp-legend { display: flex; gap: 16px; flex-wrap: wrap; list-style: none; margin: 0; padding: 0;
    font-size: 12px; color: var(--text-muted); }
.ftp-legend li { display: flex; align-items: center; gap: 6px; }
.ftp-legend .sw { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }

.ftp-sub { font-size: 13px; font-weight: 560; color: var(--text-muted); margin: 0 0 6px; }
/* Each column is one month: an "up" box, the zero rule, a "down" box. The bar grows AWAY from the rule,
   so the sign is read from direction and never from colour alone. */
.ftp-bars { display: flex; align-items: center; gap: 2px; overflow-x: auto; padding-bottom: 2px; }
.ftp-bar { flex: 1 1 6px; min-width: 5px; max-width: 24px; display: grid; cursor: help;
    grid-template-rows: 48px 1px 48px; justify-items: stretch; }
.ftp-bar .up { align-self: end; border-radius: 4px 4px 0 0; background: var(--up); }
.ftp-bar .dn { align-self: start; border-radius: 0 0 4px 4px; background: var(--down); }
.ftp-bar .rule { background: var(--chart-grid); }
.ftp-mnote { font-size: 11px; color: var(--text-faint); margin: 6px 0 0; }

.ftp-table > summary { font-size: 13px; color: var(--text-muted); cursor: pointer; }
.ftp-table > summary:hover { color: var(--text); }
.ftp-table table { margin-top: 10px; }
.ftp-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.ftp-table .is-up { color: var(--up); }
.ftp-table .is-down { color: var(--down); }

.ftp-caveats { margin: 0; padding-left: 18px; font-size: 12px; color: var(--text-muted); display: grid; gap: 4px; }

@media (max-width: 560px) {
    .ftp-plot { grid-template-columns: 1fr; }
    .ftp-yaxis { grid-row: auto; grid-column: 1; flex-direction: row; justify-content: space-between; text-align: left; }
    .ftp-svg { grid-column: 1; }
    .ftp-xaxis { grid-column: 1; }
}

/* ── Reported server identity ─────────────────────────────────────────────────────────────────────────────
   What an MT5 server says about ITSELF, read over the Manager Web API. Rendered by
   partials/reported-server-identity.blade.php, which is included from more than one place — so its baseline
   lives HERE rather than in a layout's inline <style>. A shared partial styled from a layout is the documented
   wire:navigate head-merge trap: Livewire APPENDS a changed inline <style> without removing the stale one, so
   the previous page's rules keep governing this one (the .risk-note--ledger bug). Tokens only, both themes. */
.srv-ident { margin-top: 14px; padding: 12px 13px; border: 1px solid var(--border);
    border-radius: var(--r-sm); background: var(--surface-2); }
.srv-ident__hd { font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted); font-weight: 700; margin-bottom: 9px; }
.srv-ident__list { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px 18px; margin: 0; }
.srv-ident__list > div { min-width: 0; }
.srv-ident__list dt { font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted); }
.srv-ident__list dd { margin: 2px 0 0; font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.srv-ident__num { font-variant-numeric: tabular-nums; }
/* A DISAGREEMENT between the typed name and the reported one is information, not a failure — warn tones, never
   the error red, because both names can legitimately be right. */
.srv-ident__warn { margin: 10px 0 0; padding: 8px 10px; font-size: 12px; line-height: 1.45;
    color: var(--warn-text); background: var(--warn-bg); border: 1px solid var(--warn-border);
    border-radius: var(--r-xs); }


/*================================================================================================
  MOVED OUT OF BLADE  (see the header of this file's `Layout scopes` note)

  Everything below used to be an inline <style> in a Blade view. Head-level <style> blocks
  ACCUMULATE across wire:navigate -- Livewire's mergeNewHead() removes only NON-asset head
  children, and isAsset() counts <style>, <link rel=stylesheet> and <script>. So arriving at
  /explore from / left the marketing layout's 31KB and the home page's 17KB still applying:
  measured at +48,569 bytes over a fresh load. That is why a page could show the OLD design
  until a hard refresh -- a hard refresh is a full document load, which starts a clean head.

  Living in ONE stylesheet fixes it structurally: the <link> is byte-identical on every page,
  so Livewire dedupes it and the merge is a no-op.

  Each layout's rules are SCOPED to that layout's body class so they cannot match elsewhere.
  The scope also preserves the old cascade: layouts and components both gain exactly +(0,1,1),
  so where they used to tie they still tie, and source order (components last, as they were in
  <body>) still breaks it the same way.
================================================================================================*/


/* ---- site chrome (header/footer/spine)  <- resources/views/partials/site-chrome.blade.php   [scope: ly-chrome] ---- */

body:where(.ly-chrome) { display: flex; flex-direction: column; }
/* ---- Top bar ------------------------------------------------------------------------------
   Translucent rather than solid so content scrolls visibly beneath it, with a solid fallback
   first for browsers without backdrop-filter (otherwise the bar reads as see-through). */
:where(body.ly-chrome) .topbar {
    position: sticky; top: 0; z-index: 50;
    background: var(--canvas);
    border-bottom: 1px solid var(--hairline);
}
/* The bar was ALREADY the one glass surface in this system — a hand-rolled 72% tint of the canvas
   with its own blur, which is Apple's translucent nav. It now reads the shared tokens instead, so
   it degrades with everything else and the values are the measured ones rather than a local guess.
   The @supports guard stays: an opaque fallback first, or the bar reads as see-through. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
:where(body.ly-chrome) .topbar {
        /* Denser than a pane. A sticky bar passes over EVERY ground the page has, including the
           dark ledger chambers, so it is held to the overlay tint rather than the card tint. */
        background: var(--glass-dense);
        background-image: var(--glass-sheen);
        -webkit-backdrop-filter: var(--glass-blur-lg);
        backdrop-filter: var(--glass-blur-lg);
        box-shadow: inset 0 -1px 0 var(--rim-ring), var(--cast);
        border-bottom: 0;
    }
}
:where(body.ly-chrome) .topbar-inner {
    max-width: var(--maxw); margin: 0 auto;
    /* WIG: full-bleed chrome has to clear the notch in landscape. */
    padding: 0 max(var(--s-lg), env(safe-area-inset-left)) 0 max(var(--s-lg), env(safe-area-inset-right));
    min-height: 48px;
    display: flex; align-items: center; gap: var(--s-lg); flex-wrap: wrap;
}
:where(body.ly-chrome) .topbar .brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; color: var(--text); }
:where(body.ly-chrome) .topbar .brand:hover { text-decoration: none; }
:where(body.ly-chrome) .topbar .brand .brand-word { font-size: 17px; font-weight: 600; letter-spacing: -.02em; color: var(--text); }
:where(body.ly-chrome) .topbar nav { margin-left: auto; display: flex; gap: 2px; align-items: center; flex-wrap: wrap; }
/* 12px labels are Apple's global-nav size — small on purpose, because the bar is not the point. */
:where(body.ly-chrome) .topbar nav a {
    color: var(--text-2); text-decoration: none;
    font-size: 12px; font-weight: 400; line-height: 1;
    padding: 10px 11px; border-radius: var(--r-xs);
    transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
:where(body.ly-chrome) .topbar nav a:hover { color: var(--text); background: var(--surface-3); text-decoration: none; }
:where(body.ly-chrome) .topbar nav a:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-chrome) .topbar nav a[aria-current="page"] { color: var(--text); font-weight: 600; }
/* The one pill on the bar is the sign-up; everything else is a plain label.
   The colour has to be restated here: `.topbar nav a` is (0,1,2) and `.btn` is (0,1,0), so without
   this the nav's link colour outranks the button's own and the label renders in text grey on the
   accent fill — invisible on the lemon in dark mode. Same class of bug as any component dropped
   into a scope that styles its element by tag. */
:where(body.ly-chrome) .topbar nav a.btn {
    font-size: 12px; font-weight: 600; padding: 8px 15px; margin-left: 6px;
    color: var(--on-accent); background: var(--accent);
}
:where(body.ly-chrome) .topbar nav a.btn:hover { color: var(--on-accent); background: var(--accent-focus); }
:where(body.ly-chrome) .topbar .sep { width: 1px; height: 18px; background: var(--hairline); margin: 0 6px; }
/* ---- Nav dropdown — build-less: hover + :focus-within, no JS ------------------------------- */
:where(body.ly-chrome) .topbar nav .dropdown { position: relative; display: inline-flex; }
:where(body.ly-chrome) .topbar nav .drop-trigger {
    font: inherit; font-size: 12px; font-weight: 400; line-height: 1; color: var(--text-2);
    background: none; border: 0; cursor: pointer; padding: 10px 11px; border-radius: var(--r-xs);
    display: inline-flex; align-items: center; gap: 4px;
    transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
:where(body.ly-chrome) .topbar nav .drop-trigger:active { transform: none; }
:where(body.ly-chrome) .topbar nav .drop-trigger:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-chrome) .topbar nav .dropdown:hover .drop-trigger,
:where(body.ly-chrome) .topbar nav .dropdown:focus-within .drop-trigger { color: var(--text); background: var(--surface-3); }
:where(body.ly-chrome) .topbar nav .drop-trigger .caret { width: 11px; height: 11px; transition: transform var(--dur-fast) var(--ease); }
:where(body.ly-chrome) .topbar nav .dropdown:hover .drop-trigger .caret,
:where(body.ly-chrome) .topbar nav .dropdown:focus-within .drop-trigger .caret { transform: rotate(180deg); }
:where(body.ly-chrome) .topbar nav .drop-menu {
    position: absolute; top: 100%; left: 0; padding-top: 6px; z-index: 60;
    opacity: 0; visibility: hidden;
    transition: opacity var(--dur-fast) var(--ease), visibility var(--dur-fast);
}
:where(body.ly-chrome) .topbar nav .dropdown:hover .drop-menu,
:where(body.ly-chrome) .topbar nav .dropdown:focus-within .drop-menu { opacity: 1; visibility: visible; }
:where(body.ly-chrome) .topbar nav .drop-menu-box {
    min-width: 190px; background: var(--surface); border: 1px solid var(--hairline);
    border-radius: var(--r-md); padding: 6px; display: flex; flex-direction: column; gap: 1px;
}
:where(body.ly-chrome) .topbar nav .drop-menu-box a { display: block; white-space: nowrap; font-size: 14px; padding: 9px 11px; border-radius: var(--r-xs); }
/* ---- Page container ----------------------------------------------------------------------- */
:where(body.ly-chrome) .wrap {
    width: 100%; max-width: var(--maxw); margin: 0 auto;
    padding: var(--s-xxl) var(--s-lg) var(--s-lg);
    flex: 1;
}
/* A page built from full-bleed bands supplies its own padding; .wrap-flush lets it reach the edges. */
:where(body.ly-chrome) .wrap-flush { max-width: none; padding: 0; }
/* ---- Footer ------------------------------------------------------------------------------- */
:where(body.ly-chrome) .site-footer {
    border-top: 1px solid var(--hairline);
    background: color-mix(in srgb, var(--canvas-alt) 74%, transparent);
    background-image: var(--glass-sheen);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    margin-top: var(--s-section);
    font-size: 12px; line-height: 1.33;
}
/* ...but a page built from full-bleed bands already ends with its last band's own bottom padding,
   so that page-rhythm margin is double-counted AND it exposes 80px of raw canvas — the ledger
   closes on a DARK entry, so measured at the bottom of the home page it read #272729, then 80px of
   #000000, then the #1D1D1F footer. In the shipped LIGHT theme it is worse: 80px of pure white
   between the dark closing band and the parchment footer, which reads as a rendering fault rather
   than as spacing. The hairline above is the boundary; it does not need a gap as well.

   Keyed on the ledger band, not on a layout. A rule a LAYOUT writes for the SHARED footer would
   follow the visitor across a wire:navigate onto pages that layout never renders (Livewire merges
   the <head> and leaves the old inline <style> behind) — the trap documented at length on the
   ledger variant of the risk caveat in ft-live.css. (Named in words on purpose: this block is
   inline in the <head> of EVERY page on both layouts, so spelling that modifier out here put the
   string into /contact's HTML and tripped the guard that says a non-ledger page must not carry it.
   Never quote a Blade directive here either — it is COMPILED inside a CSS comment, so writing one
   out renders it, or fails the whole view with a PHP parse error once its arguments are dropped.)
   Where :has() is unsupported this simply keeps today's margin, which is the status quo.
   layouts.public gives main the padded .wrap and is deliberately untouched. */
:where(body.ly-chrome) main:has(> section.led:last-child) + .site-footer,
:where(body.ly-chrome) main:has(> section.closing:last-child) + .site-footer { margin-top: 0; }
:where(body.ly-chrome) .foot-wrap {
    max-width: var(--maxw); margin: 0 auto;
    padding: var(--s-xl) var(--s-lg) var(--s-md);
    display: flex; gap: var(--s-xxl); flex-wrap: wrap; justify-content: space-between;
}
:where(body.ly-chrome) .foot-brand { max-width: 300px; }
:where(body.ly-chrome) .foot-brand .brand { display: inline-flex; align-items: center; gap: 9px; color: var(--text); text-decoration: none; }
:where(body.ly-chrome) .foot-brand .brand .brand-word { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
:where(body.ly-chrome) .foot-brand p { margin: 10px 0 0; line-height: 1.5; color: var(--text-muted); }
:where(body.ly-chrome) .foot-links { display: flex; gap: var(--s-xxl); flex-wrap: wrap; }
:where(body.ly-chrome) .foot-links .col { display: flex; flex-direction: column; gap: 9px; }
:where(body.ly-chrome) .foot-links .col-h { color: var(--text); font-weight: 600; margin-bottom: 2px; }
:where(body.ly-chrome) .foot-links a { color: var(--text-muted); }
:where(body.ly-chrome) .foot-links a:hover { color: var(--text); text-decoration: underline; }
:where(body.ly-chrome) .foot-legal {
    max-width: var(--maxw); margin: var(--s-lg) auto 0;
    padding: var(--s-md) var(--s-lg) calc(var(--s-lg) + env(safe-area-inset-bottom));
    border-top: 1px solid var(--hairline);
    color: var(--text-muted);
    display: flex; gap: var(--s-md); flex-wrap: wrap; justify-content: space-between; align-items: baseline;
}
:where(body.ly-chrome) .foot-legal p { margin: 0; max-width: 78ch; }
:where(body.ly-chrome) .foot-legal a { color: var(--text-muted); }
:where(body.ly-chrome) .foot-legal a:hover { color: var(--text); }
/* The consent-withdrawal control is a POST, so a button, but it reads as one of the links beside it. The
   flex row already spaces it; the form itself must not introduce a margin of its own. Deliberately does
   NOT quote the visible label here — this <style> is inline in the page, so a comment containing the
   label would satisfy any test or scraper looking for the control's text when the control is absent. */
:where(body.ly-chrome) .foot-legal form.ftx-choices { margin: 0; }
:where(body.ly-chrome) .foot-legal form.ftx-choices button.linklike { color: var(--text-muted); }
:where(body.ly-chrome) .foot-legal form.ftx-choices button.linklike:hover { color: var(--text); }
/* ---- Mobile menu -------------------------------------------------------------------------- */
:where(body.ly-chrome) .nav-toggle { display: none; margin-left: auto; }
@media (max-width: 720px) {
:where(body.ly-chrome) .topbar-inner { padding-left: max(var(--s-md), env(safe-area-inset-left)); padding-right: max(var(--s-md), env(safe-area-inset-right)); gap: var(--s-sm); }
:where(body.ly-chrome) .wrap { padding: var(--s-xl) var(--s-md) var(--s-md); }
:where(body.ly-chrome) .foot-wrap { gap: var(--s-lg); padding: var(--s-lg) var(--s-md) var(--s-sm); }
:where(body.ly-chrome) .foot-links { gap: var(--s-xl); }
:where(body.ly-chrome) .foot-legal { padding-left: var(--s-md); padding-right: var(--s-md); }
/* The bar keeps the brand, the theme toggle and the menu button; every link moves into a panel
       below it. `hide-sm` marks the links that used to be DROPPED at this width — they appear here
       now instead of nowhere. */
:where(body.ly-chrome) .nav-toggle { display: inline-flex; }
:where(body.ly-chrome) .topbar nav {
        display: none; width: 100%; order: 3; margin-left: 0;
        flex-direction: column; align-items: stretch; gap: 1px;
        padding: var(--s-xs) 0 var(--s-sm); border-top: 1px solid var(--hairline);
    }
:where(body.ly-chrome) .topbar nav.is-open { display: flex; }
/* 44px minimum touch height for every row in the panel. */
:where(body.ly-chrome) .topbar nav a,
:where(body.ly-chrome) .topbar nav .drop-trigger {
        width: 100%; text-align: left; justify-content: flex-start;
        min-height: 44px; display: flex; align-items: center;
        padding: 0 var(--s-sm); font-size: 15px; border-radius: var(--r-sm);
    }
:where(body.ly-chrome) .topbar nav a.btn { justify-content: center; margin: var(--s-xs) 0 0; font-size: 15px; }
:where(body.ly-chrome) .topbar .sep { display: none; }
/* A hover dropdown is meaningless on touch: flatten it into the list it was hiding. */
:where(body.ly-chrome) .topbar nav .dropdown { display: block; width: 100%; }
:where(body.ly-chrome) .topbar nav .drop-trigger .caret { display: none; }
:where(body.ly-chrome) .topbar nav .drop-menu { position: static; opacity: 1; visibility: visible; padding-top: 0; }
:where(body.ly-chrome) .topbar nav .drop-menu-box {
        border: 0; background: none; padding: 0 0 0 var(--s-sm);
        /* It stops being a pane here, so it stops taking the pane material. */
        box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; border-radius: 0;
    }
:where(body.ly-chrome) .topbar nav .drop-menu-box a { padding-left: var(--s-sm); }
/* The theme toggle is a control, not a nav item — it stays on the bar beside the menu button. */
:where(body.ly-chrome) .topbar nav .icon-btn { position: absolute; top: 2px; right: 60px; }
}
/* Above the breakpoint the panel is always the horizontal bar, whatever the toggle last set. */
@media (min-width: 721px) {
:where(body.ly-chrome) .topbar nav.is-open { display: flex; }
}


/* ---- marketing layout  <- resources/views/layouts/marketing.blade.php   [scope: ly-marketing] ---- */

/* ---------------------------------------------------------------------------------------
   MARKETING SURFACE — the logged-out site.

   There are no card shadows and no gradients anywhere on these pages. The ONE shadow in the
   system is spent on the sample Trader Passport, which is the deliberate translation of
   Apple's product render: this platform's product is a credential, so the credential is the
   thing given weight.

   The section-stacking system that used to live here (centred bands, three-column feature
   grids, a boxed step list, a gradient CTA band) has been DELETED, not deprecated — see the
   ledger system below. Only two rules from it survive, because both still have consumers:
   `.prose` renders staff-authored CMS pages, and `.risk-note` is the shared caveat partial.
   --------------------------------------------------------------------------------------- */
/* ---- Risk caveat -------------------------------------------------------------------------
   NOT styled here. Both the baseline AND the ledger-spine variant live in ft-live.css, and each
   marketing page opts in by passing `ftRiskLedger => true` when it includes partials.risk-note.
   (Described in words, never written as the directive: Blade compiles an include directive even
   inside a CSS comment, so quoting the call here first rendered a second copy of the caveat into
   every marketing page's <style> block, and then — once the arguments were dropped from the
   quote — a PHP parse error. Use the doubled sigil if a directive ever must be shown.)
   Styling a SHARED partial from a layout is unsafe anyway:
   Livewire's wire:navigate merges the <head> and leaves this block in place, so these rules
   used to follow the visitor onto /contact (layouts.public) and indent its small print by
   129px. See the comment in ft-live.css. */
/* ---- Rendered markdown (content pages) ---------------------------------------------------- */
:where(body.ly-marketing) .prose { max-width: var(--maxw-read); color: var(--text-2); line-height: 1.6; }
:where(body.ly-marketing) .prose h2 { font-size: 28px; margin: var(--s-xl) 0 var(--s-sm); color: var(--text); letter-spacing: -.015em; }
:where(body.ly-marketing) .prose h3 { font-size: 21px; margin: var(--s-lg) 0 var(--s-xs); color: var(--text); letter-spacing: -.008em; }
:where(body.ly-marketing) .prose p { margin: 0 0 var(--s-md); }
:where(body.ly-marketing) .prose ul,
:where(body.ly-marketing) .prose ol { margin: 0 0 var(--s-md); padding-left: 22px; }
:where(body.ly-marketing) .prose li { margin: 5px 0; }
:where(body.ly-marketing) .prose code { background: var(--surface-3); padding: 1px 6px; border-radius: var(--r-xs); font-size: .88em; }
:where(body.ly-marketing) .prose blockquote { margin: 0 0 var(--s-md); padding: 4px 0 4px var(--s-md); border-left: 2px solid var(--accent); color: var(--text-muted); }
:where(body.ly-marketing) .prose hr { border: 0; border-top: 1px solid var(--hairline); margin: var(--s-lg) 0; }
/* ==========================================================================================
   THE LEDGER SYSTEM — the structural language of the logged-out site.

   The old marketing pages were a stack of centred bands, each holding a three-column grid of
   feature cards. That shape is the reason the site read as a template, and it is replaced
   wholesale by this: one continuous hairline spine with numbered row keys, and a small set of
   ROW GRAMMARS that sections choose between. The rule that keeps it from collapsing back into
   a template is that no two consecutive sections may use the same grammar.

   Available grammars: `.figs` (measured figures), `.clauses` (a claim plus the mechanism that
   enforces it), `.notes` (native disclosures). Pages add their own where the content earns it.
   ========================================================================================== */
/* ------------------------------------------------------------------------------------------
   THE SPINE. One grid, declared once, inherited by every section — which is what makes the
   hairline continuous rather than a per-section decoration that drifts.
   ------------------------------------------------------------------------------------------ */
:where(body.ly-marketing) main > section.led {
    --rail: clamp(44px, 6vw, 84px);
    --gut: max(var(--s-md), calc((100% - 1180px) / 2));
    position: relative;
    /* The section rhythm, tightened a third time on the same report ("too much empty space").
       Two paddings meet at every boundary, so the coefficient is halved in effect: 5.2vw put
       150px of nothing between the last line of one entry and the heading of the next at
       1440px, and 4.4vw puts 127px. The FLOOR stays 40px so nothing changes below ~910px,
       where the clamp was already bottoming out and the page is tight enough already. */
    padding: clamp(40px, 4.4vw, 66px) var(--gut);
    background: var(--canvas);
    display: grid;
    grid-template-columns: [key-s] var(--rail) [body-s] minmax(0, 1fr) [body-e];
    column-gap: clamp(12px, 2vw, 28px);
    align-content: start;
}
/* The chamber. A TONE STEP from the page, taken within the active theme — see --chamber. It carries
   no token overrides at all now: the page's own tokens are already correct on it, which is why the
   entry's colour is plain var(--text) rather than the old --on-dark. */
:where(body.ly-marketing) main > section.led--dark { background: var(--chamber); color: var(--text); }
/* Two dark entries in a row are ONE chamber, not two tiles — so the boundary between them gets
   no doubled padding and the spine runs through unbroken. Without this the hero and the issuance
   sequence are separated by ~150px of nothing on the same background. */
:where(body.ly-marketing) main > section.led--dark + section.led--dark { padding-top: 0; }
/* The spine itself: one hairline at the rail boundary, drawn per section so it is unbroken from
   the top of the page to the last entry. `--fill` is written by the scroll observer (0..1) and
   is the only scroll-linked value on the page. */
:where(body.ly-marketing) .led::before {
    content: ""; position: absolute; top: 0; bottom: 0;
    left: calc(var(--gut) + var(--rail) - 1px); width: 1px;
    background: var(--hairline);
}
:where(body.ly-marketing) .led::after {
    content: ""; position: absolute; top: 0; left: calc(var(--gut) + var(--rail) - 1px);
    width: 1px; height: calc(var(--fill, 0) * 100%);
    background: var(--accent); opacity: .55;
}
/* NO chamber-specific spine rule here. A later `.led::before` derives the spine from --text, which
   already follows the theme and is the same specificity — so a rule here would be dead code that
   reads as if it were doing something. --chamber-line is still real: the spec plate uses it. */
/* The row key — the ledger's line number, DERIVED from position with a counter so a
   conditional section can never leave a gap in the sequence. The Blade supplies only the
   label; `00 ·` is prepended here. Sticky so the key stays beside the entry it names. */
:where(body.ly-marketing) main { counter-reset: led-row -1; }
:where(body.ly-marketing) .led__key::before {
    counter-increment: led-row;
    content: counter(led-row, decimal-leading-zero) " \00b7 ";
}
:where(body.ly-marketing) .led__key {
    /* justify-self: end shrink-wraps the key to its own width and parks it against the spine.
       Left stretched, vertical-rl places the line box at one edge of the FULL rail and the
       180deg rotation throws it to the far side, so the label drifts away from the line it
       names as the rail widens. */
    grid-column: key-s / body-s; justify-self: end;
    position: sticky; top: 68px; align-self: start;
    /* Clear of the spine — with a MARGIN, not padding.
       `writing-mode: vertical-rl` puts the line box at the element's RIGHT edge, so padding-right
       does separate the ink from that edge... and then `transform: rotate(180deg)` spins the box
       about its centre and lands the ink on the LEFT with the padding against the spine, which is
       the exact opposite of what was wanted. A transform flips physical sides; padding cannot
       survive it. A margin moves the whole box in LAYOUT, before the transform, and the rotation
       then happens in place — so the gap holds whichever way the box is spun. */
    margin-right: 16px;
    font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-muted); font-variant-numeric: tabular-nums;
    writing-mode: vertical-rl; text-orientation: mixed; transform: rotate(180deg);
    white-space: nowrap;
}
:where(body.ly-marketing) .led__body { grid-column: body-s / body-e; min-width: 0; }
/* The rail collapses on a phone: the key moves above its entry and the spine hugs the edge.
   This lives WITH the spine rather than in a page's own CSS — it is part of the system, and
   when it sat in welcome.blade.php only the home page collapsed, leaving every sub-page with
   44px of vertical rail on a 375px screen. */
@media (max-width: 640px) {
:where(body.ly-marketing) main > section.led {
        --rail: 0px; column-gap: 0;
        grid-template-columns: [key-s] 0 [body-s] minmax(0, 1fr) [body-e];
        padding-left: calc(var(--gut) + 14px);
    }
:where(body.ly-marketing) .led::before,
:where(body.ly-marketing) .led::after { left: var(--gut); }
:where(body.ly-marketing) .led__key {
        grid-column: body-s / body-e; writing-mode: horizontal-tb; transform: none;
        position: static; margin: 0 0 var(--s-sm);
    }
}
/* Nothing on these pages is centred. Headings hang from the spine's content edge. */
:where(body.ly-marketing) .led h2 {
    font-size: clamp(28px, 3.6vw, 42px); line-height: 1.08; letter-spacing: -.02em;
    margin: 0 0 var(--s-sm); max-width: 20ch; text-wrap: balance;
}
:where(body.ly-marketing) .led .lede { font-size: clamp(17px, 1.6vw, 21px); line-height: 1.45; color: var(--text-2); max-width: 58ch; margin: 0 0 var(--s-xl); }
:where(body.ly-marketing) .led--dark .lede { color: var(--text-2); }
:where(body.ly-marketing) .led .note { font-size: 13px; line-height: 1.5; color: var(--text-muted); margin: var(--s-sm) 0 0; max-width: 62ch; }
:where(body.ly-marketing) .led .more { margin: var(--s-lg) 0 0; font-size: 15px; }
/* ------------------------------------------------------------------------------------------
   03 · EXECUTION — three measured figures, hairline-separated. No diagram: twelve unlabelled
   dots on an arc would say less than the numbers and imply live traffic that is not there.
   ------------------------------------------------------------------------------------------ */
:where(body.ly-marketing) .figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); border-top: 1px solid var(--hairline); }
:where(body.ly-marketing) .figs div { padding: var(--s-lg) var(--s-md) var(--s-lg) 0; border-bottom: 1px solid var(--hairline); }
:where(body.ly-marketing) .figs b { display: block; font-size: clamp(30px, 4vw, 46px); font-weight: 600; letter-spacing: -.03em; line-height: 1; font-variant-numeric: tabular-nums; }
:where(body.ly-marketing) .figs b i { font-style: normal; font-size: .4em; font-weight: 400; color: var(--text-muted); margin-left: 3px; }
:where(body.ly-marketing) .figs span { display: block; font-size: 13px; color: var(--text-muted); margin-top: 9px; line-height: 1.4; }
@media (max-width: 700px) {
:where(body.ly-marketing) .figs { grid-template-columns: 1fr; }
}
/* ------------------------------------------------------------------------------------------
   06 · CLAUSES — the guarantees, with the enforcement mechanism named on the right. A claim
   that says HOW it is enforced is a different kind of statement from a claim in a card.
   ------------------------------------------------------------------------------------------ */
:where(body.ly-marketing:not(.v2)) .clauses { border-top: 1px solid var(--hairline); }
:where(body.ly-marketing:not(.v2)) .clauses > div { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 148px); gap: var(--s-sm) var(--s-lg); padding: var(--s-lg) 0; border-bottom: 1px solid var(--divider-soft); align-items: start; }
:where(body.ly-marketing:not(.v2)) .clauses h3 { font-size: 19px; letter-spacing: -.01em; margin: 0 0 6px; }
:where(body.ly-marketing:not(.v2)) .clauses p { margin: 0; font-size: 15px; line-height: 1.5; color: var(--text-muted); max-width: 62ch; }
:where(body.ly-marketing:not(.v2)) .clauses .enf { font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--text-muted); text-align: right; padding-top: 4px; }
@media (max-width: 700px) {
:where(body.ly-marketing:not(.v2)) .clauses > div { grid-template-columns: 1fr; }
:where(body.ly-marketing:not(.v2)) .clauses .enf { text-align: left; }
}
/* ------------------------------------------------------------------------------------------
   04 · THE RATCHET — the high-water mark, drawn. The y-axis is deliberately unnumbered: this
   shows a RULE, and numbering it would imply a real account.

   THE FIGURE SITS BESIDE THE PROSE, and that is a legibility fix rather than a taste one.
   An SVG's font-size and stroke-width are in USER UNITS, so they are multiplied by the viewBox
   scale: widening the drawing MAGNIFIES its own type and lines. When the chart was given the
   full pane to close an enclosed gap it rendered 1000px wide — scale 1.923 — so its 11-unit
   labels came out at 21px (larger than the 17px body copy, and 63% larger than the 13px note
   beside them) and its 2.5-unit data line at 4.8px. It read as a poster, not as a figure.
   Beside the prose the drawing lands near 490px, which is scale ~0.94.

   The labels are HTML now, not <text>, so they are 13 real CSS px whatever the drawing does —
   at 1920 and at 320 alike. The old fix was viewport media queries, which key on the WINDOW
   and not on the chart's own rendered width, and inside a two-column split those are not the
   same number: at a 1024px window the drawing is ~420px and no bump fires, so the labels
   would have been 8.9px with nothing to say so.

   THE HAIRLINES — AND ONLY THE HAIRLINES — ARE non-scaling-stroke, and the exception is not
   fussiness. `.line` carries `.ft-draw`, which draws itself by animating stroke-dashoffset
   over a path normalised with pathLength="1". non-scaling-stroke moves dash computation into
   the rendered space while the pathLength normalisation stays in user space, so the painted
   fraction collapses to 1/scale — and above scale 1 the tail of the path is simply never
   drawn. MEASURED at a 900px window (one column, scale 1.62): the last 40% of the line was
   missing, which on this figure is the new high above the previous peak, i.e. the entire
   thing it is drawn to show. It fails silently and only above scale 1, so it is invisible at
   the desktop width anyone would check. MarketingLedgerTest pins the split.

   The static strokes keep it and gain: `.grid` and `.peak` are authored at stroke-width 1 and
   were rendering at 1.9px while the drawing was 1000px wide — a doubled hairline against a
   card whose every other rule is 1px. They are exactly 1px now, and `.peak`'s dashes are in
   rendered px too, which is why the dasharray is 5 5 and not the 4 4 it needed while it was
   being scaled up. The data line scales with the drawing: 2.0-2.7px across the two-column
   widths (against 4.8px before), and heavier only in the one-column fallback, where the whole
   figure is large and a proportionally heavier line is right.
   ------------------------------------------------------------------------------------------ */
:where(body.ly-marketing) .hwm { width: 100%; height: auto; display: block; max-width: none; overflow: visible; }
:where(body.ly-marketing) .hwm .grid,
:where(body.ly-marketing) .hwm .peak { vector-effect: non-scaling-stroke; }
:where(body.ly-marketing) .hwm .grid { stroke: var(--hairline); stroke-width: 1; }
:where(body.ly-marketing) .hwm .peak { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 5 5; }
:where(body.ly-marketing) .hwm .line { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
:where(body.ly-marketing) .hwm .charged { fill: var(--accent); opacity: .16; }
html.js :where(body.ly-marketing) .hwm .charged { opacity: 0; transition: opacity 500ms var(--ease) 900ms; }
html.js :where(body.ly-marketing) .in .hwm .charged { opacity: .16; }
@media (prefers-reduced-motion: reduce) {
html.js :where(body.ly-marketing) .hwm .charged { opacity: .16 !important; transition: none !important; }
}
/* The peak annotation is HTML laid over the drawing, so its position has to be stated as a
   fraction of the viewBox rather than in user units. For the geometry both pages draw —
   viewBox "48 12 460 296", peak line at y 110.7, label anchored at x 186 —
     top  = (110.7 − 12) / 296 = 33.34%
     left = (186 − 48)  / 460 = 30.00%
   An SVG with the default preserveAspectRatio fills this box exactly, so both fractions hold
   at every rendered size. MarketingLedgerTest recomputes them from the markup, because a
   label that has quietly detached from the line it names still looks deliberate. */
:where(body.ly-marketing) .hwm-plot { position: relative; --hwm-peak-top: 33.34%; --hwm-peak-left: 30%; }
:where(body.ly-marketing) .hwm-peak {
    position: absolute; left: var(--hwm-peak-left); top: var(--hwm-peak-top);
    transform: translateY(-100%); margin-top: -6px;
    /* Wraps rather than overflowing the drawing at the narrowest widths: it starts 30% in, so
       this is what is left of the line it is naming. */
    max-width: calc(100% - var(--hwm-peak-left) - 4px);
    font-size: 13px; line-height: 1.35; color: var(--text-muted);
}
/* The axis captions are a row, not two absolutely-placed labels: at 360px "shaded = the only
   part a fee is charged on" is wider than the half it would be given, and a flex row lets it
   wrap upward instead of colliding with "Time". */
:where(body.ly-marketing) .hwm-axis {
    display: flex; justify-content: space-between; gap: var(--s-md);
    margin: 9px 0 0; padding-top: 9px; border-top: 1px solid var(--hairline);
    font-size: 13px; line-height: 1.4; color: var(--text-muted);
}
:where(body.ly-marketing) .hwm-axis span:last-child { text-align: right; }
/* ROWS — a hairline list of title + explanation. This is what the three-column feature-card
   grid becomes: same content, no cards, no tinted icon squares, ragged-right. It is the
   default grammar for reference sections on the sub-pages. */
:where(body.ly-marketing) .rows { border-top: 1px solid var(--hairline); }
:where(body.ly-marketing) .rows > div {
    display: grid; grid-template-columns: minmax(0, .42fr) minmax(0, 1fr);
    gap: var(--s-xs) var(--s-xl); padding: var(--s-lg) 0;
    border-bottom: 1px solid var(--divider-soft); align-items: start;
}
:where(body.ly-marketing) .rows h3 { font-size: 19px; margin: 0; letter-spacing: -.01em; line-height: 1.3; }
:where(body.ly-marketing) .rows p { margin: 0; font-size: 16px; line-height: 1.55; color: var(--text-muted); max-width: 64ch; }
:where(body.ly-marketing) .rows p + p { margin-top: var(--s-xs); }
@media (max-width: 760px) {
:where(body.ly-marketing) .rows > div { grid-template-columns: 1fr; }
}
/* STEPS — numbered entries hung off the spine. Replaces .step-row's boxed card. */
:where(body.ly-marketing) .steps { border-top: 1px solid var(--hairline); counter-reset: led-step; }
:where(body.ly-marketing) .steps > div {
    display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--s-sm) var(--s-md);
    padding: var(--s-lg) 0; border-bottom: 1px solid var(--divider-soft); align-items: start;
}
:where(body.ly-marketing) .steps > div::before {
    counter-increment: led-step; content: counter(led-step, decimal-leading-zero);
    font-size: 12px; font-weight: 600; letter-spacing: .06em; color: var(--text-muted);
    font-variant-numeric: tabular-nums; padding-top: 5px;
}
/* A TRAP, made harmless rather than left to be remembered. The ::before above fills column 1, so a step
   written as `<div><h3>…</h3><p>…</p></div>` — the obvious markup, and the shape every OTHER grammar here
   takes — puts the heading in column 2 and the paragraph back in the 44px number column, which renders it
   one word per line. Every existing step wraps its content in an inner <div>; pinning the columns means the
   unwrapped form merely stacks instead of collapsing. Harmless to the wrapped form, whose children are not
   direct descendants and so are not matched. */
:where(body.ly-marketing) .steps > div > h3,
:where(body.ly-marketing) .steps > div > p { grid-column: 2; }
:where(body.ly-marketing) .steps h3 { font-size: 19px; margin: 0 0 6px; letter-spacing: -.01em; }
:where(body.ly-marketing) .steps p { margin: 0; font-size: 16px; line-height: 1.55; color: var(--text-muted); max-width: 64ch; }
/* 08 · CLOSING ENTRY — the ledger closes on the dark ground it opened on. */
:where(body.ly-marketing) .close__in { display: flex; gap: var(--s-lg); align-items: flex-end; justify-content: space-between; flex-wrap: wrap; }
:where(body.ly-marketing) .close h2 { color: var(--text); margin-bottom: 6px; }
/* Scoped to the body, not the whole section: `.close p` also matched `.led__key`, which is a
   <p>, and beat it on specificity — the ledger's last row number rendered at 17px. */
:where(body.ly-marketing) .close__in p { margin: 0; color: var(--text-2); font-size: 17px; max-width: 46ch; }
/* 07 · NOTES — native <details>, hung on the spine rather than boxed. */
:where(body.ly-marketing) .notes { border-top: 1px solid var(--hairline); max-width: 76ch; }
:where(body.ly-marketing) .notes details { border-bottom: 1px solid var(--divider-soft); }
:where(body.ly-marketing) .notes summary { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-md); padding: var(--s-md) 0; font-size: 17px; font-weight: 600; cursor: pointer; }
:where(body.ly-marketing) .notes summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-xs); }
:where(body.ly-marketing) .notes summary::after { content: "+"; color: var(--accent-text); font-size: 20px; font-weight: 400; line-height: 1; flex-shrink: 0; }
:where(body.ly-marketing) .notes details[open] summary::after { content: "\2212"; }
:where(body.ly-marketing) .notes .a { margin: 0; padding: 0 0 var(--s-md); font-size: 15px; line-height: 1.6; color: var(--text-muted); max-width: 68ch; }
/* 09 · GET THE APP — /download only. Not a row grammar: it is one figure (the QR) beside one
   action (the button), which is a shape no other page needs, so it is named for the page. */
:where(body.ly-marketing) .dl-get {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: clamp(20px, 3vw, 40px);
    align-items: start;
    margin-top: var(--s-lg);
    padding-top: var(--s-lg);
    border-top: 1px solid var(--hairline);
}
/* One column early. The QR panel is a fixed 190px and the button needs room for its label, so
   they stop fitting side by side well before the layout's own 640px rail collapse. */
@media (max-width: 720px) {
    :where(body.ly-marketing) .dl-get { grid-template-columns: 1fr; }
}
/* The COLUMN is wider than the panel on purpose. Capped at the panel's width, the caption's URL had to
   break mid-word ("…/down load"), which is the one string on the page somebody may need to read and retype. */
:where(body.ly-marketing) .dl-qr { display: flex; flex-direction: column; gap: var(--s-sm); max-width: 250px; }
/* WHITE, WITH DARK INK, IN BOTH THEMES — and that is deliberate rather than an oversight.
   A QR code's contrast is a machine-readability requirement, not a style choice: the symbology
   specifies dark modules on a light background, and while most modern scanners cope with an
   inverted code, some refuse one outright. A failed scan here costs an install, so the panel
   carries its own ground and does not follow the theme. The two colours live HERE rather than in
   the Blade, so the view still carries no hex; the path paints with currentColor.
   The border is fixed for the same reason — a themed hairline would be invisible on white in one
   of the two themes, which is the exact bug the token system exists to prevent. */
:where(body.ly-marketing) .dl-qr__panel {
    background: #ffffff;
    color: #0b0b0c;
    border: 1px solid #d8d8dc;
    border-radius: var(--r-md);
    /* The padding is quiet zone too. The code's own margin is ~3.5 modules; this adds ~2 more, which puts the
       total comfortably past the 4 the symbology asks for. */
    padding: 10px;
    line-height: 0;
    max-width: 208px;
}
:where(body.ly-marketing) .dl-qr__code { display: block; width: 100%; height: auto; }
:where(body.ly-marketing) .dl-qr__cap { margin: 0; font-size: 12px; line-height: 1.45; color: var(--text-muted); }
/* `anywhere`, not `break-all`: break-all splits eagerly at any character even when the word would have fit,
   which is what put a line break inside the path. This breaks only when there is no alternative. */
:where(body.ly-marketing) .dl-qr__url { color: var(--text-2); font-weight: 600; overflow-wrap: anywhere; }
/* What scanning will do, said before it happens. Deliberately the same size and colour as the caption above
   it rather than a warning tone: an immediate download is the intended behaviour, not a hazard. `--text-faint`
   was measured and rejected here — at 12px against the parchment it is the ramp's floor, and this line is the
   one that stops an unexpected download reading as a fault. */
:where(body.ly-marketing) .dl-qr__note { margin: 0; font-size: 12px; line-height: 1.45; color: var(--text-muted); }
:where(body.ly-marketing) .dl-take { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-sm); }
:where(body.ly-marketing) .dl-note { margin: 0; font-size: 14px; line-height: 1.5; color: var(--text-muted); max-width: 52ch; }
/* The dormant state's headline. Sized like a statement rather than an error: nothing is broken,
   there is simply not a build yet, and red would say otherwise. */
:where(body.ly-marketing) .dl-none { margin: 0; font-size: 17px; font-weight: 600; color: var(--text); }
/* A real limitation of the published build (single-architecture). `--warn` rather than `--down`: it is a
   caveat somebody must read BEFORE spending 46 MB, not a failure that has happened. Left-ruled rather than
   boxed — a filled panel here would be a second card inside a card. */
:where(body.ly-marketing) .dl-warn {
    margin: 0;
    padding-left: var(--s-sm);
    border-left: 2px solid var(--warn);
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-2);
    max-width: 52ch;
}
:where(body.ly-marketing) .dl-meta { display: flex; flex-wrap: wrap; gap: var(--s-xs) var(--s-lg); margin: var(--s-xs) 0 0; }
:where(body.ly-marketing) .dl-meta > div { display: flex; flex-direction: column; gap: 2px; }
:where(body.ly-marketing) .dl-meta dt { font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--text-muted); }
:where(body.ly-marketing) .dl-meta dd { margin: 0; font-size: 15px; color: var(--text); font-variant-numeric: tabular-nums; }
:where(body.ly-marketing) .dl-sum { max-width: 62ch; }
:where(body.ly-marketing) .dl-sum summary {
    display: inline-flex; align-items: center; gap: 6px;
    /* Padding, not decoration: 13px type on a bare <summary> is a ~19px target, under WCAG 2.2's 24px
       floor and well under the 44px this system uses elsewhere. */
    padding: 6px 0;
    font-size: 13px; font-weight: 600; color: var(--accent-text); cursor: pointer;
}
/* Base `summary { list-style: none }` strips the native triangle from every disclosure in the system,
   so each component supplies its own affordance — `.notes` uses +/−, and without this the checksum
   disclosure was a bare line of green text with nothing saying it opens. */
:where(body.ly-marketing) .dl-sum summary::after { content: "+"; font-size: 15px; font-weight: 400; line-height: 1; }
:where(body.ly-marketing) .dl-sum[open] summary::after { content: "\2212"; }
:where(body.ly-marketing) .dl-sum summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-xs); }
/* `--text-2`, not `--text-muted`: the shell commands render as <code> at ~11.4px, and measured from the
   PAINTED PIXELS of this page --text-muted came in just under AA there. The margin is small either way and
   depends on which surface it is read over, so the darker token is used rather than the marginal one — this
   is the paragraph whose whole purpose is to be read character by character. */
:where(body.ly-marketing) .dl-sum p { margin: var(--s-xs) 0 0; font-size: 13px; line-height: 1.55; color: var(--text-2); }
/* The digest wraps rather than scrolls: it is 64 characters somebody may want to select whole,
   and a horizontal scrollbar inside a card is worse than three short lines. */
:where(body.ly-marketing) .dl-sum__hash code { font-size: 12px; word-break: break-all; color: var(--text-2); }
:where(body.ly-marketing) .dl-stores { display: flex; flex-wrap: wrap; gap: var(--s-sm); margin-top: var(--s-xs); }
/* Store links are token-styled, NOT the official Google/Apple badge images. Those are trademarked
   artwork with their own brand rules on size, clear space and wording, and drawing an approximation
   would be passing off something we made as theirs. A plain link that says where it goes is honest
   and on-brand; swap in each store's supplied asset when there is a listing to point at. */
:where(body.ly-marketing) .dl-store {
    display: inline-flex;
    flex-direction: column;
    gap: 1px;
    padding: 9px 16px;
    border: 1px solid var(--field-border);
    border-radius: var(--r-sm);
    text-decoration: none;
    color: var(--text);
    transition: border-color .15s ease, background-color .15s ease;
}
:where(body.ly-marketing) .dl-store:hover { text-decoration: none; background: var(--surface-3); border-color: var(--text-muted); }
:where(body.ly-marketing) .dl-store:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-marketing) .dl-store:active { transform: scale(.95); }
:where(body.ly-marketing) .dl-store__pre { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
:where(body.ly-marketing) .dl-store__name { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
/* ================================ LIQUID GLASS ================================
   CARD EVERY ENTRY, with no Blade change on any of the six marketing pages.

   `.led__body` is already the element the ledger contract requires as a direct child of
   every section, so making IT the pane turns the whole logged-out site into a ledger of
   cards while the spine, the counter-derived row keys and the no-two-consecutive-grammars
   rule survive untouched. Adding a wrapper would have broken the first of those.

   Tokens come from public/ft-live.css; a dark chamber inverts the whole glass set there, so
   a pane inside one carries pale ink on a dark tint instead of composing to #c3c3c3 and
   taking its link text to 2.82:1. */
:where(body.ly-marketing) .led__body {
    padding: clamp(20px, 2.6vw, 34px);
    border-radius: var(--r-lg);
    background-color: var(--glass);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    box-shadow: var(--glass-edge), var(--cast);
}
/* Two abutting dark entries share ONE chamber, so the section between them carries no top
   padding — which with panes would butt two cards edge to edge. Restore just enough gap for
   the two lit edges to read as two panes. */
:where(body.ly-marketing) main > section.led--dark + section.led--dark { padding-top: clamp(14px, 1.6vw, 22px); }
/* A FIGURE IS NOT A PANE — this rule used to give `.close__in, .hwm` the material and both
   were wrong. `.hwm` is a chart nested INSIDE an entry that is already a card, so a border, a
   sheen and a cast shadow of its own made a card within a card: the chart read as a boxed
   inset rather than as a drawing on the page. (Second time: `.spark-wrap` had to be pulled out
   of the same list for the same reason, so the rule is now written down.) `.close__in` never
   needed it either — it is always on the SAME element as `.led__body`, which already carries
   the material, so the declaration was pure duplication.
   Anything that draws (an svg, a chart holder, a sparkline) sits ON a pane. It is not one. */
/* The spine has to stay visible against a translucent chamber rather than an opaque band. */
:where(body.ly-marketing) .led::before { background: color-mix(in srgb, var(--text) 18%, transparent); }
/* ================================ FILLING THE PANE ================================
   Before every entry became a card, the empty right-hand side was the ledger's ragged-right
   language: a column of prose simply ended, which is what prose does. A CARD's visible edge
   changes what that space MEANS — bounds promise to be filled, so the identical gap reads as
   a hole. Measured across twelve pages, eight cards had an enclosed gap of 18-48% of their
   content box. Two different causes, so two different fixes.

   (1) A FIGURE that was capped narrower than its card just fills it. No new content, and the
       figure is better for it — see .hwm and .notes below.
   (2) PROSE must stay at a reading measure, so the space beside it takes a real aside. That is
       .led__spec: a compressed, per-page statement of the same terms the entry argues at
       length. Never invented copy — every line restates a claim the page already makes. */
:where(body.ly-marketing) .led__body:has(> .led__spec) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(210px, 300px);
    column-gap: clamp(24px, 4vw, 60px);
    align-items: start;
}
/* No wrapper element around the prose: the existing children are placed into column one and
   the aside into column two, so adding an aside costs one element rather than a restructure
   (and cannot disturb the one-direct-child-.led__body rule the ledger contract enforces). */
:where(body.ly-marketing) .led__body:has(> .led__spec) > :not(.led__spec) { grid-column: 1; }
/* `grid-row: 1` was wrong and looked absurd: the aside then SHARES row one with the eyebrow,
   so the row grows to the aside's height and the headline is shoved ~280px down the card.
   Spanning instead lets the aside's height distribute across rows the prose does not fill.
   `-1` cannot be used — it resolves against the EXPLICIT grid, and there are none here — hence
   a span large enough for any entry. row-gap stays 0 (only column-gap is set), so the surplus
   implicit rows cost nothing. */
:where(body.ly-marketing) .led__body > .led__spec { grid-column: 2; grid-row: 1 / span 99; align-self: start; }
:where(body.ly-marketing) .led__spec { margin: 0; display: grid; align-content: start; }
:where(body.ly-marketing) .led__spec > div {
    display: grid; gap: 3px;
    padding: 12px 0;
    border-top: 1px solid var(--hairline);
}
:where(body.ly-marketing) .led__spec > div:first-child { border-top: 0; padding-top: 2px; }
/* Reuses the eyebrow's voice rather than introducing a new type role. */
:where(body.ly-marketing) .led__spec dt {
    font-size: 10.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
    color: var(--text-muted);
}
:where(body.ly-marketing) .led__spec dd { margin: 0; font-size: 14px; line-height: 1.42; color: var(--text-2); }
/* Below the two-column threshold the aside becomes a footer of the same entry. It keeps its
   hairlines, so it still reads as a spec strip rather than as more prose. */
@media (max-width: 900px) {
:where(body.ly-marketing) .led__body:has(> .led__spec) { grid-template-columns: minmax(0, 1fr); }
:where(body.ly-marketing) .led__body:has(> .led__spec) > :not(.led__spec),
:where(body.ly-marketing) .led__body > .led__spec { grid-column: 1; grid-row: auto; }
:where(body.ly-marketing) .led__body > .led__spec { margin-top: var(--s-md); padding-top: var(--s-sm); border-top: 1px solid var(--hairline); }
:where(body.ly-marketing) .led__spec > div:first-child { border-top: 0; }
}
/* (1) The chart was capped at 640px inside a 1000px card — 318px of enclosed nothing. Its cap
       is lifted (see the RATCHET block above), but the FIRST fix here was to let it fill the
       whole pane, and that traded one defect for a worse one: an SVG scales its own type and
       strokes with the drawing, so a 1000px-wide chart shouted its labels at 21px over 17px
       body copy. Reported as "ugly and over zoomed", which is exactly what it was.

       .led__fig is the correction and the third answer to "what fills a card". (1) was a
       figure that simply widens; (2) was prose plus a compressed .led__spec aside. This is
       (3): the figure sits BESIDE the prose, so both columns are filled by real content and
       neither has to be inflated to cover ground. Use it when an entry has one drawing worth
       looking at while it is being explained — the reader can hold the sentence and the shape
       at once, which stacking them never allows.

       PROSE LEFT, FIGURE RIGHT is not interchangeable here. The ledger's spine runs down the
       LEFT and every heading hangs off it; putting the drawing there would break that
       alignment for one entry. It also keeps DOM order and visual order identical, so the
       single-column fallback reads heading → explanation → figure with no grid reordering and
       no focus-order surprise on the link the entry contains. */
:where(body.ly-marketing) .led__fig {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
    column-gap: clamp(24px, 3.6vw, 52px);
    align-items: start;
}
:where(body.ly-marketing) .led__fig > figure { margin: 0; min-width: 0; }
/* A caption under a figure belongs to the figure, so it takes the figure's measure rather than
   .led .note's 62ch reading measure — which would stop 80px short of the drawing it describes
   and reopen a small version of the hole this whole block exists to close. */
:where(body.ly-marketing) .led__fig > figure > .note { max-width: none; margin-top: var(--s-sm); }
/* Collapses at the same width as .led__spec: below this the text column is too narrow to hold
   a 42px heading and the drawing is too narrow to be worth reading. Figure follows prose,
   which is already the DOM order. */
@media (max-width: 900px) {
:where(body.ly-marketing) .led__fig { grid-template-columns: minmax(0, 1fr); row-gap: var(--s-lg); }
}
/* (1) Four disclosures in one column left 261px empty. Two columns fill the pane and halve the
       scroll; a details element that opens simply grows its own row. */
:where(body.ly-marketing) .notes {
    display: grid;
    /* `min(300px, 100%)`, not a bare 300px. auto-fit collapses the NUMBER of columns as the container
       narrows but cannot shrink a track below its own minimum, so a bare 300px floor kept one 300px column
       inside a ~269px content box and pushed the page 31px wider than the viewport at 320px — measured, on
       the home page as well as here, both of which use this grammar. min() lets the last track give way.
       Identical at every width where 300px fits, which is why nothing above 320px moves. */
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    column-gap: clamp(24px, 4vw, 56px);
    /* The 76ch cap above is a READING measure, which is right for one column of prose and
       wrong for a grid of them — it was what kept the two columns inside 739px of a 1000px
       card, so the pane got shorter but the hole stayed exactly 261px wide. */
    max-width: none;
}
:where(body.ly-marketing) .notes > details { align-self: start; }


/* ---- public layout  <- resources/views/layouts/public.blade.php   [scope: ly-public] ---- */

/* ---------------------------------------------------------------------------------------
   DISCOVERY SURFACE — the Marketplace / Directory pattern.

   The card is the unit of this whole layout, and it is where the visual language does the
   most work: a hairline, a radius, and a figure. No shadow (the one shadow in the system is
   reserved for the artifact), no gradient, no lift on hover — the previous card slid an
   accent bar in, translated upward and grew a drop shadow on every hover, which is three
   decorations doing the job a border colour does on its own.
   --------------------------------------------------------------------------------------- */
:where(body.ly-public) .page-head { max-width: 62ch; margin: 0 0 var(--s-xl); }
:where(body.ly-public) .page-head h1 { font-size: clamp(32px, 4vw, 44px); line-height: 1.08; letter-spacing: -.02em; margin: 0 0 var(--s-sm); }
:where(body.ly-public) .page-head p { margin: 0; font-size: 19px; line-height: 1.42; color: var(--text-muted); }
:where(body.ly-public) .back { display: inline-block; font-size: 14px; margin-bottom: var(--s-md); }
:where(body.ly-public) h2 { font-size: 24px; margin: var(--s-xl) 0 var(--s-sm); }
/* ---- Facet tabs. A pill row, one selected. -------------------------------------------- */
:where(body.ly-public) .tabs { display: flex; gap: var(--s-xs); margin: 0 0 var(--s-md); flex-wrap: wrap; }
:where(body.ly-public) .tab {
    padding: 8px 17px; border-radius: var(--r-pill);
    background: var(--surface); border: 1px solid var(--hairline);
    color: var(--text-2); font-size: 14px; font-weight: 400; line-height: 1.29; cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
                background-color var(--dur-fast) var(--ease);
}
:where(body.ly-public) .tab:hover { border-color: var(--field-border); color: var(--text); background: var(--surface); }
:where(body.ly-public) .tab.active {
    border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 600;
}
:where(body.ly-public) .tab.active:hover { background: var(--accent-focus); border-color: var(--accent-focus); color: var(--on-accent); }
/* ---- Search + filters. In this pattern the search bar IS the primary call to action, so it
        leads the page and is sized like one rather than tucked into a toolbar. -------------- */
:where(body.ly-public) .filterbar {
    display: flex; gap: var(--s-sm); flex-wrap: wrap; align-items: center;
    background: var(--surface-2); border: 1px solid var(--hairline);
    border-radius: var(--r-lg); padding: var(--s-sm); margin-bottom: var(--s-md);
}
:where(body.ly-public) .filterbar input[type=search] { flex: 1; min-width: 220px; }
:where(body.ly-public) .filterbar select,
:where(body.ly-public) .filterbar input[type=number] { width: auto; }
:where(body.ly-public) .filterbar input[type=number] { width: 128px; }
:where(body.ly-public) .filterbar .check { font-size: 14px; white-space: nowrap; }
:where(body.ly-public) .result-meta { color: var(--text-muted); font-size: 14px; margin: 0 0 var(--s-md); font-variant-numeric: tabular-nums; }
/* ---- Cards ---------------------------------------------------------------------------- */
:where(body.ly-public) .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s-md); }
:where(body.ly-public) .card {
    position: relative; display: flex; flex-direction: column;
    background: var(--surface); border: 1px solid var(--hairline);
    border-radius: var(--r-lg); padding: var(--s-lg);
    color: inherit; text-decoration: none; overflow: hidden;
    transition: border-color var(--dur) var(--ease);
}
:where(body.ly-public) .card:hover { border-color: var(--field-border); text-decoration: none; }
/* The keyboard ring belongs to the whole card, not just the invisible link inside it. */
:where(body.ly-public) .card:focus-within { border-color: var(--accent-focus); box-shadow: var(--ring); }
/* Whole-card click target sits UNDER the display content; the interactive foot (the card's CTA —
   Copy on a strategy card, Follow on a channel card) sits ABOVE it. */
:where(body.ly-public) .card .card-stretch { position: absolute; inset: 0; z-index: 0; }
:where(body.ly-public) .card .card-foot { position: relative; z-index: 1; margin-top: auto; padding-top: var(--s-md); display: flex; justify-content: flex-end; }
:where(body.ly-public) .card .card-head { display: flex; gap: var(--s-sm); align-items: center; }
:where(body.ly-public) .card .idbox { min-width: 0; flex: 1; }
:where(body.ly-public) .card .name {
    font-weight: 600; font-size: 17px; letter-spacing: -.01em; line-height: 1.3;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
:where(body.ly-public) .card .kindlabel {
    font-size: 12px; color: var(--text-muted); text-transform: uppercase;
    letter-spacing: .05em; font-weight: 600; margin-top: 4px;
}
/* ---- Trust ring. The score is the artifact on a discovery card, so it gets the ring and the
        tabular figure. A conic-gradient with a HARD stop is a gauge, not a decorative blend:
        one hue against the track, no colour interpolation anywhere. --v = 0..100. ---------- */
:where(body.ly-public) .trust-ring {
    --v: 0; position: relative; isolation: isolate; flex-shrink: 0;
    width: 60px; height: 60px; border-radius: 50%; pointer-events: none;
    background: conic-gradient(var(--accent) calc(var(--v) * 3.6deg), var(--meter-track) 0);
}
:where(body.ly-public) .trust-ring::after { content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--surface); z-index: 0; }
:where(body.ly-public) .trust-ring .rv { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; }
:where(body.ly-public) .trust-ring .rv b { font-size: 19px; font-weight: 600; letter-spacing: -.02em; line-height: 1; font-variant-numeric: tabular-nums; }
:where(body.ly-public) .trust-ring .rv i {
    font-size: 8px; font-style: normal; font-weight: 600; text-transform: uppercase;
    letter-spacing: .06em; color: var(--text-muted); margin-top: 2px;
}
:where(body.ly-public) .trust-ring.na { background: var(--meter-track); }
/* right-side head stat (e.g. channel followers) */
:where(body.ly-public) .head-stat { flex-shrink: 0; text-align: center; padding-left: var(--s-xxs); }
:where(body.ly-public) .head-stat b { display: block; font-size: 24px; font-weight: 600; letter-spacing: -.02em; line-height: 1; color: var(--text); font-variant-numeric: tabular-nums; }
:where(body.ly-public) .head-stat span { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
/* ---- Metric mini-tiles (Performance / Integrity / Pricing …) --------------------------- */
:where(body.ly-public) .card-metrics { display: flex; gap: var(--s-xs); margin-top: var(--s-md); }
:where(body.ly-public) .metric { flex: 1; min-width: 0; background: var(--surface-2); border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 10px 12px; }
:where(body.ly-public) .metric .ml { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:where(body.ly-public) .metric .mv {
    font-size: 19px; font-weight: 600; letter-spacing: -.02em; margin-top: 3px; line-height: 1.15;
    font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
:where(body.ly-public) .metric .mv.txt { font-size: 14px; font-weight: 600; }
:where(body.ly-public) .metric .mbar { height: 3px; border-radius: var(--r-pill); background: var(--meter-track); overflow: hidden; margin-top: 9px; }
:where(body.ly-public) .metric .mbar > i { display: block; height: 100%; border-radius: var(--r-pill); background: var(--accent); }
:where(body.ly-public) .metric .mbar > i.p { background: var(--up); }
:where(body.ly-public) .metric .mbar > i.i { background: var(--info); }
/* ---- Avatar. A flat accent fill, not a two-stop gradient. --on-accent tracks the theme, so
        the initials stay legible on the deep lime by day and the lemon by night. ------------ */
:where(body.ly-public) .avatar {
    flex-shrink: 0; display: flex; align-items: center; justify-content: center;
    font-weight: 600; letter-spacing: -.01em;
    color: var(--on-accent); background: var(--accent);
    width: 48px; height: 48px; border-radius: var(--r-md); font-size: 19px;
}
:where(body.ly-public) .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s-sm); }
:where(body.ly-public) .chip {
    font-size: 12px; padding: 4px 11px; border-radius: var(--r-pill);
    background: var(--surface-2); color: var(--text-2);
    border: 1px solid var(--hairline); font-weight: 400;
}
:where(body.ly-public) .chip.ok { background: var(--ok-bg); color: var(--ok-text); border-color: var(--ok-border); }
/* sparkline band (real score-history trend; see partials/sparkline) */
:where(body.ly-public) .spark-wrap { margin-top: var(--s-md); }
:where(body.ly-public) .spark-label { display: block; font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; margin-bottom: 6px; }
:where(body.ly-public) .spark svg { display: block; width: 100%; height: 42px; }
:where(body.ly-public) .spark-empty {
    height: 42px; display: flex; align-items: center; justify-content: center;
    font-size: 12px; color: var(--text-muted); border: 1px dashed var(--hairline); border-radius: var(--r-sm);
}
/* ---- Signal-channel card variant. Distinguished by the badge and the stat row rather than by
        a second accent colour — this system has exactly one. --------------------------------- */
:where(body.ly-public) .sig-badge {
    flex-shrink: 0; width: 48px; height: 48px; border-radius: var(--r-md);
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-text);
}
:where(body.ly-public) .sig-badge svg { width: 23px; height: 23px; }
:where(body.ly-public) .sig-stats { display: flex; gap: var(--s-lg); margin-top: var(--s-md); }
:where(body.ly-public) .sig-stats .ssl { display: block; font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
:where(body.ly-public) .sig-stats .ssv { font-size: 24px; font-weight: 600; letter-spacing: -.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
:where(body.ly-public) .pagination { margin-top: var(--s-lg); }
/* ---- Public profile / strategy detail --------------------------------------------------- */
:where(body.ly-public) .profile-hero {
    display: flex; gap: var(--s-lg); align-items: flex-start;
    background: var(--surface-2); border: 1px solid var(--hairline);
    border-radius: var(--r-lg); padding: var(--s-xl); margin-top: var(--s-sm); flex-wrap: wrap;
}
:where(body.ly-public) .profile-hero .avatar { width: 68px; height: 68px; border-radius: var(--r-lg); font-size: 27px; }
:where(body.ly-public) .profile-hero .hd { flex: 1; min-width: 200px; }
:where(body.ly-public) .profile-hero h1 { margin: 0; font-size: clamp(28px, 3.4vw, 40px); line-height: 1.1; }
:where(body.ly-public) .profile-hero .ref { font-size: 14px; color: var(--text-muted); }
:where(body.ly-public) .profile-hero .badges { display: flex; gap: var(--s-xs); flex-wrap: wrap; margin-top: var(--s-sm); }
/* The headline score is THE artifact of a passport page — the one place the system's single
   shadow is spent, because this figure is what the whole product is selling. */
:where(body.ly-public) .score-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-sm); margin-top: var(--s-md); }
:where(body.ly-public) .score-tile { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: var(--s-lg); }
:where(body.ly-public) .score-tile.hero { box-shadow: var(--shadow-artifact); border-color: transparent; }
:where(body.ly-public) .score-tile .v { font-size: 40px; font-weight: 600; line-height: 1.07; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
:where(body.ly-public) .score-tile.hero .v { font-size: clamp(44px, 5vw, 56px); }
:where(body.ly-public) .score-tile .l { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; margin-top: 8px; }
:where(body.ly-public) .score-tile .meter { margin-top: var(--s-sm); }
:where(body.ly-public) .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: var(--s-sm); margin-top: var(--s-sm); }
:where(body.ly-public) .tile { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: var(--s-md); }
:where(body.ly-public) .tile .k { font-size: 13px; color: var(--text-muted); }
:where(body.ly-public) .tile .v { font-size: 17px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }
:where(body.ly-public) .copy-cta { display: flex; gap: var(--s-sm); align-items: center; flex-wrap: wrap; margin-top: var(--s-md); }
@media (max-width: 560px) {
:where(body.ly-public) .score-grid { grid-template-columns: 1fr; }
:where(body.ly-public) .profile-hero { padding: var(--s-lg); }
}
/* ================================ LIQUID GLASS ================================
   The tokens live in public/ft-live.css; the panes of THIS layout consume them here,
   because per-layout component classes must not be flattened into the global file.
   Only three declarations are needed — what the material does to the ground behind it,
   its own sheen, and its edge plus weight. The tint and the border already arrive
   through --surface and --hairline, which the stylesheet has redirected. */
/* `.page-head`, `.head-stat` and `.sig-stats` are NOT here — bare text/flex blocks with no
   background and no radius, so a ring on them is a square box. */
:where(body.ly-public) .card,
:where(body.ly-public) .tile,
:where(body.ly-public) .score-tile,
:where(body.ly-public) .metric,
:where(body.ly-public) .profile-hero,
:where(body.ly-public) .filterbar {
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    box-shadow: var(--glass-edge), var(--cast);
}
/* A pane inside a pane: denser tint, and NO second cast — a shadow inside a shadowed
   parent reads as dirt rather than as depth. */
:where(body.ly-public) .card .tile,
:where(body.ly-public) .card .metric,
:where(body.ly-public) .profile-hero .metric {
    background-color: var(--glass-2);
    background-image: radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: var(--glass-edge);
}
/* A pane that is a link responds like a material: it gains weight rather than changing
   colour. This is the only motion the material adds, and it is why a card feels physical. */
:where(body.ly-public) .card {
    transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease),
        border-color var(--dur) var(--ease);
}
:where(body.ly-public) .card:hover { box-shadow: var(--glass-edge), var(--cast-lg); transform: translateY(-2px); }
:where(body.ly-public) .card:active { transform: scale(.99); }
/* The trust ring is a GAUGE, not a pane — its ::after disc must stay opaque or the conic
   sweep behind it shows through and the score becomes unreadable. */
:where(body.ly-public) .trust-ring::after { background: var(--glass-dense); }


/* ---- app shell layout  <- resources/views/layouts/app.blade.php   [scope: ly-app] ---- */

/* ---------------------------------------------------------------------------------------
   AUTHENTICATED APP SHELL.

   A quiet sidebar and a content column. Everything that used to lift, glow or cast a shadow
   on hover now moves a border colour instead — the previous shell had four hover transforms
   and five shadow levels, which made a dashboard of ordinary numbers feel like it was
   constantly reacting to the pointer.

   Shared component classes (.tiles/.tile, .panel, table.list, .passport, .tabs, .field,
   forms) are consumed by /panel/* and /account/* too — the names are unchanged so those
   pages inherit the new language without edits.
   --------------------------------------------------------------------------------------- */
:where(body.ly-app) .app { display: flex; min-height: 100vh; }
/* ============================ SIDEBAR ============================ */
:where(body.ly-app) .sidebar {
    width: 262px; flex-shrink: 0;
    background: var(--canvas-alt); border-right: 1px solid var(--hairline);
    padding: var(--s-sm) var(--s-sm) var(--s-md);
    padding-left: max(var(--s-sm), env(safe-area-inset-left));
    display: flex; flex-direction: column; gap: 1px;
    position: sticky; top: 0; height: 100vh; overflow-y: auto; overscroll-behavior: contain;
}
:where(body.ly-app) .side-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 4px 4px var(--s-sm); margin-bottom: var(--s-xs); border-bottom: 1px solid var(--hairline);
}
:where(body.ly-app) .side-head .brand { font-size: 17px; font-weight: 600; letter-spacing: -.02em; color: var(--text); text-decoration: none; }
:where(body.ly-app) .nav-group-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted);
    font-weight: 600; padding: var(--s-md) var(--s-sm) 6px; display: flex; align-items: center; gap: 7px;
}
:where(body.ly-app) .nav-group-label .ngl-ico { width: 13px; height: 13px; display: inline-flex; }
:where(body.ly-app) .nav-group-label .ngl-ico svg { width: 13px; height: 13px; display: block; }
:where(body.ly-app) .navlink {
    position: relative; display: flex; align-items: center; gap: 11px;
    padding: 9px var(--s-sm); border-radius: var(--r-sm);
    color: var(--text-2); font-size: 15px; font-weight: 400; text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
:where(body.ly-app) .navlink:hover { background: var(--surface-3); text-decoration: none; color: var(--text); }
:where(body.ly-app) .navlink:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-app) .navlink.active { background: var(--surface); color: var(--text); font-weight: 600; }
/* The rail is the position marker; the weight change is the second, non-colour signal. */
:where(body.ly-app) .navlink.active::before {
    content: ""; position: absolute; left: -12px; top: 50%; transform: translateY(-50%);
    width: 3px; height: 20px; border-radius: 0 3px 3px 0; background: var(--accent);
}
:where(body.ly-app) .navlink .nl-ico { width: 18px; height: 18px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); }
:where(body.ly-app) .navlink .nl-ico svg { width: 18px; height: 18px; display: block; }
/* Unread count on a sidebar link. Carries the accent FILL, so it takes --on-accent rather than a text colour:
   the accent inverts between themes (deep lime carrying white by day, lemon carrying near-black by night) and
   a hardcoded foreground here would be unreadable in one of them. `margin-left:auto` rather than a spacer,
   so the link's flex row needs no extra element. */
:where(body.ly-app) .navlink .nl-badge {
    margin-left: auto; flex-shrink: 0;
    min-width: 20px; padding: 1px 6px;
    border-radius: var(--r-pill);
    background: var(--accent); color: var(--on-accent);
    font-size: 12px; font-weight: 600; line-height: 18px; text-align: center;
    font-variant-numeric: tabular-nums;
}
:where(body.ly-app) .navlink.active .nl-ico { color: var(--accent-text); }
:where(body.ly-app) .sidebar .spacer { flex: 1; min-height: var(--s-sm); }
:where(body.ly-app) .side-user {
    display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: var(--r-md);
    background: var(--surface); border: 1px solid var(--hairline); margin-top: 6px;
}
/* A flat accent fill with --on-accent for the initials, so the pair stays legible in both themes
   without the theme-independent gradient this used to carry. */
:where(body.ly-app) .side-user .ava {
    width: 36px; height: 36px; border-radius: var(--r-sm); flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-weight: 600; color: var(--on-accent); font-size: 15px; background: var(--accent);
}
:where(body.ly-app) .side-user .who { min-width: 0; flex: 1; }
:where(body.ly-app) .side-user .who .nm { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:where(body.ly-app) .side-user .who .em { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:where(body.ly-app) .sidebar form { margin-top: 4px; }
:where(body.ly-app) .sidebar form button.ghost {
    width: 100%; text-align: left; justify-content: flex-start; gap: 11px;
    font-size: 15px; padding: 9px var(--s-sm); color: var(--text-muted); border-radius: var(--r-sm);
    /* Log out is NAV CHROME, not an action: it is the last row of a column of plain nav links, so the
       boundary the base .ghost carries would make it the only boxed row in the rail. Its affordance is
       the row itself — same as every link above it. (0,2,2) beats `button.ghost` (0,1,1), so this wins
       without !important, on hover too. */
    border-color: transparent;
}
:where(body.ly-app) .sidebar form button.ghost .nl-ico { width: 18px; height: 18px; display: inline-flex; }
:where(body.ly-app) .sidebar form button.ghost .nl-ico svg { width: 16px; height: 16px; display: block; }
/* Small print at the foot of the rail (currently just "Cookie choices"). Styled off the partial's own
   class rather than a wrapper div: the partial renders NOTHING until a choice exists, and a wrapper
   would still hold its whitespace — so `:empty` would never match and an invisible element would keep
   reserving space on every fresh visit. */
:where(body.ly-app) .sidebar form.ftx-choices { margin-top: var(--s-xs); padding: 0 var(--s-sm); }
:where(body.ly-app) .sidebar form.ftx-choices button.linklike { font-size: 12px; color: var(--text-muted); }
/* ============================ MAIN ============================ */
:where(body.ly-app) .main { flex: 1; min-width: 0; padding: var(--s-lg) var(--s-xl) var(--s-xxl); }
:where(body.ly-app) .main-wrap { max-width: 1200px; margin: 0 auto; }
:where(body.ly-app) h1 { font-size: 32px; line-height: 1.12; margin: 0 0 4px; letter-spacing: -.02em; }
:where(body.ly-app) h2 { font-size: 24px; margin: var(--s-xl) 0 var(--s-sm); color: var(--text); letter-spacing: -.01em; }
/* Passport header (shared: dashboard + /panel strategist/signalist/analyst pages) */
:where(body.ly-app) .passport {
    position: relative; overflow: hidden;
    background: var(--surface-2); border: 1px solid var(--hairline);
    border-radius: var(--r-lg); padding: var(--s-lg); margin-bottom: var(--s-lg);
}
:where(body.ly-app) .passport .ref { font-size: 14px; color: var(--text-muted); }
:where(body.ly-app) .scores { display: flex; gap: var(--s-lg); margin-top: var(--s-md); flex-wrap: wrap; }
:where(body.ly-app) .score { text-align: left; min-width: 96px; }
:where(body.ly-app) .score .v { font-size: 32px; font-weight: 600; line-height: 1.07; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
:where(body.ly-app) .score .l { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; margin-top: 6px; }
:where(body.ly-app) .score .sm { height: 3px; border-radius: var(--r-pill); background: var(--meter-track); overflow: hidden; margin-top: 9px; max-width: 120px; }
:where(body.ly-app) .score .sm > span { display: block; height: 100%; border-radius: var(--r-pill); }
/* Tabs (shared with account/wallet) */
:where(body.ly-app) .tabs { display: flex; gap: var(--s-xs); flex-wrap: wrap; margin-bottom: var(--s-lg); }
:where(body.ly-app) .tab {
    padding: 8px 17px; border-radius: var(--r-pill);
    background: var(--surface); border: 1px solid var(--hairline);
    color: var(--text-2); font-size: 14px; font-weight: 400; cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
:where(body.ly-app) .tab:hover { border-color: var(--field-border); color: var(--text); background: var(--surface); }
:where(body.ly-app) .tab.active { border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 600; }
:where(body.ly-app) .tab.active:hover { background: var(--accent-focus); border-color: var(--accent-focus); color: var(--on-accent); }
:where(body.ly-app) .tab .dot { font-size: 10px; opacity: .75; }
/* Panel tiles (shared across /panel + /account + dashboard panels) */
:where(body.ly-app) .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--s-sm); }
:where(body.ly-app) .tile {
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md);
    padding: var(--s-md); transition: border-color var(--dur) var(--ease);
}
:where(body.ly-app) .tile:hover { border-color: var(--field-border); }
:where(body.ly-app) .tile .k { font-size: 13px; color: var(--text-muted); }
:where(body.ly-app) .tile .v { font-size: 19px; font-weight: 600; margin-top: 5px; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
:where(body.ly-app) .tile.soon { border-style: dashed; }
:where(body.ly-app) .tile.soon:hover { border-color: var(--hairline); }
:where(body.ly-app) .tile.soon .tag { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
/* `display:block` + `overflow-x:auto` at EVERY width, not only on a phone. A table never shrinks below
   its min-content width, and `.main { min-width:0 }` lets the flex column shrink under it — so an
   11-column table (the signalist history, the broker Accounts list) escaped the panel and scrolled the
   whole PAGE sideways on any window between ~900px and ~1250px, which is every laptop and iPad landscape. */
:where(body.ly-app) table.list {
    width: 100%; border-collapse: collapse; font-size: 15px;
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md);
    overflow: hidden; display: block; overflow-x: auto;
    -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
}
/* display:block drops the table's own layout, so the rows have to stretch to the scroll width again. */
:where(body.ly-app) table.list > thead,
:where(body.ly-app) table.list > tbody { display: table; width: 100%; table-layout: auto; }
:where(body.ly-app) table.list th,
:where(body.ly-app) table.list td { text-align: left; padding: 12px var(--s-sm); border-bottom: 1px solid var(--divider-soft); }
:where(body.ly-app) table.list tbody tr:last-child td,
:where(body.ly-app) table.list tr:last-child td { border-bottom: 0; }
:where(body.ly-app) table.list th {
    color: var(--text-muted); font-weight: 600; font-size: 12px;
    text-transform: uppercase; letter-spacing: .05em; background: var(--surface-2);
}
:where(body.ly-app) table.list tbody tr { transition: background-color var(--dur-fast) var(--ease); }
:where(body.ly-app) table.list tbody tr:hover { background: var(--surface-2); }
/* Account area: forms, panels, sub-nav (shared) */
:where(body.ly-app) .subnav { display: flex; gap: var(--s-xs); flex-wrap: wrap; margin: var(--s-xs) 0 var(--s-lg); }
:where(body.ly-app) .subnav a {
    padding: 8px 17px; border-radius: var(--r-pill);
    background: var(--surface); border: 1px solid var(--hairline);
    color: var(--text-2); font-size: 14px; font-weight: 400; text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
:where(body.ly-app) .subnav a:hover { border-color: var(--field-border); color: var(--text); text-decoration: none; }
:where(body.ly-app) .subnav a.active { border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 600; }
:where(body.ly-app) .panel { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: var(--s-lg); margin: var(--s-md) 0; }
:where(body.ly-app) .panel h2 { margin: 0 0 var(--s-xs); font-size: 21px; }
:where(body.ly-app) .field { margin: var(--s-md) 0; max-width: 460px; }
:where(body.ly-app) .field label { display: block; font-size: 14px; font-weight: 600; margin: 0 0 7px; color: var(--text); }
:where(body.ly-app) .field input,
:where(body.ly-app) .field select,
:where(body.ly-app) .field textarea { width: 100%; }
:where(body.ly-app) .form-actions { margin: var(--s-lg) 0 0; display: flex; gap: var(--s-sm); align-items: center; flex-wrap: wrap; }
:where(body.ly-app) .opts { display: inline-flex; gap: var(--s-xs); align-items: center; flex-wrap: wrap; }
:where(body.ly-app) .recovery-codes {
    background: var(--surface-2); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: var(--s-md);
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; font-size: 14px;
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: var(--s-sm) 0;
}
:where(body.ly-app) .toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-md); padding: var(--s-md) 0; border-bottom: 1px solid var(--divider-soft); }
:where(body.ly-app) .toggle-row:last-child { border-bottom: 0; }
:where(body.ly-app) .toggle-row .opts { display: flex; gap: var(--s-md); flex-shrink: 0; }
:where(body.ly-app) .toggle-row .opts label { display: flex; gap: 6px; align-items: center; font-size: 14px; color: var(--text-2); white-space: nowrap; }
/* A QR needs a white quiet zone to scan reliably — this is a functional requirement, not a colour choice. */
:where(body.ly-app) .qr { background: #ffffff; display: inline-block; padding: 10px; border-radius: var(--r-md); }
/* ==================== Dashboard system (ft-) ==================== */
:where(body.ly-app) .ft-page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-md); flex-wrap: wrap; margin-bottom: var(--s-lg); }
/* Reserve top-right space so the fixed notification bell never overlaps the KYC/standing chips
   (the bell is viewport-anchored; only relevant once the sidebar is vertical). */
@media (min-width: 901px) {
:where(body.ly-app) .ft-page-head { padding-right: 46px; }
}
:where(body.ly-app) .ft-eyebrow {
    display: inline-flex; align-items: center; gap: 7px; margin: 0 0 var(--s-xs);
    color: var(--text-muted); font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
}
:where(body.ly-app) .ft-eyebrow .ic { width: 14px; height: 14px; display: inline-flex; }
:where(body.ly-app) .ft-eyebrow .ic svg { width: 14px; height: 14px; display: block; }
:where(body.ly-app) .ft-page-head h1 { font-size: clamp(28px, 3.4vw, 40px); line-height: 1.1; margin: 0 0 5px; letter-spacing: -.02em; }
:where(body.ly-app) .ft-ph-sub { margin: 0; color: var(--text-muted); font-size: 17px; }
:where(body.ly-app) .ft-ph-r { display: flex; gap: var(--s-xs); align-items: center; flex-wrap: wrap; }
/* Match the "View passport" button to the KYC/standing badges beside it: same pill shape AND
   same height, so the three chips line up as one row. */
:where(body.ly-app) .ft-ph-r .badge,
:where(body.ly-app) .ft-ph-r .btn { border-radius: var(--r-pill); height: 32px; display: inline-flex; align-items: center; box-sizing: border-box; }
/* KPI cards. The number is the artifact — it gets the display size and tabular figures, and
   the card around it stays a plain hairline. */
:where(body.ly-app) .ft-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-sm); margin-bottom: var(--s-xs); }
:where(body.ly-app) .ft-kpi {
    position: relative; overflow: hidden;
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg);
    padding: var(--s-md); transition: border-color var(--dur) var(--ease);
}
:where(body.ly-app) .ft-kpi:hover { border-color: var(--field-border); }
:where(body.ly-app) .ft-kpi-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
:where(body.ly-app) .ft-kpi-label { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
:where(body.ly-app) .ft-kpi-ico {
    width: 30px; height: 30px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--r-sm); background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-text);
}
:where(body.ly-app) .ft-kpi-ico svg { width: 16px; height: 16px; display: block; }
:where(body.ly-app) .ft-kpi-val { font-size: 40px; font-weight: 600; line-height: 1.07; letter-spacing: -.02em; margin-top: var(--s-sm); font-variant-numeric: tabular-nums; }
:where(body.ly-app) .ft-kpi-val .u { font-size: 15px; font-weight: 400; color: var(--text-muted); letter-spacing: 0; margin-left: 3px; }
:where(body.ly-app) .ft-meter { height: 3px; border-radius: var(--r-pill); background: var(--meter-track); overflow: hidden; margin-top: var(--s-sm); }
:where(body.ly-app) .ft-meter > span { display: block; height: 100%; border-radius: var(--r-pill); }
:where(body.ly-app) .ft-kpi-hint { font-size: 13px; color: var(--text-muted); margin-top: 10px; }
:where(body.ly-app) .ft-scored-note { font-size: 13px; color: var(--text-muted); margin: var(--s-sm) 2px 0; display: flex; align-items: center; gap: 6px; }
:where(body.ly-app) .ft-scored-note .ic { width: 13px; height: 13px; display: inline-flex; }
:where(body.ly-app) .ft-scored-note .ic svg { width: 13px; height: 13px; display: block; }
/* Role switcher (segmented) */
:where(body.ly-app) .ft-role-switch {
    display: inline-flex; flex-wrap: wrap; gap: 4px; padding: 4px; border-radius: var(--r-md);
    background: var(--surface-2); border: 1px solid var(--hairline); margin: var(--s-lg) 0 var(--s-md);
}
:where(body.ly-app) .ft-role {
    display: inline-flex; align-items: center; gap: 7px; padding: 8px 15px; border-radius: var(--r-xs);
    border: 0; background: none; color: var(--text-2); font-size: 14px; font-weight: 400; cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
:where(body.ly-app) .ft-role:hover { background: var(--surface-3); color: var(--text); transform: none; }
:where(body.ly-app) .ft-role:active { transform: none; }
:where(body.ly-app) .ft-role:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-app) .ft-role.active { background: var(--surface); color: var(--text); font-weight: 600; }
:where(body.ly-app) .ft-role .st {
    font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
    padding: 2px 6px; border-radius: var(--r-xs); background: var(--surface-3); color: var(--text-muted);
}
/* Role lead heading above the card grid */
:where(body.ly-app) .ft-lead { display: flex; align-items: center; gap: 11px; margin: var(--s-xxs) 0 var(--s-md); }
:where(body.ly-app) .ft-lead-ico {
    width: 34px; height: 34px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--r-sm); background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-text);
}
:where(body.ly-app) .ft-lead-ico svg { width: 19px; height: 19px; display: block; }
:where(body.ly-app) .ft-lead h2 { margin: 0; font-size: 21px; letter-spacing: -.01em; }
:where(body.ly-app) .ft-lead .sub { font-size: 14px; color: var(--text-muted); margin-top: 2px; }
/* Card grid + section cards */
:where(body.ly-app) .ft-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-md); align-items: start; }
:where(body.ly-app) .ft-card {
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg);
    overflow: hidden; transition: border-color var(--dur) var(--ease);
}
:where(body.ly-app) .ft-card:hover { border-color: var(--field-border); }
:where(body.ly-app) .ft-card--wide { grid-column: 1 / -1; }
:where(body.ly-app) .ft-card__hd { display: flex; align-items: center; gap: 11px; padding: var(--s-md) var(--s-md) var(--s-sm); }
:where(body.ly-app) .ft-card__ico,
:where(body.ly-app) .ft-tbl__ico {
    width: 32px; height: 32px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--r-sm); background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-text);
}
:where(body.ly-app) .ft-card__ico svg,
:where(body.ly-app) .ft-tbl__ico svg { width: 17px; height: 17px; display: block; }
:where(body.ly-app) .ft-card__title,
:where(body.ly-app) .ft-tbl__title { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -.01em; color: var(--text); }
:where(body.ly-app) .ft-card__act,
:where(body.ly-app) .ft-tbl__act { margin-left: auto; font-size: 14px; white-space: nowrap; }
:where(body.ly-app) .ft-card__bd { padding: 0 var(--s-md) var(--s-md); }
/* TABLE BLOCK — a heading above a table, with NO frame of its own.
   `table.list` is already a surface: it carries the hairline, the radius and the shaded header band. So
   putting one inside `.ft-card` (or `.panel`) drew a second rounded frame around the first, with a gutter
   between them — the "card within a card" this codebase has now hit four times (the `.hwm` chart, guest
   `.card`, `.close__in`, and here). The old fix was `.ft-card--table`, which stripped the TABLE's border and
   let the card supply the frame; the card was still a card, which is what was reported. `.ft-tbl` inverts
   it: the table keeps its own single frame and the block around it contributes only layout — no border,
   no radius, no padding, no glass. TablesAreNotCardsTest fails a rule here that gives it any of those. */
:where(body.ly-app) .ft-tbl { grid-column: 1 / -1; }
:where(body.ly-app) .ft-tbl__hd { display: flex; align-items: center; gap: 11px; margin: 0 0 var(--s-sm); }
/* Sub-stats: reuse .tiles/.tile markup but recede them INSIDE a card (scoped, no bleed elsewhere) */
:where(body.ly-app) .ft-card .tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s-xs); }
:where(body.ly-app) .ft-card .tile { background: var(--surface-2); border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 13px 14px; }
:where(body.ly-app) .ft-card .tile:hover { border-color: var(--hairline); }
:where(body.ly-app) .ft-card .tile .k { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600; color: var(--text-muted); }
:where(body.ly-app) .ft-card .tile .v { font-size: 21px; font-weight: 600; margin-top: 6px; }
:where(body.ly-app) .ft-card .tile.soon { border-style: dashed; background: transparent; }
/* Empty-state note inside a card */
:where(body.ly-app) .ft-empty { display: flex; align-items: center; gap: 10px; color: var(--text-muted); font-size: 15px; margin: 2px 0; }
:where(body.ly-app) .ft-empty .ic {
    width: 30px; height: 30px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--r-sm); background: var(--surface-3); color: var(--text-muted);
}
:where(body.ly-app) .ft-empty .ic svg { width: 16px; height: 16px; display: block; }
/* Live activity list */
:where(body.ly-app) .ft-live-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; }
:where(body.ly-app) .ft-live-list li { display: flex; align-items: center; gap: 10px; font-size: 15px; padding: 10px 0; border-bottom: 1px solid var(--divider-soft); }
:where(body.ly-app) .ft-live-list li:last-child { border-bottom: 0; }
:where(body.ly-app) .ft-live-sym { font-weight: 600; }
:where(body.ly-app) .ft-live-pl { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 1080px) {
:where(body.ly-app) .ft-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
:where(body.ly-app) .ft-grid { grid-template-columns: 1fr; }
:where(body.ly-app) .ft-card--wide { grid-column: auto; }
}
@media (max-width: 900px) {
:where(body.ly-app) .app { flex-direction: column; }
:where(body.ly-app) .sidebar {
        width: auto; height: auto; position: static; flex-direction: row; flex-wrap: wrap; align-items: center;
        gap: 4px; border-right: 0; border-bottom: 1px solid var(--hairline); padding: var(--s-sm) var(--s-md);
    }
/* Reserve right space so the fixed notification bell (top-right) doesn't cover the theme toggle. */
:where(body.ly-app) .side-head { width: 100%; padding: 0 56px var(--s-xs) 4px; margin-bottom: 4px; }
:where(body.ly-app) .nav-group-label { display: none; }
:where(body.ly-app) .sidebar .spacer { display: none; }
:where(body.ly-app) .side-user { display: none; }
:where(body.ly-app) .sidebar form { margin: 0; }
:where(body.ly-app) .sidebar form button.ghost { width: auto; }
:where(body.ly-app) .main { padding: var(--s-md) var(--s-md) var(--s-xl); }
/* The vertical active rail only makes sense in the vertical sidebar. */
:where(body.ly-app) .navlink.active::before { display: none; }
}
@media (max-width: 560px) {
:where(body.ly-app) .ft-kpis { grid-template-columns: 1fr; }
:where(body.ly-app) .ft-role-switch { display: flex; }
}
/* ================================ LIQUID GLASS ================================
   The tokens live in public/ft-live.css; the panes of THIS layout consume them here,
   because per-layout component classes must not be flattened into the global file.
   Only three declarations are needed — what the material does to the ground behind it,
   its own sheen, and its edge plus weight. The tint and the border already arrive
   through --surface and --hairline, which the stylesheet has redirected. */
/* `.score`, `.opts` and `.subnav` are NOT here: each is a bare text block or flex row with no
   background and no radius, so the material's ring drew a square box around them. `.sidebar` is
   handled separately below — it is full-bleed. */
/* Full-bleed, so it takes its RIGHT edge only — the border it already had — rather than a ring
   that would draw a hairline down the viewport edge. Same treatment as the sticky topbar. */
:where(body.ly-app) .sidebar {
    -webkit-backdrop-filter: var(--glass-blur-lg);
    backdrop-filter: var(--glass-blur-lg);
    background-image: var(--glass-sheen);
    box-shadow: inset -1px 0 0 var(--rim-ring);
    border-right: 0;
}
/* `table.list` is NOT here either, and neither is `.ft-tbl`. The ring would merely duplicate the
   border the table already carries, and `.ft-tbl` is a heading plus layout with no background of
   its own — the ring would draw a box around a bare flex row (the guest-`.card` trap). A table's
   own hairline is the whole treatment it needs; it is a surface, not a pane. */
:where(body.ly-app) .panel,
:where(body.ly-app) .ft-card,
:where(body.ly-app) .ft-kpi,
:where(body.ly-app) .side-user,
:where(body.ly-app) .recovery-codes,
:where(body.ly-app) .ft-role-switch {
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    box-shadow: var(--glass-edge), var(--cast);
}
/* A pane inside a pane: denser tint, and NO second cast — a shadow inside a shadowed
   parent reads as dirt rather than as depth. */
:where(body.ly-app) .ft-card .tile,
:where(body.ly-app) .panel .tile,
:where(body.ly-app) .tiles .tile {
    background-color: var(--glass-2);
    background-image: radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: var(--glass-edge);
}


/* ---- guest/auth layout  <- resources/views/layouts/guest.blade.php   [scope: ly-guest] ---- */

body:where(.ly-guest) { min-height: 100vh; background: var(--canvas-alt); }
:where(body.ly-guest) .auth-page {
    min-height: 100vh; display: flex; align-items: center; justify-content: center;
    padding: clamp(24px, 5vh, 56px) clamp(16px, 4vw, 56px);
    padding-bottom: max(clamp(24px, 5vh, 56px), env(safe-area-inset-bottom));
}
:where(body.ly-guest) .auth-panel {
    width: 100%; max-width: var(--maxw);
    display: grid; grid-template-columns: 1.05fr minmax(380px, 460px);
    background: var(--surface); border: 1px solid var(--hairline);
    border-radius: var(--r-lg); overflow: hidden;
}
/* ---- Left: brand + trust story. Dark in BOTH themes — the tile itself is the divider. ---- */
:where(body.ly-guest) .auth-aside {
    position: relative; overflow: hidden;
    color: var(--on-dark); background: var(--band-dark);
    padding: clamp(30px, 2.6vw, 48px);
    display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start;
    gap: var(--s-md);
    /* The lemon, not the deep lime: on a #272729 tile the dark fill colour falls below contrast.
       This is the documented light/dark split of the ONE accent, not a second accent. */
    --il-accent: #C7EA46;
    --il-ink: #ffffff;
    --il-surface: #3a3a3c;
    --il-line: #48484a;
}
:where(body.ly-guest) .auth-aside > * { position: relative; z-index: 1; width: 100%; max-width: 480px; }
:where(body.ly-guest) .auth-aside .brand-lockup { color: var(--on-dark); text-decoration: none; }
:where(body.ly-guest) .auth-aside .brand-lockup span { color: var(--on-dark); }
:where(body.ly-guest) .auth-aside .brand-mark { background: rgba(255, 255, 255, .14); color: var(--on-dark); }
:where(body.ly-guest) .aside-top { display: flex; flex-direction: column; gap: var(--s-md); }
:where(body.ly-guest) .aside-eyebrow {
    display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600;
    letter-spacing: .06em; text-transform: uppercase; color: #a1a1a6;
}
:where(body.ly-guest) .auth-aside .headline {
    font-size: clamp(28px, 2.6vw, 40px); line-height: 1.1; letter-spacing: -.02em;
    margin: 0; max-width: 15ch; font-weight: 600; text-wrap: balance; color: var(--on-dark);
}
:where(body.ly-guest) .auth-aside .headline em { font-style: normal; color: #C7EA46; }
:where(body.ly-guest) .auth-aside .lede { color: var(--on-dark-2); font-size: 17px; line-height: 1.47; margin: 0; }
:where(body.ly-guest) .aside-art { margin: 0; }
:where(body.ly-guest) .aside-bottom { display: flex; flex-direction: column; gap: var(--s-md); }
:where(body.ly-guest) .auth-aside ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
:where(body.ly-guest) .auth-aside li { display: flex; align-items: flex-start; gap: 11px; font-size: 15px; color: var(--on-dark-2); line-height: 1.45; }
:where(body.ly-guest) .auth-aside li svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 2px; color: #C7EA46; }
:where(body.ly-guest) .auth-aside .fine {
    font-size: 12px; color: #a1a1a6; line-height: 1.45;
    padding-top: var(--s-md); border-top: 1px solid rgba(255, 255, 255, .12);
}
/* ---- Right: the form. The panel IS the container, so there is no inner card box. ---- */
:where(body.ly-guest) .auth-main {
    position: relative; display: flex; flex-direction: column; justify-content: center;
    padding: clamp(28px, 3vw, 48px); background: var(--surface);
}
:where(body.ly-guest) .auth-toggle { position: absolute; top: 10px; right: 10px; }
:where(body.ly-guest) .auth-stack { width: 100%; max-width: 392px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s-md); }
:where(body.ly-guest) .card { width: 100%; background: transparent; border: 0; border-radius: 0; padding: 0; }
:where(body.ly-guest) .card .card-brand {
    display: inline-flex; align-items: center; gap: 10px; margin: 0 0 6px;
    font-size: 19px; font-weight: 600; letter-spacing: -.02em; color: var(--text); text-decoration: none;
}
:where(body.ly-guest) .card .card-brand span.w { color: var(--text); }
:where(body.ly-guest) .card .card-brand span.w span { color: var(--text); }
:where(body.ly-guest) .sub { margin: 0 0 var(--s-lg); font-size: 15px; color: var(--text-muted); }
:where(body.ly-guest) h1 { font-size: 28px; line-height: 1.14; margin: 0 0 6px; letter-spacing: -.015em; }
:where(body.ly-guest) label { display: block; font-size: 14px; font-weight: 600; margin: var(--s-md) 0 7px; color: var(--text); }
:where(body.ly-guest) input[type=text],
:where(body.ly-guest) input[type=email],
:where(body.ly-guest) input[type=password] { width: 100%; }
:where(body.ly-guest) .row { display: flex; align-items: center; gap: 8px; margin-top: var(--s-md); font-size: 15px; color: var(--text-2); }
:where(body.ly-guest) .card button[type=submit] { width: 100%; margin-top: var(--s-lg); }
:where(body.ly-guest) .links { margin-top: var(--s-md); font-size: 15px; display: flex; justify-content: center; gap: var(--s-sm); flex-wrap: wrap; }
:where(body.ly-guest) .value-note {
    margin-top: var(--s-lg); padding-top: var(--s-md); border-top: 1px solid var(--hairline);
    font-size: 13px; color: var(--text-muted); display: flex; align-items: center; gap: 8px; line-height: 1.45;
}
:where(body.ly-guest) .value-note svg { width: 15px; height: 15px; color: var(--up); flex-shrink: 0; }
/* "What happens next" onboarding steps (register) */
:where(body.ly-guest) .next-steps { margin-top: var(--s-lg); padding-top: var(--s-md); border-top: 1px solid var(--hairline); display: grid; gap: var(--s-sm); }
:where(body.ly-guest) .next-steps .ns-head { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
:where(body.ly-guest) .next-steps .ns { display: flex; align-items: flex-start; gap: 11px; font-size: 14px; color: var(--text-2); line-height: 1.45; }
:where(body.ly-guest) .next-steps .ns .num {
    flex-shrink: 0; width: 22px; height: 22px; border-radius: var(--r-pill);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 12px; font-weight: 600; color: var(--on-accent); background: var(--accent);
}
:where(body.ly-guest) .next-steps .ns strong { color: var(--text); font-weight: 600; }
/* Trust stat strip below the form */
:where(body.ly-guest) .trust-strip {
    display: grid; grid-template-columns: repeat(3, 1fr);
    background: var(--surface-2); border: 1px solid var(--hairline);
    border-radius: var(--r-md); overflow: hidden;
}
:where(body.ly-guest) .trust-strip .stat { padding: var(--s-sm) 12px; text-align: center; position: relative; }
:where(body.ly-guest) .trust-strip .stat + .stat::before { content: ""; position: absolute; left: 0; top: 18%; height: 64%; width: 1px; background: var(--hairline); }
:where(body.ly-guest) .trust-strip .stat .n {
    font-size: 24px; font-weight: 600; letter-spacing: -.02em; color: var(--text); line-height: 1;
    font-variant-numeric: tabular-nums;
}
:where(body.ly-guest) .trust-strip .stat .l { display: block; font-size: 11px; color: var(--text-muted); margin-top: 6px; line-height: 1.35; }
@media (max-width: 900px) {
:where(body.ly-guest) .auth-panel { grid-template-columns: 1fr; max-width: 460px; }
:where(body.ly-guest) .auth-aside { display: none; }
:where(body.ly-guest) .auth-page { padding: var(--s-lg) var(--s-md); }
}
/* ================================ LIQUID GLASS ================================
   The tokens live in public/ft-live.css; the panes of THIS layout consume them here,
   because per-layout component classes must not be flattened into the global file.
   Only three declarations are needed — what the material does to the ground behind it,
   its own sheen, and its edge plus weight. The tint and the border already arrive
   through --surface and --hairline, which the stylesheet has redirected. */
/* ONLY the two real panes. `.card` in THIS layout is a deliberately transparent, borderless,
   `border-radius: 0`, `padding: 0` form wrapper — the same class name means something entirely
   different here than it does in layouts.public, which is exactly the hazard of listing
   components by name. The material's edge includes an `inset 0 0 0 1px` ring, and an inset ring
   follows the element's radius — so on a radius-0 wrapper it drew a hard SQUARE box around the
   whole sign-in form that had never existed. `.next-steps` and `.value-note` are `border-top`
   blocks with no background and no radius, and got the same square box.
   Check that a thing IS a pane (its own background AND its own radius) before giving it one. */
:where(body.ly-guest) .auth-panel,
:where(body.ly-guest) .trust-strip {
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    box-shadow: var(--glass-edge), var(--cast);
}


/* ---- onboarding layout  <- resources/views/layouts/onboarding.blade.php   [scope: ly-onboarding] ---- */

/* ---------------------------------------------------------------------------------------
   ONBOARDING WIZARD. A single centred card on a parchment field — the one place in the
   product where the visitor should see exactly one thing at a time.

   The card carries no shadow (the system's one shadow belongs to the artifact, and a form is
   not one), the role cards no longer lift on hover, and the step pills state progress with a
   fill rather than a tint. --------------------------------------------------------------- */
body:where(.ly-onboarding) { padding: var(--s-xl) var(--s-md) calc(var(--s-xl) + env(safe-area-inset-bottom)); background: var(--canvas-alt); }
:where(body.ly-onboarding) .ob-toggle { position: fixed; top: var(--s-sm); right: var(--s-sm); z-index: 10; }
:where(body.ly-onboarding) .shell { width: 100%; max-width: 620px; margin: 0 auto; }
:where(body.ly-onboarding) .shell.wide { max-width: 960px; }
:where(body.ly-onboarding) .shell .brand {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    font-size: 19px; font-weight: 600; margin: 0 0 var(--s-lg); letter-spacing: -.02em;
    color: var(--text); text-decoration: none;
}
:where(body.ly-onboarding) .card { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg); padding: var(--s-xl); }
:where(body.ly-onboarding) h1 { font-size: 28px; line-height: 1.14; margin: 0 0 6px; letter-spacing: -.015em; }
:where(body.ly-onboarding) .sub { margin: 0 0 var(--s-lg); font-size: 15px; color: var(--text-muted); line-height: 1.47; }
:where(body.ly-onboarding) .field { margin: var(--s-md) 0; max-width: none; }
:where(body.ly-onboarding) label { display: block; font-size: 14px; font-weight: 600; margin: 0 0 7px; color: var(--text); }
/* Required-field marker — driven by the handler's actual rules() so it never lies. */
:where(body.ly-onboarding) .req { color: var(--down); margin-left: 3px; font-weight: 600; }
:where(body.ly-onboarding) input[type=text],
:where(body.ly-onboarding) input[type=email],
:where(body.ly-onboarding) input[type=password],
:where(body.ly-onboarding) input[type=number],
:where(body.ly-onboarding) input[type=date],
:where(body.ly-onboarding) select,
:where(body.ly-onboarding) textarea { width: 100%; }
:where(body.ly-onboarding) .check { align-items: flex-start; margin: var(--s-md) 0; }
:where(body.ly-onboarding) .check input { width: auto; margin-top: 2px; }
:where(body.ly-onboarding) .actions { display: flex; justify-content: space-between; gap: var(--s-sm); margin-top: var(--s-lg); }
:where(body.ly-onboarding) .actions button[type=submit] { flex: 1; }
:where(body.ly-onboarding) .links { margin-top: var(--s-md); font-size: 14px; display: flex; justify-content: space-between; align-items: center; gap: var(--s-sm); }
/* ---- Step pills. The current step is filled, done steps are outlined and clickable, future
        steps are dashed — three states you can tell apart without relying on colour. ------- */
:where(body.ly-onboarding) .steps { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--s-lg); }
:where(body.ly-onboarding) .step {
    display: inline-block; padding: 5px 12px; border-radius: var(--r-pill);
    background: var(--surface-2); border: 1px solid var(--hairline); color: var(--text-muted);
    font-family: inherit; font-weight: 600; font-size: 12px; line-height: 1.5;
    cursor: default;
}
:where(body.ly-onboarding) .step.current { border-color: var(--accent); color: var(--on-accent); background: var(--accent); }
:where(body.ly-onboarding) button.step.done {
    cursor: pointer; color: var(--text-2);
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
:where(body.ly-onboarding) button.step.done:hover { border-color: var(--accent); color: var(--accent-text); background: var(--surface-2); transform: none; }
:where(body.ly-onboarding) button.step:active { transform: none; }
:where(body.ly-onboarding) button.step:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ly-onboarding) .step.future { border-style: dashed; background: transparent; }
/* ---- Repeater: one card per row; subfields flow so they never truncate ------------------ */
:where(body.ly-onboarding) .repeater-row { background: var(--surface-2); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: var(--s-sm); margin-bottom: 10px; }
:where(body.ly-onboarding) .repeater-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 11px 12px; }
:where(body.ly-onboarding) .repeater-grid .sub { min-width: 0; margin: 0; }
:where(body.ly-onboarding) .repeater-grid .sub input,
:where(body.ly-onboarding) .repeater-grid .sub select { width: 100%; }
:where(body.ly-onboarding) .repeater-grid .sub-label { display: block; font-size: 12px; color: var(--text-2); font-weight: 600; margin: 0 0 5px; }
/* A file subfield spans the whole row (set inline) + a more compact drop zone, so it reads as
   part of the row rather than a panel dwarfing the short name/role/ownership fields beside it. */
:where(body.ly-onboarding) .repeater-grid .sub--file { margin-top: 2px; }
:where(body.ly-onboarding) .repeater-grid .sub--file .filedrop-zone { min-height: 96px; padding: var(--s-md); gap: 9px; }
:where(body.ly-onboarding) .repeater-grid .sub--file .fd-title { font-size: 14px; }
:where(body.ly-onboarding) .repeater-grid .sub--file .fd-hint { font-size: 12px; }
/* Endpoint split into IP/host (wide) + port (narrow), side by side with a ":" between. */
:where(body.ly-onboarding) .host-port-inputs { display: flex; align-items: center; gap: 8px; }
:where(body.ly-onboarding) .host-port-inputs .hp-host { flex: 1 1 auto; min-width: 0; }
:where(body.ly-onboarding) .host-port-inputs .hp-port { flex: 0 0 96px; width: 96px; text-align: center; }
:where(body.ly-onboarding) .host-port-inputs .hp-colon { color: var(--text-2); font-weight: 600; }
:where(body.ly-onboarding) .repeater-foot { display: flex; justify-content: flex-end; margin-top: 11px; }
/* ---- Role chooser ----------------------------------------------------------------------- */
:where(body.ly-onboarding) .rolegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s-sm); margin-top: var(--s-lg); }
:where(body.ly-onboarding) .rolecard {
    background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-lg);
    padding: var(--s-lg); display: flex; flex-direction: column; align-items: flex-start;
    transition: border-color var(--dur) var(--ease);
}
:where(body.ly-onboarding) .rolecard:hover { border-color: var(--field-border); }
:where(body.ly-onboarding) .rolecard:focus-within { border-color: var(--accent-focus); box-shadow: var(--ring); }
:where(body.ly-onboarding) .rolecard .ricon {
    display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px;
    border-radius: var(--r-md); background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent-text); margin-bottom: var(--s-sm);
}
:where(body.ly-onboarding) .rolecard .ricon svg { width: 22px; height: 22px; }
:where(body.ly-onboarding) .rolename { font-size: 19px; font-weight: 600; letter-spacing: -.01em; }
:where(body.ly-onboarding) .roletag { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; margin-top: 5px; }
:where(body.ly-onboarding) .roledesc { font-size: 15px; color: var(--text-muted); line-height: 1.47; margin: var(--s-sm) 0 0; }
:where(body.ly-onboarding) .roleact { margin-top: auto; padding-top: var(--s-md); }
@media (max-width: 520px) {
:where(body.ly-onboarding) .rolegrid { grid-template-columns: 1fr; }
:where(body.ly-onboarding) .card { padding: var(--s-lg); }
}
/* ================================ LIQUID GLASS ================================
   The tokens live in public/ft-live.css; the panes of THIS layout consume them here,
   because per-layout component classes must not be flattened into the global file.
   Only three declarations are needed — what the material does to the ground behind it,
   its own sheen, and its edge plus weight. The tint and the border already arrive
   through --surface and --hairline, which the stylesheet has redirected. */
:where(body.ly-onboarding) .card,
:where(body.ly-onboarding) .rolecard,
:where(body.ly-onboarding) .step,
:where(body.ly-onboarding) .repeater-row {
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    background-image:
        radial-gradient(var(--spec-size) circle at var(--spec-x) var(--spec-y),
        var(--spec-color) 0%, transparent 62%),
        var(--glass-sheen);
    box-shadow: var(--glass-edge), var(--cast);
}


/* ---- home page  <- resources/views/welcome.blade.php   [scope: pg-home v2] ----
   The LEDGER home page that used to live here (119 rules: .org*, .iss*, .cred*, .plot*, .formula,
   .weights, .wires, .reg*) was replaced by the design-v2 port, which is appended at the end of this
   file under its own banner. Nothing was salvaged: every one of those rules named markup the new
   page does not render.

   The .hwm* chart block further up is NOT part of that and was deliberately left alone -- it reads
   as home-page CSS, sits in ly-marketing, and /pricing and the strategist panel both use it. ---- */

/* ---- AI assistant widget  <- resources/views/livewire/chatbot/chat-widget.blade.php   [scope: ft] ---- */

/* ── Scoped to the widget. Tokens only; the palette lives in public/ft-live.css. ──────────────────
   ft-live.css styles every <button> as an accent pill (gradient, shadow, hover lift) via a bare
   element selector. Every button here therefore carries its own class, which outranks it on
   specificity — no !important needed anywhere. */
:where(body.ft) [x-cloak] { display: none !important; }
:where(body.ft) .ftx-chat { position: fixed; right: 20px; bottom: 20px; z-index: 140; font-size: 13px; }
/* ── launcher ───────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-chat .ftx-launch {
    display: flex; align-items: center; justify-content: center;
    width: 52px; height: 52px; padding: 0; margin-left: auto;
    border: 1px solid transparent; border-radius: var(--radius-pill);
    background: var(--accent); color: var(--on-accent);
    box-shadow: var(--shadow-primary); cursor: pointer;
    transition: transform .16s ease, box-shadow .16s ease, background-color .16s ease;
}
:where(body.ft) .ftx-chat .ftx-launch svg { width: 22px; height: 22px; }
:where(body.ft) .ftx-chat .ftx-launch:hover { transform: translateY(-2px); }
:where(body.ft) .ftx-chat .ftx-launch:focus-visible { outline: none; box-shadow: var(--ring); }
/* Open: step back so the panel is the thing you look at. */
:where(body.ft) .ftx-chat .ftx-launch.is-open {
    background: var(--surface-3); color: var(--text-2);
    border-color: var(--border-strong); box-shadow: var(--shadow-sm);
}
:where(body.ft) .ftx-chat .ftx-launch.is-open:hover { color: var(--text); }
/* ── panel ──────────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-panel {
    position: absolute; right: 0; bottom: 64px;
    width: min(396px, calc(100vw - 32px));
    height: min(600px, calc(100vh - 120px));
    display: flex; flex-direction: column; overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}
/* Panel open/close. Origin bottom-right so it grows out of the launcher rather than the page centre. */
:where(body.ft) .ftx-anim { transition: opacity .16s ease, transform .16s ease; transform-origin: bottom right; }
:where(body.ft) .ftx-anim-from { opacity: 0; transform: translateY(8px) scale(.97); }
:where(body.ft) .ftx-anim-to { opacity: 1; transform: none; }
/* ── header ─────────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-head {
    flex: none; display: flex; align-items: center; gap: 10px;
    padding: 12px 12px 12px 14px;
    background: var(--surface-2); border-bottom: 1px solid var(--border);
}
:where(body.ft) .ftx-avatar,
:where(body.ft) .ftx-mini {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-pill); background: var(--surface-3);
    border: 1px solid var(--border); color: var(--primary);
}
/* ft-live.css pins `.brand-glyph { color: var(--on-primary) }` so the mark reads correctly on its
   usual lime tile. Here it sits on a dark surface instead, so both rules below re-assert the colour —
   `.ftx-avatar svg` (0,1,1) outranks `.brand-glyph` (0,1,0). Without this the glyph is near-black on
   near-black and simply disappears. The width/height attributes on the partial are overridden the
   same way. */
:where(body.ft) .ftx-avatar { width: 32px; height: 32px; }
:where(body.ft) .ftx-avatar svg { width: 17px; height: 17px; color: var(--primary); }
:where(body.ft) .ftx-mini svg { color: var(--primary); }
:where(body.ft) .ftx-head-text { flex: 1; min-width: 0; }
:where(body.ft) .ftx-head-text strong {
    display: block; font-size: 13.5px; font-weight: 600; color: var(--text); line-height: 1.3;
}
:where(body.ft) .ftx-head-text small {
    display: block; font-size: 11px; color: var(--text-muted); line-height: 1.35; margin-top: 1px;
}
:where(body.ft) .ftx-chat .ftx-icon {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; padding: 0;
    border: 1px solid transparent; border-radius: var(--r-sm);
    background: none; color: var(--text-muted); box-shadow: none; cursor: pointer;
    transition: color .14s ease, background-color .14s ease;
}
:where(body.ft) .ftx-chat .ftx-icon svg { width: 16px; height: 16px; }
:where(body.ft) .ftx-chat .ftx-icon:hover { color: var(--text); background: var(--surface-3); transform: none; }
:where(body.ft) .ftx-chat .ftx-icon:focus-visible { outline: none; box-shadow: var(--ring); }
/* ── log ────────────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-log {
    flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    padding: 14px; display: flex; flex-direction: column; gap: 12px;
    scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
:where(body.ft) .ftx-log::-webkit-scrollbar { width: 8px; }
:where(body.ft) .ftx-log::-webkit-scrollbar-track { background: transparent; }
:where(body.ft) .ftx-log::-webkit-scrollbar-thumb {
    background: var(--border-strong); border-radius: var(--radius-pill);
    border: 2px solid var(--surface);
}
:where(body.ft) .ftx-row { display: flex; align-items: flex-end; gap: 8px; max-width: 100%; }
:where(body.ft) .ftx-row.me { justify-content: flex-end; }
:where(body.ft) .ftx-mini { width: 24px; height: 24px; margin-bottom: 2px; }
:where(body.ft) .ftx-mini svg { width: 13px; height: 13px; }
:where(body.ft) .ftx-stack { min-width: 0; display: flex; flex-direction: column; }
:where(body.ft) .ftx-bubble {
    max-width: 296px; padding: 9px 12px;
    font-size: 13px; line-height: 1.55; word-break: break-word;
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border);
    /* A squared corner on the speaker's side reads as a tail without drawing one. */
    border-radius: 14px 14px 14px 4px;
}
:where(body.ft) .ftx-row.me .ftx-bubble {
    background: var(--primary); color: var(--on-primary); border-color: transparent;
    border-radius: 14px 14px 4px 14px;
}
:where(body.ft) .ftx-bubble.is-failed { border-color: var(--err-border); }
/* pre-wrap ONLY here — see the note at the top of this file. */
:where(body.ft) .ftx-t { white-space: pre-wrap; }
:where(body.ft) .ftx-md :where(p, ul, ol) { margin: 0 0 8px; }
:where(body.ft) .ftx-md :where(p, ul, ol):last-child { margin-bottom: 0; }
:where(body.ft) .ftx-md :where(ul, ol) { padding-left: 17px; }
:where(body.ft) .ftx-md li { margin: 2px 0; }
:where(body.ft) .ftx-md li::marker { color: var(--text-muted); }
:where(body.ft) .ftx-md strong { font-weight: 600; }
:where(body.ft) .ftx-md a { color: var(--primary); text-underline-offset: 2px; }
:where(body.ft) .ftx-md code {
    background: var(--surface-3); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: 1px 5px; font-size: 12px;
}
:where(body.ft) .ftx-row.me .ftx-md a,
:where(body.ft) .ftx-row.me .ftx-md code { color: inherit; }
/* ── suggestion chips ───────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-chips { display: flex; flex-wrap: wrap; gap: 6px; padding-left: 32px; }
:where(body.ft) .ftx-chat .ftx-chip {
    padding: 6px 11px; font-size: 12px; font-weight: 500; line-height: 1.3;
    color: var(--text-2); background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-pill);
    box-shadow: none; cursor: pointer; text-align: left;
    transition: color .14s ease, border-color .14s ease;
}
:where(body.ft) .ftx-chat .ftx-chip:hover {
    color: var(--primary); border-color: var(--primary); transform: none; filter: none;
}
:where(body.ft) .ftx-chat .ftx-chip:focus-visible { outline: none; box-shadow: var(--ring); }
/* ── feedback ───────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-rate { display: flex; align-items: center; gap: 2px; margin: 4px 0 0 2px; }
:where(body.ft) .ftx-chat .ftx-thumb {
    display: flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; padding: 0;
    border: 1px solid transparent; border-radius: var(--r-sm);
    background: none; color: var(--text-faint); box-shadow: none; cursor: pointer;
    transition: color .14s ease, background-color .14s ease;
}
:where(body.ft) .ftx-chat .ftx-thumb svg { width: 14px; height: 14px; }
:where(body.ft) .ftx-chat .ftx-thumb:hover { color: var(--primary); background: var(--surface-2); transform: none; }
:where(body.ft) .ftx-chat .ftx-thumb:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ft) .ftx-rated { font-size: 11px; color: var(--text-muted); }
/* ── typing indicator ───────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-dots { display: inline-flex; gap: 3px; align-items: center; vertical-align: middle; }
:where(body.ft) .ftx-t:not(:empty) + .ftx-dots { display: none; }
:where(body.ft) .ftx-dots i {
    width: 5px; height: 5px; border-radius: 50%; background: var(--text-muted);
    animation: ftxdot 1.1s infinite ease-in-out;
}
:where(body.ft) .ftx-dots i:nth-child(2) { animation-delay: .15s; }
:where(body.ft) .ftx-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes ftxdot { 0%, 80%, 100% { opacity: .25; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-2px); } }
/* ── notices ────────────────────────────────────────────────────────────────────────────────── */
:where(body.ft) .ftx-note {
    font-size: 12px; line-height: 1.5; color: var(--err-text); background: var(--err-bg);
    border: 1px solid var(--err-border); border-radius: var(--r-md); padding: 8px 10px; margin: 0;
}
:where(body.ft) .ftx-note-form { flex: none; margin: 0 12px 10px; }
/* ── composer ───────────────────────────────────────────────────────────────────────────────── */
/* -- what the question is about: server-resolved chips above the composer ------------------------- */
/* Tokens only. Every button here carries its own class so it outranks the global <button> accent-pill
   rule on specificity, which is the same reason .ftx-send and .ftx-chip do. */
:where(body.ft) .ftx-scope {
    flex: none; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    padding: 8px 12px 0;
}
:where(body.ft) .ftx-scope-chip {
    display: inline-flex; align-items: center; gap: 6px;
    max-width: 100%; padding: 4px 4px 4px 8px;
    font-size: 12px; line-height: 1.2; color: var(--text);
    background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--r-sm);
}
/* The KIND, so "Strategy" and "Channel" are never guessed at from the name alone. */
:where(body.ft) .ftx-scope-kind {
    flex: none; font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted);
}
:where(body.ft) .ftx-scope-name {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600;
}
:where(body.ft) .ftx-chat .ftx-scope-x {
    flex: none; display: flex; align-items: center; justify-content: center;
    /* 22px is below the 24px target floor, so the hit area is extended with padding rather than by
       growing the visible chip — WCAG 2.5.8 counts the target, not the ink. */
    width: 22px; height: 22px; margin: -4px -2px -4px 0; padding: 4px;
    color: var(--text-muted); background: none; border: 0; border-radius: var(--r-xs);
    box-shadow: none; cursor: pointer;
}
:where(body.ft) .ftx-chat .ftx-scope-x svg { width: 12px; height: 12px; }
:where(body.ft) .ftx-chat .ftx-scope-x:hover { color: var(--text); background: var(--surface-2); transform: none; }
:where(body.ft) .ftx-chat .ftx-scope-x:focus-visible { outline: none; box-shadow: var(--ring); }
/* One sentence for every kind of miss — see MentionResolver::refusal(). */
:where(body.ft) .ftx-scope-note { flex: 1 1 100%; font-size: 12px; color: var(--text-muted); }

/* -- the subject picker ---------------------------------------------------------------------------- */
:where(body.ft) .ftx-pick {
    flex: none; display: flex; flex-direction: column; gap: 6px;
    padding: 8px 12px 0;
}
:where(body.ft) .ftx-pick-input {
    width: 100%; padding: 8px 10px;
    font: inherit; font-size: 13px; color: var(--text); background: var(--surface);
    border: 1px solid var(--field-border); border-radius: var(--r-md);
}
:where(body.ft) .ftx-pick-input::placeholder { color: var(--text-faint); }
:where(body.ft) .ftx-pick-input:focus-visible { outline: none; border-color: var(--accent-focus); box-shadow: var(--ring); }
:where(body.ft) .ftx-pick-empty { margin: 0; font-size: 12px; color: var(--text-muted); }
:where(body.ft) .ftx-pick-list {
    /* Bounded so a long list cannot push the composer off a phone screen. */
    max-height: 168px; overflow-y: auto; margin: 0; padding: 0; list-style: none;
    border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface);
    scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
:where(body.ft) .ftx-chat .ftx-pick-item {
    display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
    width: 100%; padding: 8px 10px; text-align: left;
    color: var(--text); background: none; border: 0; border-bottom: 1px solid var(--hairline);
    border-radius: 0; box-shadow: none; cursor: pointer;
}
:where(body.ft) .ftx-chat .ftx-pick-item:hover { background: var(--surface-2); transform: none; }
:where(body.ft) .ftx-chat .ftx-pick-item:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ft) .ftx-pick-list li:last-child .ftx-pick-item { border-bottom: 0; }
:where(body.ft) .ftx-pick-name { font-size: 13px; font-weight: 600; }
:where(body.ft) .ftx-pick-meta { font-size: 11px; color: var(--text-muted); }

/* The composer's own mention control. Sized to the send button beside it so the field row stays level. */
:where(body.ft) .ftx-chat .ftx-at {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0;
    color: var(--text-muted); background: var(--surface);
    border: 1px solid var(--field-border); border-radius: var(--r-md);
    box-shadow: none; cursor: pointer;
}
:where(body.ft) .ftx-chat .ftx-at svg { width: 18px; height: 18px; }
:where(body.ft) .ftx-chat .ftx-at:hover { color: var(--text); background: var(--surface-2); transform: none; }
:where(body.ft) .ftx-chat .ftx-at:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ft) .ftx-chat .ftx-at.is-on { color: var(--accent-text); border-color: var(--accent-focus); }

/* -- whose words those were ------------------------------------------------------------------------ */
/* Rendered by the server whenever an answer used an owner's own description. Muted, but never dimmer
   than --text-muted: it is the one line that tells a reader the description was not the platform's. */
:where(body.ft) .ftx-attrib {
    margin: 4px 0 0; padding-left: 2px;
    font-size: 11px; line-height: 1.45; color: var(--text-muted);
}

/* Two buttons in a discovery card's footer now (Ask, then Copy), so they need to be told apart. */
:where(body.ly-public) .card .card-foot { gap: var(--s-sm); }

:where(body.ft) .ftx-form {
    flex: none; display: flex; gap: 8px; align-items: flex-end;
    padding: 10px 12px; background: var(--surface-2); border-top: 1px solid var(--border);
}
:where(body.ft) .ftx-form textarea {
    flex: 1; min-width: 0; resize: none; overflow-y: auto;
    height: 38px; max-height: 116px; padding: 9px 11px;
    font: inherit; font-size: 13px; line-height: 1.35;
    color: var(--text); background: var(--surface);
    border: 1px solid var(--field-border); border-radius: var(--r-md);
    scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
:where(body.ft) .ftx-form textarea::placeholder { color: var(--text-faint); }
:where(body.ft) .ftx-form textarea:focus-visible { outline: none; border-color: var(--accent-focus); box-shadow: var(--ring); }
:where(body.ft) .ftx-form textarea:disabled { opacity: .6; }
:where(body.ft) .ftx-chat .ftx-send {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0;
    /* Matches the composer textarea beside it — a control in a field row takes the field radius. */
    border: 1px solid transparent; border-radius: var(--r-md);
    background: var(--primary); color: var(--on-primary);
    box-shadow: none; cursor: pointer;
    transition: filter .14s ease, opacity .14s ease;
}
:where(body.ft) .ftx-send-icon { width: 18px; height: 18px; }
:where(body.ft) .ftx-chat .ftx-send:hover { transform: none; filter: brightness(1.06); }
:where(body.ft) .ftx-chat .ftx-send:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.ft) .ftx-chat .ftx-send:disabled { opacity: .45; cursor: not-allowed; filter: none; }
:where(body.ft) .ftx-sr {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}
/* ── responsive: a full-height sheet on a phone, where a 396px card floating over the page is
      neither readable nor dismissable with a thumb. ─────────────────────────────────────────── */
@media (max-width: 520px) {
:where(body.ft) .ftx-chat { right: 14px; bottom: 14px; }
:where(body.ft) .ftx-panel {
        position: fixed; inset: 8px 8px 76px;
        width: auto; height: auto; border-radius: var(--radius-lg);
    }
:where(body.ft) .ftx-bubble { max-width: none; }
:where(body.ft) .ftx-row.me .ftx-stack { max-width: 84%; }
}
@media (prefers-reduced-motion: reduce) {
:where(body.ft) .ftx-chat .ftx-launch,
:where(body.ft) .ftx-chat .ftx-icon,
:where(body.ft) .ftx-chat .ftx-chip,
:where(body.ft) .ftx-chat .ftx-thumb,
:where(body.ft) .ftx-chat .ftx-send,
:where(body.ft) .ftx-anim { transition: none; }
:where(body.ft) .ftx-dots i { animation: none; opacity: .6; }
}


/* ---- notification bell  <- resources/views/livewire/notification-bell.blade.php   [scope: ft] ---- */

/* Reuse the theme .icon-btn (proven 40px icon button); .ftx-bell-btn only anchors the badge + sizes the
   glyph. !important defeats the global `button { padding / gradient }` rule that was inflating it. */
:where(body.ft) .ftx-bell-btn { position:relative; width:40px !important; height:40px !important; padding:0 !important; }
:where(body.ft) .ftx-bell-btn > svg { width:21px; height:21px; display:block; }
:where(body.ft) .ftx-bell-btn:hover { transform:none; filter:none; }
:where(body.ft) .ftx-bell-badge { position:absolute; top:-6px; right:-6px; min-width:17px; height:17px; padding:0 4px; border-radius:999px;
    background:var(--primary); color:var(--on-primary); font-size:10.5px; font-weight:800; display:flex; align-items:center;
    justify-content:center; line-height:1; box-shadow:0 0 0 2px var(--surface); pointer-events:none; }
:where(body.ft) .ftx-bell-panel { position:absolute; top:48px; right:0; width:340px; max-width:calc(100vw - 32px);
    background:var(--surface); border:1px solid var(--border); border-radius:14px; box-shadow:var(--shadow-lg);
    overflow:hidden; }
:where(body.ft) .ftx-bell-head { display:flex; align-items:center; justify-content:space-between; padding:13px 15px;
    border-bottom:1px solid var(--border); }
:where(body.ft) .ftx-bell-head .t { font-weight:700; font-size:14px; }
/* These are <button>s, so they inherit the global button rule's gradient fill + --shadow-primary glow +
   hover transform/filter. Reset every leaked property so a row/action is flat chrome, not a raised pill. */
:where(body.ft) .ftx-bell-head button { background:none; border:0; border-radius:0; color:var(--primary); font-size:12px;
    font-weight:600; cursor:pointer; padding:0; box-shadow:none; transform:none; filter:none; transition:color .12s ease; }
:where(body.ft) .ftx-bell-head button:hover { background:none; box-shadow:none; transform:none; filter:none;
    color:var(--primary-hover); text-decoration:underline; }
:where(body.ft) .ftx-bell-list { max-height:min(60vh,420px); overflow-y:auto; }
:where(body.ft) .ftx-bell-item { position:relative; display:block; width:100%; text-align:left; padding:12px 15px; border:0;
    border-bottom:1px solid var(--border); border-radius:0; background:none; cursor:pointer; color:var(--text-2);
    font:inherit; box-shadow:none; transform:none; filter:none; transition:background-color .12s ease; }
:where(body.ft) .ftx-bell-item:last-child { border-bottom:0; }
:where(body.ft) .ftx-bell-item:hover { background:var(--surface-3); box-shadow:none; transform:none; filter:none; }
:where(body.ft) .ftx-bell-item:focus-visible { outline:none; box-shadow:inset 0 0 0 2px color-mix(in srgb, var(--primary) 55%, transparent); }
:where(body.ft) .ftx-bell-item.unread { background:color-mix(in srgb, var(--primary) 7%, transparent); }
:where(body.ft) .ftx-bell-item.unread:hover { background:color-mix(in srgb, var(--primary) 12%, transparent); }
/* unread dot — absolutely placed so read/unread rows keep identical text metrics (no reflow) */
:where(body.ft) .ftx-bell-item.unread::after { content:""; position:absolute; top:16px; right:14px; width:7px; height:7px;
    border-radius:50%; background:var(--primary); }
:where(body.ft) .ftx-bell-item .m { font-size:13px; line-height:1.4; color:var(--text); padding-right:14px; }
:where(body.ft) .ftx-bell-item .a { font-size:11px; color:var(--text-muted); margin-top:3px; }
:where(body.ft) .ftx-bell-empty { padding:22px 14px; text-align:center; color:var(--text-muted); font-size:13px; }
:where(body.ft) .ftx-bell-notify { padding:12px 15px; border-top:1px solid var(--border); display:flex; flex-direction:column;
    align-items:flex-start; gap:8px; }
:where(body.ft) .ftx-bell-notify p { margin:0; font-size:12.5px; line-height:1.45; color:var(--text-muted); }
:where(body.ft) .ftx-bell-foot { padding:10px 14px; border-top:1px solid var(--border); display:flex; justify-content:space-between; font-size:12px; }
:where(body.ft) .ftx-bell-foot a { color:var(--text-muted); text-decoration:none; font-weight:600; }
:where(body.ft) .ftx-bell-foot a:hover { color:var(--primary); }
:where(body.ft) .ftx-toast { position:fixed; bottom:90px; right:22px; z-index:130; max-width:340px; background:var(--surface);
    border:1px solid var(--hairline); border-left:3px solid var(--accent); border-radius:var(--r-md);
    padding:12px 14px; font-size:14px; line-height:1.45; color:var(--text); text-align:left;
    font-family:inherit; font-weight:400; letter-spacing:-.224px; cursor:pointer; display:block; }
:where(body.ft) .ftx-toast:hover { background:var(--surface-2); color:var(--text); }
:where(body.ft) .ftx-toast:active { transform:none; }
:where(body.ft) .ftx-toast:focus-visible { outline:none; box-shadow:var(--ring); }
@media (max-width:900px) {
:where(body.ft) .ftx-bell-wrap { top:12px; right:14px; }
}


/* ---- contact page  <- resources/views/livewire/marketing/contact.blade.php   [scope: ft] ---- */

/* Two columns. The form spans BOTH rows so the left column can carry a second entry underneath
   its intro instead of stopping ~390px short of the form's bottom edge (measured before this
   block existed). Placement is explicit rather than auto-flowed because the aside comes after
   the form in source order — see the comment on .contact-aside above.

   THE TWO COLUMNS END ON ONE BASELINE, and the message box is what makes that true. The panel
   has a hard border, so a left column overshooting its bottom edge — 93px, measured — reads as
   the layout running out of room rather than as a choice. Rather than cut the left column or
   leave dead space inside the panel, the panel STRETCHES to the row and the textarea absorbs
   whatever slack there is: the recovered height lands in the one field a contact form wants
   bigger. It self-balances in both directions, because a form taller than the left column still
   grows the rows as it did before. */
:where(body.ft) .contact-grid { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); gap:32px;
                align-items:start; margin-top:14px; }
:where(body.ft) .contact-grid .panel { margin:0; }
:where(body.ft) .contact-intro { grid-column:1; grid-row:1; }
:where(body.ft) .contact-form { grid-column:2; grid-row:1 / span 2; align-self:stretch; display:flex; }
:where(body.ft) .contact-aside { grid-column:1; grid-row:2; min-width:0; }
:where(body.ft) .contact-form > form { display:flex; flex-direction:column; flex:1; min-width:0; }
/* The message field is the one that grows; `min-height` keeps it usable when there is no slack
   (a phone, or a left column shorter than the form). */
:where(body.ft) .contact-form .field-grow { display:flex; flex-direction:column; flex:1; }
:where(body.ft) .contact-form .field-grow textarea { flex:1; min-height:150px; }
:where(body.ft) .contact-form .form-actions { margin-top:auto; }
:where(body.ft) .contact-intro h1 { margin:6px 0 10px; }
:where(body.ft) .contact-intro .contact-lede { font-size:15px; line-height:1.6; max-width:46ch; }
:where(body.ft) .contact-points { list-style:none; padding:0; margin:22px 0 0; display:flex; flex-direction:column; gap:14px; }
:where(body.ft) .contact-points li { position:relative; padding-left:24px; color:var(--text-2); font-size:14px; line-height:1.55; }
:where(body.ft) .contact-points li::before { content:""; position:absolute; left:2px; top:8px; width:8px; height:8px;
                             border-radius:50%; background:var(--primary); }
/* the form sits in the wider column — let fields fill it instead of the shared 460px cap */
:where(body.ft) .contact-form .field { max-width:none; }
/* ---- Left column, lower entry -------------------------------------------------------------
   Quiet on purpose: the form is the point of this page, so none of this competes with it. No
   panel, no shadow (the one shadow in the system is spent elsewhere), hairlines only. The h2
   size is restated because the shared 24px h2 is layout-scoped, and a layout's rules can
   follow a visitor across a wire:navigate — the same hazard that indented the risk note. */
:where(body.ft) .contact-block + .contact-block { margin-top:26px; }
:where(body.ft) .contact-aside h2 { font-size:15px; font-weight:600; letter-spacing:-.01em; line-height:1.35;
                    margin:0 0 6px; color:var(--text); }
:where(body.ft) .contact-aside .contact-block-lede { margin:0; font-size:13px; line-height:1.5; color:var(--text-muted); }
/* The state track. A dot per state joined by a hairline — the site's ledger idiom, drawn with
   borders rather than an asset. The first dot is FILLED and the rest are rings because that is
   literally true of a message you have not sent yet: it starts at New.
   The ring is a BORDER and the connector a border-left, not a box-shadow and a background:
   forced-colors mode drops box-shadows and repaints backgrounds, which would have erased the
   whole figure while leaving its labels behind. */
:where(body.ft) .contact-flow { list-style:none; margin:14px 0 0; padding:0; }
:where(body.ft) .contact-flow li { position:relative; padding:0 0 14px 24px; }
:where(body.ft) .contact-flow li:last-child { padding-bottom:0; }
:where(body.ft) .contact-flow li::before { content:""; position:absolute; left:0; top:4px;
                           width:9px; height:9px; box-sizing:border-box;
                           border-radius:50%; background:var(--canvas);
                           border:1.5px solid var(--field-border); }
:where(body.ft) .contact-flow li.is-now::before { background:var(--accent); border-color:var(--accent); }
:where(body.ft) .contact-flow li:not(:last-child)::after { content:""; position:absolute; left:4px; top:17px;
                                          bottom:2px; border-left:1px solid var(--hairline); }
:where(body.ft) .contact-flow .cf-state { display:block; font-size:11.5px; font-weight:600; letter-spacing:.05em;
                          text-transform:uppercase; color:var(--text-2); }
:where(body.ft) .contact-flow .cf-say { display:block; margin-top:2px; font-size:13px; line-height:1.5;
                        color:var(--text-muted); }
/* Handling facts, set as hairline-separated rows so they read as the small print they are and
   rhyme with the risk caveat at the foot of the page. */
:where(body.ft) .contact-facts { list-style:none; margin:12px 0 0; padding:0; }
:where(body.ft) .contact-facts li { padding:10px 0; border-top:1px solid var(--hairline);
                    font-size:13px; line-height:1.5; color:var(--text-muted); }
:where(body.ft) .contact-facts li:last-child { padding-bottom:0; }
@media (max-width:820px) {
/* One column: auto-flow puts intro, then the form, then this context — so the explicit
       placement above has to be released or it would fight the single-column template. */
:where(body.ft) .contact-grid { grid-template-columns:1fr; gap:24px; }
:where(body.ft) .contact-intro,
:where(body.ft) .contact-form,
:where(body.ft) .contact-aside { grid-column:auto; grid-row:auto; }
/* Nothing to balance against in one column: the message box goes back to its own size
       rather than stretching to whatever the row happens to be. */
:where(body.ft) .contact-form .field-grow { flex:0 1 auto; }
}


/* ---- feedback page  <- resources/views/livewire/marketing/feedback.blade.php   [scope: ft] ---- */

:where(body.ft) .fb-grid { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); gap:32px;
           align-items:start; margin-top:14px; }
:where(body.ft) .fb-grid .panel { margin:0; }
:where(body.ft) .fb-intro h1 { margin:6px 0 10px; }
:where(body.ft) .fb-intro .fb-lede { font-size:15px; line-height:1.6; max-width:46ch; }
:where(body.ft) .fb-points { list-style:none; padding:0; margin:22px 0 0; display:flex; flex-direction:column; gap:14px; }
:where(body.ft) .fb-points li { position:relative; padding-left:24px; color:var(--text-2); font-size:14px; line-height:1.55; }
:where(body.ft) .fb-points li::before { content:""; position:absolute; left:2px; top:8px; width:8px; height:8px;
                        border-radius:50%; background:var(--primary); }
/* the form sits in the wider column — let fields fill it instead of the shared 460px cap */
:where(body.ft) .fb-form .field { max-width:none; }
:where(body.ft) .fb-opt { font-weight:500; color:var(--text-faint); font-size:11.5px; text-transform:uppercase;
          letter-spacing:.06em; margin-left:6px; }
:where(body.ft) .fb-signed-in { margin:14px 0 0; }
/* ---- star rating -------------------------------------------------------------------------------
   Build-less and JS-free: real radios (so a keyboard and a screen reader get the native control)
   made invisible, with their labels doing the drawing. DOM order is 1..5 so arrow keys move the way
   the stars read. --- */
:where(body.ft) .fb-rate-field { border:0; padding:0; margin:4px 0 18px; min-width:0; }
:where(body.ft) .fb-rate-field legend { padding:0; font-size:13px; color:var(--text-2); font-weight:600; }
:where(body.ft) .fb-rate { position:relative; display:inline-flex; gap:4px; margin:8px 0 2px; }
:where(body.ft) .fb-rate input { position:absolute; width:1px; height:1px; opacity:0; margin:0; pointer-events:none; }
:where(body.ft) .fb-rate label { display:inline-flex; padding:3px; border-radius:var(--r-sm); cursor:pointer;
                 color:var(--meter-track); transition:color .12s ease, transform .12s ease; }
:where(body.ft) .fb-rate label svg { width:28px; height:28px; }
:where(body.ft) .fb-rate label:active { transform:scale(.92); }
/* Fill up to the chosen (or hovered) star. Three layers, in rising specificity, so they compose:
   (1) `.on` — server-rendered from the chosen rating; the only layer needing no modern selector, so
       a browser without :has() still shows the right fill (one Livewire round trip behind);
   (2) :has() — fills everything the instant a star is checked or hovered, before Livewire answers;
   (3) the `~ input + label` dim rules — HIGHER specificity than (1), which is what makes LOWERING a
       rating repaint immediately instead of waiting for the stale `.on` classes to be replaced.
   The checked-dim is suppressed while a star is hovered, so a hover preview works in BOTH directions:
   without that, hovering star 5 with 3 chosen left 4 and 5 grey — including the one under the cursor,
   which reads as "this star is not clickable". */
:where(body.ft) .fb-rate label.on { color:var(--primary); }
:where(body.ft) .fb-rate:has(input:checked) label,
:where(body.ft) .fb-rate:has(label:hover) label { color:var(--primary); }
:where(body.ft) .fb-rate:not(:has(label:hover)) input:checked ~ input + label { color:var(--meter-track); }
:where(body.ft) .fb-rate label:hover ~ input + label { color:var(--meter-track); }
:where(body.ft) .fb-rate input:focus-visible + label { box-shadow:var(--ring); }
:where(body.ft) .fb-readout { margin:2px 0 0; font-size:12.5px; color:var(--text-muted); min-height:18px; }
:where(body.ft) .fb-clear { font-size:12.5px; margin-left:8px; }
:where(body.ft) .fb-sr { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
         clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }
@media (max-width:820px) {
:where(body.ft) .fb-grid { grid-template-columns:1fr; gap:24px; }
}


/* ---- partner broker panel  <- resources/views/livewire/panel/partner_broker.blade.php   [scope: ft] ---- */

:where(body.ft) .bk-hd { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px; flex-wrap:wrap; }
:where(body.ft) .bk-sec { font-size:11.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--text-muted);
          font-weight:700; margin:22px 0 10px; }
:where(body.ft) .bk-sec:first-of-type { margin-top:4px; }
:where(body.ft) .bk-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:14px; }
:where(body.ft) .bk-srv { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg); padding:18px 19px; }
:where(body.ft) .bk-srv h3 { margin:0; font-size:15px; }
:where(body.ft) .bk-srv .sub { font-size:12px; color:var(--text-muted); margin-top:2px; word-break:break-all; }
:where(body.ft) .bk-kv { display:flex; gap:20px; flex-wrap:wrap; margin-top:15px; }
:where(body.ft) .bk-kv .k { font-size:10.5px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted); }
:where(body.ft) .bk-kv .v { font-size:16px; font-weight:700; margin-top:3px; font-variant-numeric:tabular-nums; }
:where(body.ft) .bk-note { font-size:12px; color:var(--text-muted); margin-top:12px; }
/* 30-day traffic strip. ONE series -> one hue, no legend (the heading names it). Thin bars with 4px
   rounded data-ends anchored to the baseline, a 2px surface gap, recessive baseline. Exact per-day
   numbers live in the hover title + the tiles above, never printed on every bar. */
:where(body.ft) .bk-spark { display:flex; align-items:flex-end; gap:2px; height:88px; margin-top:8px;
            border-bottom:1px solid var(--border); }
:where(body.ft) .bk-spark > span { flex:1 1 0; min-width:2px; min-height:2px; background:var(--primary);
                   border-radius:4px 4px 0 0; opacity:.88; transition:opacity .12s; }
:where(body.ft) .bk-spark > span:hover { opacity:1; }
:where(body.ft) .bk-spark > span.zero { background:var(--meter-track); }
:where(body.ft) .bk-axis { display:flex; justify-content:space-between; font-size:11px; color:var(--text-faint); margin-top:7px; }
:where(body.ft) .bk-bar { height:6px; border-radius:999px; background:var(--meter-track); overflow:hidden; width:130px; }
:where(body.ft) .bk-bar > span { display:block; height:100%; background:var(--primary); border-radius:999px; }
:where(body.ft) .bk-filters { display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end; margin-bottom:14px; }
:where(body.ft) .bk-filters .field { margin:0; min-width:220px; }
:where(body.ft) .bk-chips { display:flex; gap:6px; flex-wrap:wrap; margin-top:6px; }
:where(body.ft) .bk-num { font-variant-numeric:tabular-nums; }
/* Centred table cells. The selector MUST out-specify `table.list th, table.list td { text-align:left }`
   in the app layout: that is (0,1,2), so a bare `.bk-c` at (0,1,0) loses however late it is declared —
   source order only breaks ties at EQUAL specificity. Same trap as the top-bar button whose label went
   invisible because `.topbar nav a` outranked `.btn`. */
:where(body.ft) table.list th.bk-c,
:where(body.ft) table.list td.bk-c { text-align:center; }
/* The badges are inline-flex so they centre as one group and wrap together on a narrow column, instead of
   each sitting on its own centred line. */
:where(body.ft) .bk-badges { display:inline-flex; gap:6px; flex-wrap:wrap; justify-content:center; }
:where(body.ft) .bk-rowacts { display:inline-flex; gap:6px; flex-wrap:wrap; justify-content:center; }
/* An endpoint and a server name are unbroken strings with no spaces; without this they widen the table
   past the viewport rather than wrapping. */
:where(body.ft) .bk-wrap { overflow-wrap:anywhere; }
/* The inline edit / confirm row. Recessed so it reads as belonging to the row above rather than as
   another server. `td` needs the padding because the shared rule sets its own. */
:where(body.ft) table.list tr.bk-subrow > td { background:var(--surface-2); padding:16px 17px; }
:where(body.ft) .bk-editgrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:0 16px; }
:where(body.ft) .bk-editgrid .field { margin-bottom:12px; }


/* ---- trader passport page  <- resources/views/livewire/passport/show.blade.php   [scope: ft] ---- */

/* Centered credential column */
:where(body.ft) .ftx-pp { max-width:820px; margin:0 auto; }
:where(body.ft) .ftx-pp .ft-page-head { display:block; text-align:center; margin-bottom:24px; }
:where(body.ft) .ftx-pp .ft-eyebrow { justify-content:center; }
/* THE ONE SHADOW, and this is the figure it is reserved for. The rule above the token's definition names
   "a passport credential" explicitly; this wrapper was asking for --shadow-lg, which the legacy token bridge
   collapses to `none` — so the real credential was the only one of the three named figures rendering flat,
   while the marketing page's SAMPLE of it got the treatment. The radius must stay equal to the rx on the
   card's own outer <rect> or the wrapper clips a corner the rect does not round. */
:where(body.ft) .ftx-pp-wrap { max-width:760px; margin:0 auto; border-radius:26px; box-shadow:var(--shadow-artifact);
    transition:transform .2s ease; }
:where(body.ft) .ftx-pp-wrap:hover { transform:translateY(-2px); }
:where(body.ft) #ftx-passport-card { display:block; width:100%; height:auto; border-radius:26px; }
/* The card is a FIXED-ASPECT figure that cannot reflow — it is one SVG with absolute coordinates, because it
   doubles as the export source. So on a narrow screen `width:100%` scales the whole thing down, type included,
   and there was no media query for it anywhere: measured at a 390px viewport the card rendered 342px wide, the
   small labels at 3.48 CSS px and the QR at 1.90px per module, which is below any scanning floor. It scrolls
   inside its own container instead, at a floor that keeps the QR usable (600px -> 3.33px per module, labels
   ~6.1px). Wide content scrolling in its own overflow-x container is this system's rule; the page body still
   never scrolls sideways. Pinch-zoom is available too — the layout sets no maximum-scale.
   A true phone rendition of the card is separate work: it would mean a second, stacked layout, which would
   either duplicate the data or stop the SVG being the single export source.

   SCREEN only, and that is load-bearing: a bare `(max-width:700px)` also matches PRINT, where the page is the
   85.6mm credit card — about 324px — so the min-width below would beat the print rule's `width` (min-width
   always wins) and print the card at 600px. Measured: it rendered 600x377.8 instead of 324x204. */
@media screen and (max-width:700px) {
:where(body.ft) .ftx-pp-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; overscroll-behavior-x:contain; }
:where(body.ft) #ftx-passport-card { min-width:600px; }
}
:where(body.ft) .ftx-pp-actions { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin:22px auto 0; max-width:760px; }
:where(body.ft) .ftx-pp .ft-scored-note { display:block; text-align:center; }
:where(body.ft) .ftx-pp .ft-scored-note .ic { vertical-align:-2px; margin-right:5px; }
:where(body.ft) .ftx-pp-toast { position:fixed; bottom:22px; left:50%; transform:translateX(-50%); z-index:130;
    background:var(--surface); border:1px solid var(--primary); border-radius:11px; padding:11px 16px;
    box-shadow:var(--shadow); font-size:13px; font-weight:600; color:var(--text); }
/* Print → the card only, at ISO ID-1 credit-card size (85.6 × 53.98 mm). A clone is appended to <body>
   (see ftxPassport.print) so no display:none ancestor hides it; the on-screen card is never moved. */
:where(body.ft) .ftx-print-holder { display:none; }
@media print {
@page { size:85.6mm 53.98mm; margin:0; }
body:where(.ft) > *:not(.ftx-print-holder) { display:none !important; }
/* The card's viewBox is 1.5882 and the page is 1.5858, so `meet` letterboxes it by a hair. The bars must be
   the CARD's own ground, not some other near-black, or the seam is visible on the printed edge — this was
   #141619, a grey that appears nowhere else in the system. It now READS that ground from the same token the
   card paints itself with, rather than repeating the literal: the card follows the theme, so a hardcoded
   #1D1D1F here would letterbox a light card with black bars the moment the theme changed. */
:where(body.ft) .ftx-print-holder { display:flex !important; align-items:center; justify-content:center;
        position:absolute; inset:0; margin:0; padding:0; background:var(--cred-bg, #1D1D1F); }
:where(body.ft) .ftx-print-holder svg { width:85.6mm; height:53.98mm; }
}
/* "How your scores work" educational section (below the card). Token-only → theme-aware. */
:where(body.ft) .ftx-explain { max-width:760px; margin:34px auto 0; }
:where(body.ft) .ftx-explain > h2 { margin:0 0 6px; font-size:19px; font-weight:800; letter-spacing:-.02em; text-align:center; }
:where(body.ft) .ftx-explain__lead { margin:0 auto 22px; max-width:64ch; text-align:center; font-size:13.5px; line-height:1.6; color:var(--text-muted); }
:where(body.ft) .ftx-explain__grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:14px; }
:where(body.ft) .ftx-explain__card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg);
    padding:17px 18px; box-shadow:var(--shadow-sm); }
:where(body.ft) .ftx-explain__hd { display:flex; align-items:center; gap:9px; margin-bottom:9px; }
:where(body.ft) .ftx-explain__hd h3 { margin:0; font-size:14.5px; font-weight:700; letter-spacing:-.01em; }
:where(body.ft) .ftx-explain__dot { width:11px; height:11px; border-radius:50%; flex-shrink:0; }
:where(body.ft) .ftx-explain__card.is-trust .ftx-explain__dot { background:var(--primary); }
:where(body.ft) .ftx-explain__card.is-perf  .ftx-explain__dot { background:var(--up); }
:where(body.ft) .ftx-explain__card.is-integ .ftx-explain__dot { background:var(--info); }
:where(body.ft) .ftx-explain__card p { margin:0 0 10px; font-size:12.5px; line-height:1.55; color:var(--text-muted); }
:where(body.ft) .ftx-explain__card p:last-child { margin-bottom:0; }
:where(body.ft) .ftx-explain__tip { color:var(--text-2); }
:where(body.ft) .ftx-explain__tip strong { color:var(--text); font-weight:700; }
:where(body.ft) .ftx-explain__why { margin-top:16px; background:var(--surface-3); border:1px solid var(--border);
    border-radius:var(--radius-lg); padding:17px 20px; }
:where(body.ft) .ftx-explain__why h3 { margin:0 0 6px; font-size:14.5px; font-weight:700; letter-spacing:-.01em; }
:where(body.ft) .ftx-explain__why p { margin:0; font-size:13px; line-height:1.6; color:var(--text-muted); }
@media (max-width:600px) {
:where(body.ft) .ftx-explain { margin-top:26px; }
}


/* ---- account verification  <- resources/views/livewire/account/verification.blade.php   [scope: ft] ---- */

:where(body.ft) .vf-state { display:flex; align-items:flex-start; gap:13px; padding:14px 15px; margin:0 0 18px;
    border:1px solid var(--border); border-radius:var(--radius-lg); background:var(--surface-3); }
:where(body.ft) .vf-state__ico { width:38px; height:38px; flex-shrink:0; display:inline-flex; align-items:center;
    justify-content:center; border-radius:var(--r-md); border:1px solid var(--border); color:var(--text-muted); }
:where(body.ft) .vf-state__ico svg { width:19px; height:19px; display:block; }
:where(body.ft) .vf-state__t { margin:0; font-size:14px; font-weight:700; letter-spacing:-.01em; color:var(--text); }
:where(body.ft) .vf-state__d { margin:4px 0 0; font-size:12.5px; line-height:1.5; color:var(--text-muted); }
:where(body.ft) .vf-state.is-ok { background:var(--ok-bg);   border-color:var(--ok-border); }
:where(body.ft) .vf-state.is-ok   .vf-state__ico { background:transparent; border-color:var(--ok-border);   color:var(--up); }
:where(body.ft) .vf-state.is-warn { background:var(--warn-bg); border-color:var(--warn-border); }
:where(body.ft) .vf-state.is-warn .vf-state__ico { background:transparent; border-color:var(--warn-border); color:var(--warn); }
:where(body.ft) .vf-state.is-down { background:var(--err-bg);  border-color:var(--err-border); }
:where(body.ft) .vf-state.is-down .vf-state__ico { background:transparent; border-color:var(--err-border);  color:var(--down); }
:where(body.ft) .vf-sec { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
    color:var(--text-muted); margin:20px 0 9px; }
:where(body.ft) .vf-meta { display:grid; grid-template-columns:repeat(auto-fit, minmax(160px, 1fr)); gap:10px; }
:where(body.ft) .vf-meta__i { padding:10px 12px; background:var(--bg); border:1px solid var(--border); border-radius:var(--r-md); }
:where(body.ft) .vf-meta__k { font-size:11px; font-weight:600; color:var(--text-muted); }
:where(body.ft) .vf-meta__v { font-size:13px; font-weight:600; color:var(--text); margin-top:2px; word-break:break-word; }
:where(body.ft) .vf-docs { display:flex; flex-direction:column; gap:8px; }
:where(body.ft) .vf-doc { display:flex; align-items:center; gap:12px; padding:11px 13px; background:var(--bg);
    border:1px solid var(--border); border-radius:var(--r-md); }
:where(body.ft) .vf-doc__ico { width:32px; height:32px; flex-shrink:0; display:inline-flex; align-items:center;
    justify-content:center; border-radius:var(--r-sm); border:1px solid var(--border); color:var(--text-muted); }
:where(body.ft) .vf-doc__ico svg { width:16px; height:16px; display:block; }
:where(body.ft) .vf-doc__t { font-size:13px; font-weight:600; color:var(--text); }
:where(body.ft) .vf-doc__f { font-size:11.5px; color:var(--text-muted); margin-top:2px; word-break:break-all; }
:where(body.ft) .vf-doc.is-expired { border-color:var(--err-border); background:var(--err-bg); }
:where(body.ft) .vf-doc.is-expired .vf-doc__ico { border-color:var(--err-border); color:var(--down); }
:where(body.ft) .vf-doc.is-soon { border-color:var(--warn-border); background:var(--warn-bg); }
:where(body.ft) .vf-doc.is-soon .vf-doc__ico { border-color:var(--warn-border); color:var(--warn); }
:where(body.ft) .vf-doc__exp { margin-left:auto; flex-shrink:0; font-size:11px; font-weight:700; padding:4px 9px;
    border-radius:var(--radius-pill); border:1px solid var(--border); background:var(--surface-3);
    color:var(--text-2); white-space:nowrap; }
:where(body.ft) .vf-doc.is-expired .vf-doc__exp { border-color:var(--err-border); color:var(--err-text); }
:where(body.ft) .vf-doc.is-soon .vf-doc__exp { border-color:var(--warn-border); color:var(--warn-text); }
@media (max-width:560px) {
:where(body.ft) .vf-doc { flex-wrap:wrap; }
:where(body.ft) .vf-doc__exp { margin-left:46px; }
}


/* ---- account nav  <- resources/views/livewire/account/_nav.blade.php   [scope: ft] ---- */

/* ---- tab bar ---- */
:where(body.ft) .acct-tabs { display:flex; gap:4px; align-items:stretch; margin:2px 0 26px; border-bottom:1px solid var(--border);
    overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none; }
:where(body.ft) .acct-tabs::-webkit-scrollbar { display:none; }
:where(body.ft) .acct-tab { display:inline-flex; align-items:center; gap:8px; white-space:nowrap; flex-shrink:0;
    padding:11px 14px; margin-bottom:-1px; border-bottom:2px solid transparent;
    font-size:13.5px; font-weight:600; letter-spacing:-.005em; color:var(--text-muted); text-decoration:none;
    transition:color .14s ease, border-color .14s ease; }
:where(body.ft) .acct-tab svg { width:16px; height:16px; flex-shrink:0; opacity:.8; transition:opacity .14s ease; }
:where(body.ft) .acct-tab:hover { color:var(--text); text-decoration:none; }
:where(body.ft) .acct-tab:hover svg { opacity:1; }
:where(body.ft) .acct-tab.active { color:var(--text); border-bottom-color:var(--primary); }
:where(body.ft) .acct-tab.active svg { color:var(--primary); opacity:1; }
:where(body.ft) .acct-tab:focus-visible { outline:none; color:var(--text); border-radius:var(--r-xs) var(--r-xs) 0 0; box-shadow:var(--ring); }
/* ---- page header ---- */
:where(body.ft) .acct-page > h1 { display:flex; align-items:center; font-size:22px; font-weight:800; letter-spacing:-.022em; margin:0 0 6px; }
:where(body.ft) .acct-page > p.muted { margin:0 0 4px; font-size:13.5px; line-height:1.55; }
/* ---- content column: keep the forms a focused width so a card isn't a wide box wrapping a narrow form
   (the tab bar stays full-width; only the content blocks below it are constrained). ---- */
:where(body.ft) .acct-page > :not(nav):not(style) { max-width:640px; }
/* ---- settings card: a titled card gets a header rule; a submit form gets a separated footer ---- */
:where(body.ft) .acct-page .panel { padding:22px; margin:18px 0; box-shadow:var(--shadow-sm); }
:where(body.ft) .acct-page .panel > h2:first-child { display:flex; align-items:center; margin:-22px -22px 20px; padding:14px 22px;
    font-size:14.5px; font-weight:700; letter-spacing:-.01em; border-bottom:1px solid var(--border); }
/* A section heading that stands on its OWN, because a table follows it rather than a card. Same typography as
   the in-panel header above — it does the same job — but no pulled-out band, which needs a card to bleed into.
   Without this it fell through to the generic 24px h2 and was LARGER than the 22px page title two lines above,
   inverting the hierarchy on any account page whose table is no longer inside a panel. */
:where(body.ft) .acct-page > h2 { display:flex; align-items:center; font-size:14.5px; font-weight:700; letter-spacing:-.01em;
    margin:22px 0 10px; }
:where(body.ft) .acct-page .panel form .form-actions { margin:22px -22px 0; padding:18px 22px 0; border-top:1px solid var(--border); }
/* ---- fields: fill the card width (no empty right-hand gutter inside the card) ---- */
:where(body.ft) .acct-page .field { margin:16px 0; max-width:none; }
:where(body.ft) .acct-page .field label { font-size:12.5px; font-weight:600; color:var(--text-2); }
@media (max-width:640px) {
:where(body.ft) .acct-page .panel { padding:18px; }
:where(body.ft) .acct-page .panel > h2:first-child { margin:-18px -18px 16px; padding:13px 18px; }
:where(body.ft) .acct-page .panel form .form-actions { margin:18px -18px 0; padding:16px 18px 0; }
}


/* ---- dashboard verification cards  <- resources/views/livewire/dashboard/partials/verification-cards.blade.php   [scope: ft] ---- */

:where(body.ft) .ft-verify { display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr)); gap:14px; margin:0 0 20px; }
:where(body.ft) .ft-verify__card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg);
    padding:16px 17px; box-shadow:var(--shadow-sm); }
:where(body.ft) .ft-verify__top { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:13px; }
:where(body.ft) .ft-verify__ico { width:36px; height:36px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
    border-radius:var(--r-md); background:var(--surface-3); border:1px solid var(--border); color:var(--text-muted); }
:where(body.ft) .ft-verify__ico svg { width:18px; height:18px; display:block; }
:where(body.ft) .ft-verify__pill { font-size:11px; font-weight:700; letter-spacing:.01em; padding:4px 10px; border-radius:var(--radius-pill);
    border:1px solid var(--border); background:var(--surface-3); color:var(--text-2); white-space:nowrap; }
:where(body.ft) .ft-verify__t { margin:0 0 3px; font-size:13.5px; font-weight:700; letter-spacing:-.01em; color:var(--text); }
:where(body.ft) .ft-verify__d { margin:0; font-size:12px; line-height:1.45; color:var(--text-muted); }
:where(body.ft) .ft-verify__card.is-ok   .ft-verify__ico { color:var(--up);       border-color:var(--ok-border);   background:var(--ok-bg); }
:where(body.ft) .ft-verify__card.is-ok   .ft-verify__pill { color:var(--ok-text);   border-color:var(--ok-border);   background:var(--ok-bg); }
:where(body.ft) .ft-verify__card.is-warn .ft-verify__ico { color:var(--warn);     border-color:var(--warn-border); background:var(--warn-bg); }
:where(body.ft) .ft-verify__card.is-warn .ft-verify__pill { color:var(--warn-text); border-color:var(--warn-border); background:var(--warn-bg); }
:where(body.ft) .ft-verify__card.is-down .ft-verify__ico { color:var(--down);     border-color:var(--err-border);  background:var(--err-bg); }
:where(body.ft) .ft-verify__card.is-down .ft-verify__pill { color:var(--err-text);  border-color:var(--err-border);  background:var(--err-bg); }
:where(body.ft) .ft-verify__m { margin:9px 0 0; font-size:11.5px; font-weight:600; color:var(--text-2); }
:where(body.ft) .ft-verify__a { display:inline-block; margin-top:11px; font-size:12px; font-weight:700; color:var(--primary);
    text-decoration:none; }
:where(body.ft) .ft-verify__a:hover { text-decoration:underline; }


/* ---- research detail  <- resources/views/livewire/research/detail.blade.php   [scope: ft] ---- */

/* Self-contained article typography (layouts.public has no .prose). */
:where(body.ft) .research-prose { max-width:74ch; color:var(--text-2); line-height:1.7; margin-top:8px; }
:where(body.ft) .research-prose h1,
:where(body.ft) .research-prose h2 { font-size:20px; margin:28px 0 10px; color:var(--text); letter-spacing:-.01em; }
:where(body.ft) .research-prose h3 { font-size:16px; margin:22px 0 8px; color:var(--text); }
:where(body.ft) .research-prose p { margin:0 0 14px; }
:where(body.ft) .research-prose ul,
:where(body.ft) .research-prose ol { margin:0 0 14px; padding-left:22px; }
:where(body.ft) .research-prose li { margin:4px 0; }
:where(body.ft) .research-prose a { color:var(--primary); }
:where(body.ft) .research-prose code { background:var(--surface-2); padding:1px 6px; border-radius:5px; font-size:13px; }
:where(body.ft) .research-prose blockquote { margin:0 0 14px; padding:8px 16px; border-left:3px solid var(--border-strong); color:var(--text-muted); }
:where(body.ft) .research-prose hr { border:0; border-top:1px solid var(--border); margin:24px 0; }


/* ---- role chooser  <- resources/views/livewire/onboarding/role-chooser.blade.php   [scope: ft] ---- */

:where(body.ft) .rc-h1 { display:flex; align-items:center; gap:9px; }
:where(body.ft) .rc-h1 .rc-ico { display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px;
                 color:var(--primary); flex-shrink:0; }
:where(body.ft) .rc-h1 .rc-ico svg { width:22px; height:22px; }
/* A role closed to new sign-ups. The card stays fully READABLE — no opacity on text, which would
   reduce the contrast the design system guarantees — so only the accent tile is neutralised, which is
   what stops the card advertising itself as an available action. */
:where(body.ft) .rolecard.soon .ricon { background: color-mix(in srgb, var(--text-muted) 10%, transparent);
                        color: var(--text-muted); }
:where(body.ft) .rolecard.soon:hover { border-color: var(--hairline); }
:where(body.ft) .rolecard .soonnote { font-size: 13px; color: var(--text-muted); margin: 6px 0 0; }


/* ---- strategist history import  <- resources/views/livewire/strategist/manage-history.blade.php   [scope: ft] ---- */

:where(body.ft) .mh-picker { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
:where(body.ft) .mh-imports td .badge { white-space: nowrap; }
:where(body.ft) .mh-warn { margin: 6px 0 0; padding-left: 16px; font-size: 12px; color: var(--text-muted); }
:where(body.ft) .mh-file-meta { font-size: 12px; color: var(--text-faint); }


/* ---- cookie notice  <- resources/views/partials/cookie-notice-card.blade.php   [scope: body.ft] ---- */

/* Keep the page's own bottom edge reachable while the notice is up. */
body:where(.ft):has(.ftx-consent--chrome) { padding-bottom: calc(var(--ftx-consent-h, 190px) + var(--s-sm)); }
/* The assistant launcher (bottom:20px) and the notification toast (bottom:90px) both sit under a
   bottom-anchored notice. These overrides exist only for as long as the notice element does — when the
   notice is removed they go with it, and the launcher drops back on its own. */
body:where(.ft):has(.ftx-consent--chrome) .ftx-chat { bottom: calc(var(--ftx-consent-h, 190px) + 28px) !important; }
body:where(.ft):has(.ftx-consent--chrome) .ftx-toast { bottom: calc(var(--ftx-consent-h, 190px) + 96px) !important; }

:where(body.ft) .ftx-consent {
    position: fixed; z-index: 145;
    left: 0; right: 0; bottom: 0;
    background: var(--surface);
    border-top: 1px solid var(--hairline);
    color: var(--text-2);
    font-size: 15px; line-height: 1.47;
}
/* Card variants: not full-width, so they carry a full hairline and a radius. */
:where(body.ft) .ftx-consent--left,
:where(body.ft) .ftx-consent--right {
    left: auto; right: auto; bottom: var(--s-md);
    max-width: min(430px, calc(100vw - 2 * var(--s-md)));
    border: 1px solid var(--hairline);
    border-radius: var(--r-lg);
}
:where(body.ft) .ftx-consent--left { left: var(--s-md); }
:where(body.ft) .ftx-consent--right { right: var(--s-md); }
:where(body.ft) .ftx-consent__panel {
    max-width: var(--maxw); margin: 0 auto;
    padding: var(--s-sm) var(--s-md);
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-xs) var(--s-lg);
}
:where(body.ft) .ftx-consent--left .ftx-consent__panel,
:where(body.ft) .ftx-consent--right .ftx-consent__panel { display: block; }
:where(body.ft) .ftx-consent__copy { flex: 1 1 26rem; min-width: 0; }
:where(body.ft) .ftx-consent__title { margin: 0; font-size: 17px; font-weight: 600; color: var(--text); letter-spacing: -.2px; }
:where(body.ft) .ftx-consent__body { margin: 2px 0 0; color: var(--text-muted); }
:where(body.ft) .ftx-consent__body a { color: var(--accent-text); }
:where(body.ft) .ftx-consent__actions { display: flex; flex-wrap: wrap; gap: var(--s-xs); margin: 0; }
/* Both choices are the same size and sit side by side: a refusal that is visibly harder to make is
   not a real choice. The only difference is fill vs ghost, which is this system's CTA pair. */
:where(body.ft) .ftx-consent__actions .btn { font-size: 15px; padding: 9px 20px; }
:where(body.ft) .ftx-consent--left .ftx-consent__actions,
:where(body.ft) .ftx-consent--right .ftx-consent__actions { margin-top: var(--s-sm); }
:where(body.ft) .ftx-consent__more { flex: 1 1 100%; margin: 0; }
:where(body.ft) .ftx-consent__more > summary {
    cursor: pointer; color: var(--accent-text); font-size: 14px;
    padding: 4px 0; width: fit-content; list-style: none;
}
:where(body.ft) .ftx-consent__more > summary::-webkit-details-marker { display: none; }
:where(body.ft) .ftx-consent__more > summary::before { content: '\203A\00a0'; display: inline-block; transition: transform var(--dur-fast) var(--ease); }
:where(body.ft) .ftx-consent__more[open] > summary::before { transform: rotate(90deg); }
:where(body.ft) .ftx-consent__more > summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-xs); }
:where(body.ft) .ftx-consent__detail {
    border-top: 1px solid var(--divider-soft);
    margin-top: var(--s-xs); padding-top: var(--s-sm);
    max-height: min(46vh, 420px); overflow-y: auto;
}
:where(body.ft) .ftx-consent__cat { margin: 0 0 var(--s-sm); }
:where(body.ft) .ftx-consent__cat h3 { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
:where(body.ft) .ftx-consent__cat p { margin: 1px 0 0; color: var(--text-muted); font-size: 14px; }
:where(body.ft) .ftx-consent__tag {
    display: inline-block; margin-left: var(--s-xxs);
    font-size: 11px; font-weight: 400; letter-spacing: .02em; text-transform: uppercase;
    color: var(--text-muted); border: 1px solid var(--hairline);
    border-radius: var(--r-pill); padding: 1px 8px; vertical-align: 2px;
}
:where(body.ft) .ftx-consent__inv { overflow-x: auto; }
:where(body.ft) .ftx-consent__inv table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 34rem; }
:where(body.ft) .ftx-consent__inv th,
:where(body.ft) .ftx-consent__inv td {
    text-align: left; vertical-align: top;
    padding: 6px 10px 6px 0; border-bottom: 1px solid var(--divider-soft);
}
:where(body.ft) .ftx-consent__inv th { color: var(--text); font-weight: 600; white-space: nowrap; }
:where(body.ft) .ftx-consent__inv td { color: var(--text-muted); }
:where(body.ft) .ftx-consent__inv code { font-size: 12px; color: var(--text-2); }
:where(body.ft) .ftx-consent__policy { display: inline-block; margin-top: var(--s-xs); color: var(--accent-text); font-size: 14px; }
@media (max-width: 720px) {
:where(body.ft) .ftx-consent__panel { padding: var(--s-sm) var(--s-sm) var(--s-xs); }
:where(body.ft) .ftx-consent__actions { flex: 1 1 100%; }
/* Full-width AND taller: the compact desktop padding measured 38px in a real browser, under the
       44px touch-target floor. A consent choice is the one control on the page a visitor must be able
       to hit first time. */
:where(body.ft) .ftx-consent__actions .btn { flex: 1 1 auto; padding: 12px 20px; min-height: 44px; }
}

/* ==========================================================================================================
   MESSAGES — the participant's two-way thread with the support desk.
   ==========================================================================================================
   Scoped :where(body.ft) like everything else here, so it contributes zero specificity and source order alone
   decides ties — see the `MOVED OUT OF BLADE` banner above for why every customer rule lives in this one file
   rather than in a per-layout <style>.

   A thread is the one place on this site where WHO SAID IT has to be readable at a glance, so the two parties
   differ by ALIGNMENT and GROUND, never by colour alone: a participant's own message sits right on the accent,
   staff sit left on a surface. Colour is confirmation, not the signal — the same rule the P&L pair follows.
   ---------------------------------------------------------------------------------------------------------- */

:where(body.ft) .ftx-thread { display: flex; flex-direction: column; gap: var(--s-xs); }

:where(body.ft) .ftx-msg { display: flex; flex-direction: column; max-width: min(46rem, 82%); }
:where(body.ft) .ftx-msg--them { align-self: flex-start; }
:where(body.ft) .ftx-msg--me { align-self: flex-end; align-items: flex-end; }

:where(body.ft) .ftx-msg__meta {
    font-size: 12px; color: var(--text-muted);
    margin-bottom: 4px; padding: 0 2px;
}
:where(body.ft) .ftx-msg__who { font-weight: 600; color: var(--text-2); }
:where(body.ft) .ftx-msg--them .ftx-msg__who { color: var(--accent-text); }

:where(body.ft) .ftx-msg__body {
    /* pre-wrap, not a <p> per line: somebody's paragraph breaks are part of what they wrote. overflow-wrap
       so a pasted account number or URL with no spaces cannot push the page sideways. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    padding: 10px 14px;
    border-radius: var(--r-lg);
    font-size: 15px; line-height: 1.5;
}
:where(body.ft) .ftx-msg--them .ftx-msg__body {
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--hairline);
    border-bottom-left-radius: var(--r-xs);
}
:where(body.ft) .ftx-msg--me .ftx-msg__body {
    background: var(--accent); color: var(--on-accent);
    border: 1px solid transparent;
    border-bottom-right-radius: var(--r-xs);
}

/* The composer sits under the thread and stays put; the thread scrolls above it. */
:where(body.ft) .ftx-composer { margin-top: var(--s-sm); border-top: 1px solid var(--divider-soft); padding-top: var(--s-sm); }
:where(body.ft) .ftx-composer textarea { min-height: 5rem; }

/* Thread list */
:where(body.ft) .ftx-threads { display: flex; flex-direction: column; }
:where(body.ft) .ftx-threads__row {
    display: flex; align-items: baseline; gap: var(--s-xs);
    padding: 12px 0; border-bottom: 1px solid var(--divider-soft);
    color: inherit; text-decoration: none;
}
:where(body.ft) .ftx-threads__row:last-child { border-bottom: 0; }
:where(body.ft) .ftx-threads__subject { font-weight: 600; flex: 1 1 auto; }
:where(body.ft) .ftx-threads__when { font-size: 12px; color: var(--text-muted); white-space: nowrap; }

/* The unread pill. A count, never a bare dot: "3 unread" is actionable and a dot is not. */
:where(body.ft) .ftx-unread {
    display: inline-block; min-width: 20px; text-align: center;
    background: var(--accent); color: var(--on-accent);
    border-radius: var(--r-pill); padding: 1px 7px;
    font-size: 12px; font-weight: 600; line-height: 1.5;
}

@media (max-width: 640px) {
:where(body.ft) .ftx-msg { max-width: 92%; }
}


/* ==========================================================================================================
   design-v2 sections  <- resources/views/welcome.blade.php   [scope: v2]
   ==========================================================================================================

   The redesigned public home page. Scoped `:where(body.v2)`, and `v2` is a real body class: a page carries
   it once it has been ported. That is deliberately a MIGRATION SWITCH, not a page name --
   `:where(body.ly-marketing:not(.v2))` above keeps the outgoing ledger grammar on the six pages that have
   not been ported yet, and converting one becomes "add v2 to its body". When the last page is converted,
   every `:not(.v2)` in this file is dead and can be swept in one commit.

   ORDER MATTERS AND IS THE ORDER BELOW: tokens, shared vocabulary, then the ten sections in the order they
   appear on the page. `:where()` contributes zero specificity, so almost everything here ties with its
   neighbours and source order is the only tie-break.

   Fonts are self-hosted at public/fonts/ (relative URLs in the @font-face block resolve against THIS file,
   not against the document). Artwork is at public/img/v2/.
   ========================================================================================================== */

/* ---------------- design-v2: 00-tokens.css ---------------- */
/*! FinTruvix design v2 — TOKEN LAYER (merged — this file is served)
    ===========================================================================================================
    This is stage 1 of porting the mizhar-developer/fintruvix landing design into the customer front end.
    Nothing here ships yet. At stage 2 these blocks merge into public/ft-ui.css in its declared order
    (base -> site-chrome -> layouts -> page -> components) and the scope changes from `.v2` to the real
    layout scopes.

    THE ORGANISING DECISION: the reference has ONE palette and no theming at all — zero `prefers-color-scheme`
    and zero `data-theme` in 4,508 lines. This product has two themes, a live toggle, and tests that recompute
    contrast from tokens. So the port does NOT bolt a second palette on beside the existing one; it expresses
    the reference's design in the EXISTING token vocabulary wherever the meaning matches, and adds `--v2-*`
    only for what is genuinely new. That is what keeps `ftToggleTheme()`, the accent inversion, and
    LiquidGlassLegibilityTest working unchanged.

    WHAT WAS DELIBERATELY *NOT* ADOPTED FROM THE REFERENCE, and why:

      • `--ft-lime: #c4e01a` -> the existing `--accent` (#C7EA46 dark / #4d7c0f light) is KEPT.
        Measured: #c4e01a on the reference ink is 13.31:1; #C7EA46 on this product's --canvas is 14.18:1.
        Both are excellent, the difference is imperceptible at a glance, and #C7EA46 is the value whose
        `--on-accent` pair (#14210A, 12.21:1) has already been verified. Changing the accent would ripple
        through every control in the product to buy nothing measurable.
        The ceiling WASH keeps the reference's own 9-stop ramp — a designed gradient is not the accent token,
        and a glow reading slightly more saturated than a flat fill is correct.

      • `--ft-ink: #08090a` -> the existing `--canvas` (#0C0D0B dark) is KEPT. Relative luminance 0.00269 vs
        0.00390 — indistinguishable, and churning it would invalidate every contrast figure already recorded.

      • `--ft-glow: #a6e22e` is NOT ported. It has zero var() consumers in the reference; the value appears
        once, hardcoded, as an SVG floodColor. Porting an unused token would be porting a mistake.

    THE ONE VALUE THE REFERENCE GOT WRONG, AND THE FIX IT NAMES ITSELF (globals.css:15-18):
        "--ceiling-copy: #fff ... matches the reference comp but measures 2.02:1 on the lime (body) and
         2.75:1 (formula) — both below WCAG AA. Switch to var(--ft-ink) for 9.88:1 / 7.24:1."
    The reference applies that fix only below 860px, so the desktop composition it was drawn for depends on a
    treatment that fails AA. Here `--v2-on-wash` is ink at EVERY width — and that single change is also what
    makes the light variant possible, because a lime card carrying dark text is legible on any page ground.
    The AA fix and the light-theme problem turn out to have the same answer.
    =========================================================================================================== */

/* ---------------------------------------------------------------------------------------------------------
   TYPE. The reference is Figtree (display/body) + Geist Mono (the passport MRZ, the clause enforcement pill,
   the cookie table). ft-ui.css currently ships NO webfont and says why: "a CDN request would add a
   third-party dependency, a preconnect and a CLS risk to a build-less stylesheet".

   That objection is to the CDN, not to the typeface — and it is answered by self-hosting, which this repo
   already does for the Control Tower (spatie/laravel-google-fonts, config/google-fonts.php, disk `public`).
   Both families are VARIABLE, so all weights 400-700 come from ONE file per unicode range:
       Figtree    latin 19.7 KB + latin-ext 10.0 KB
       Geist Mono latin 22.6 KB + latin-ext 14.4 KB      = 66.7 KB total, no third-party request.
   `font-display: swap` plus a matched fallback stack keeps the CLS risk to a reflow, not a blank page.
   --------------------------------------------------------------------------------------------------------- */

@font-face {
    font-family: 'Figtree'; font-style: normal; font-weight: 400 700; font-display: swap;
    src: url('fonts/figtree-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
                   U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Figtree'; font-style: normal; font-weight: 400 700; font-display: swap;
    src: url('fonts/figtree-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
                   U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
                   U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Geist Mono'; font-style: normal; font-weight: 400 500; font-display: swap;
    src: url('fonts/geistmono-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
                   U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Geist Mono'; font-style: normal; font-weight: 400 500; font-display: swap;
    src: url('fonts/geistmono-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
                   U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
                   U+2C60-2C7F, U+A720-A7FF;
}

/* =========================================================================================================
   LIGHT — the shipped default, and the half the reference does not have.
   ========================================================================================================= */
:root {
    /* --- type ------------------------------------------------------------------------------------------ */
    /* The fallback stack is the CURRENT product stack, so a font that fails to load degrades to exactly
       what ships today rather than to Times. (Measured: with `--font-figtree` undefined the reference's own
       `font-family: var(--font-figtree), ui-sans-serif, …` is INVALID and inherits from <html>, which sets
       none — so the whole page renders in the UA serif. A missing font token must never be able to do that.) */
    --font-display: 'Figtree', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, 'Segoe UI',
        Roboto, Inter, 'Helvetica Neue', Arial, sans-serif;
    --font-mono-v2: 'Geist Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

    /* --- grounds --------------------------------------------------------------------------------------- */
    /* The thin frame the reference puts around every full-bleed card, and the card itself. On light the
       card is white so the lime wash has somewhere to fade TO; on dark it is near-black. */
    --v2-frame: var(--canvas);
    --v2-card: #ffffff;
    --v2-card-line: var(--hairline);
    --v2-plate: var(--surface-3);          /* stat tiles, inner panels */

    /* --- the ceiling wash ------------------------------------------------------------------------------ */
    /* One value, not nine tokens: a designed multi-stop gradient is a single decision and splitting it into
       stops invites somebody to edit one of them. The LIGHT ramp is the reference's own top three stops
       (the lime is the lime in both themes) and then climbs back to the card instead of down into black. */
    /* THE WASH IS A PLATE PLUS A FIXED-HEIGHT FADE, NOT ONE LONG RAMP — and that is a correctness fix,
       not a preference.

       The reference paints a single 9-stop gradient across a box whose height comes from the VIEWPORT
       (42vw) while the copy inside it has a height that comes from the CONTENT. Those two disagree the
       moment the text wraps, so where a line lands on the ramp is not a property anyone controls.
       Measured, on the reference's own stops: the formula reads 3.36:1 at 1440 and 1.14:1 at 768, because
       at 768 the box is smaller and the copy is taller, so the formula slides from 62% of the ramp to
       ~90% — deep in the dark. Re-tuning the stops moved the failure around; it could not remove it.

       So: a flat BRIGHT plate wherever text can be, and a fade band of FIXED HEIGHT anchored to the
       bottom edge, with the intro's bottom padding set to that same height so no copy can ever enter it.
       Legibility then stops depending on layout at all — ink on the plate is 10-13:1 at every width, by
       construction rather than by tuning. */
    --v2-wash-plate:
        radial-gradient(70% 55% at 0% 0%,    rgb(20 28 6 / .10) 0%, rgb(20 28 6 / 0) 72%),
        radial-gradient(60% 50% at 100% 0%,  rgb(20 28 6 / .08) 0%, rgb(20 28 6 / 0) 70%),
        radial-gradient(120% 80% at 58% 0%,  rgb(228 255 96 / .55) 0%, rgb(228 255 96 / 0) 58%),
        linear-gradient(180deg, #c9e92c 0%, #c8e82b 46%, #c3e42d 100%);
    /* fades to the CARD, written as a literal rather than `transparent`: `transparent` is rgba(0,0,0,0),
       and interpolating a colour toward it darkens the midpoint on any engine that does not premultiply. */
    --v2-wash-fade: linear-gradient(to top, #ffffff 0%, rgb(255 255 255 / 0) 100%);
    --v2-wash-fade-h: clamp(4rem, 7vw, 7rem);

    /* THE AA FIX. Ink on the wash in BOTH themes — 9.88:1 for the body, 7.24:1 for the formula, against the
       #fff the comp specifies, which is 2.02:1 and 2.75:1. The title was already ink in the reference. */
    --v2-on-wash: #14210A;
    --v2-on-wash-2: color-mix(in srgb, #14210A 76%, transparent);

    /* --- the pixel mosaic over the wash ---------------------------------------------------------------- */
    /* The reference's tones are white at 5%/8.5% and black at 7%, authored against a wash that runs into
       near-black. On a wash that runs into white the white plates vanish, so light inverts the emphasis:
       the plates darken the lime instead of lightening it. */
    --v2-tex-1: rgb(20 28 6 / .07);
    --v2-tex-2: rgb(20 28 6 / .11);
    --v2-tex-3: rgb(255 255 255 / .16);
    --v2-tex-rule: rgb(20 28 6 / .09);

    /* --- the hero arrow --------------------------------------------------------------------------------
       The bevelled lime arrow inside the h1. Tokens rather than literals in the markup for two reasons:
       a customer Blade may not carry hex (CLAUDE.md), and the arrow has to re-tint with the brand rather
       than being five values somebody has to find. Deliberately NOT theme-split -- the hero is one of the
       permanently-dark scopes (its ground is the video), so the arrow reads the same in both themes.

       These are a bevel, not the palette: a lit face, two mid tones, a shaded face and a cast. They are
       the accent's own family (hue 86) and must stay there, because a green CONTROL and a green NUMBER
       already mean different things in this product and a third green would blur both. The specular
       sheens stay literal white in the drawing -- a highlight is light, not brand colour. */
    --v2-arrow-lit: #d9f83a;
    --v2-arrow-mid: #bced1c;
    --v2-arrow-low: #9ed318;
    --v2-arrow-deep: #79ac0d;
    --v2-arrow-cast: #5c8a06;
    --v2-arrow-glow: #a6e22e;
    /* The specular highlight on the arrow. A separate token from the bevel because it is LIGHT, not
       brand colour -- it stays white if the accent ever moves, and it is named so a future warm or
       cool sheen is a one-line change rather than six edits inside an SVG. */
    --v2-spec: #ffffff;

    /* --- full-bleed artwork ---------------------------------------------------------------------------- */
    /* The reference's section art is glow-on-black; it cannot sit on a light ground without becoming a dark
       slab — which is the exact defect this codebase already found and fixed once (2,188px of near-black on
       a light page). So on light the artwork is OFF by default and the section carries a lime-tinted ground
       instead. `.v2-keep-art` on <body> restores it for comparison; see the two harness pages. */
    --v2-art-opacity: 0;
    --v2-art-ground: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 5%, var(--canvas)) 0%,
        var(--canvas) 62%);
    --v2-art-veil: transparent;

    /* --- shape ----------------------------------------------------------------------------------------- */
    --v2-r-card: clamp(1.25rem, 2.6vw, 3.5rem);
    --v2-r-tile: clamp(.75rem, 1.4vw, 1.25rem);
    --v2-frame-pad: clamp(.75rem, 1.4vw, 1.75rem);
    --v2-card-pad-x: clamp(1.5rem, 3.8vw, 5rem);
    --v2-card-pad-top: clamp(1.25rem, 3.1vw, 4rem);
    --v2-card-pad-bottom: clamp(2.5rem, 5vw, 6rem);
}

/* =========================================================================================================
   DARK — the reference's own single palette.

   EVERY TOKEN BUILT FROM var() IS RE-DECLARED HERE AS ITS FORMULA, not merely inherited. This is the trap
   ft-ui.css documents at length: a custom property's var() references are substituted WHERE IT IS DECLARED,
   so a token composed on :root keeps its :root value inside a scope that overrides its inputs. Omitting
   --v2-wash here would leave the light ramp's final stop pointing at the LIGHT card colour on a dark page.
   ========================================================================================================= */
:root[data-theme="dark"] {
    --v2-frame: var(--canvas);
    --v2-card: #0b0c0d;
    --v2-card-line: var(--hairline);
    --v2-plate: #050507;

    /* Same plate, deeper corner shading; only the fade differs, because it lands on a different card. */
    --v2-wash-plate:
        radial-gradient(70% 55% at 0% 0%,    rgb(20 28 6 / .18) 0%, rgb(20 28 6 / 0) 72%),
        radial-gradient(60% 50% at 100% 0%,  rgb(20 28 6 / .14) 0%, rgb(20 28 6 / 0) 70%),
        radial-gradient(120% 80% at 58% 0%,  rgb(228 255 96 / .5) 0%, rgb(228 255 96 / 0) 58%),
        linear-gradient(180deg, #c9e92c 0%, #c8e82b 46%, #c3e42d 100%);
    --v2-wash-fade: linear-gradient(to top, #0b0c0d 0%, rgb(11 12 13 / 0) 100%);
    --v2-wash-fade-h: clamp(4rem, 7vw, 7rem);

    --v2-on-wash: #14210A;
    --v2-on-wash-2: color-mix(in srgb, #14210A 76%, transparent);

    --v2-tex-1: rgb(255 255 255 / .05);
    --v2-tex-2: rgb(255 255 255 / .085);
    --v2-tex-3: rgb(0 0 0 / .07);
    --v2-tex-rule: rgb(255 255 255 / .065);

    --v2-art-opacity: 1;
    --v2-art-ground: none;
    --v2-art-veil: linear-gradient(180deg,
        rgb(4 5 6 / .72) 0%, rgb(4 5 6 / .42) 34%, rgb(4 5 6 / .58) 72%, rgb(4 5 6 / .86) 100%);
}

/* ---------------- design-v2: s00-shared.css ---------------- */
/*! design v2 — SHARED SECTION VOCABULARY (merged)
    ===========================================================================================================
    Small pieces used by more than one section. They live here rather than in whichever section happened to
    need them first, because a component duplicated across two section files is how two copies drift — and
    the parallel port that produced those files is exactly the situation that breeds it.

    THE KICKER, AND WHY IT IS NOT DECORATION.

    The reference gives two sections a small outlined label above the heading: `.pass__kicker` ("The
    document") and `.clauses__kicker` ("Enforced, not promised"). Its other sections have none.

    This product's home page is a CMS surface, and the catalog carries a `*.key` field for EVERY section —
    issuance.key, formula.key, execution.key, ratchet.key, registry.key, clauses.key, faq.key, closing.key.
    They exist because the outgoing ledger design used them as row labels down its spine. HomeContentTest
    asserts BIDIRECTIONAL parity between the keys the Blade reads and the catalog, so each one must render
    somewhere or the test fails; and simply deleting them would throw away whatever wording staff have
    already put in those rows, with no export path to get it back.

    So the kicker becomes the general treatment rather than a two-section flourish: every section gets one,
    carrying its existing `*.key` value. That keeps all 84 keys live, keeps the catalog and the migration
    untouched, and is a smaller change to the reference's design language than inventing a new slot would
    be — two of its sections already look exactly like this.

    `.pass__kicker` and `.clauses__kicker` keep their names as the reference wrote them; they are aliased
    onto this rule rather than restyled, so the section files stay readable against the reference.
    =========================================================================================================== */

:where(body.v2) .v2-kicker,
:where(body.v2) .pass__kicker,
:where(body.v2) .clauses__kicker {
    display: inline-block;
    margin: 0 0 clamp(.75rem, 1.6vw, 1.5rem);
    padding: .5em .8em;
    border: 1px solid var(--field-border);
    border-radius: var(--r-sm);
    color: var(--text-muted);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .82vw, .875rem);
    letter-spacing: .08em;
    line-height: 1.3;
    text-transform: uppercase;
}

/* ------------------------------------------------------------------ *
 * Section frame
 *
 * The reference uses two spacing formulas and nothing else: a full-bleed section pad, and a thin inset
 * frame around the big rounded cards. Naming them here stops each section inventing its own.
 * ------------------------------------------------------------------ */

:where(body.v2) .v2-section {
    padding: clamp(2.5rem, 6vw, 7rem) clamp(1rem, 3vw, 4rem);
}

:where(body.v2) .v2-framed {
    padding: var(--v2-frame-pad);
    background: var(--v2-frame);
}


/* ------------------------------------------------------------------ *
 * The risk caveat, v2 variant.
 *
 * partials/risk-note is SHARED with the ledger pages, so it cannot be styled from a page's own rules --
 * this project has already paid for that once: the ledger spine treatment lived in the marketing layout,
 * wire:navigate merges the <head> and never removes a <style>, and the caveat arrived on /contact
 * indented 129px by a layout it does not belong to. So the variant is a MODIFIER on the partial and its
 * rule lives beside the baseline.
 *
 * NOT :where() here, unlike everything else in this file. The baseline `.risk-note` in ft-ui.css is a
 * single class at (0,1,0); a :where()-wrapped modifier contributes zero and would lose to it on source
 * order once these files are concatenated. The modifier has to actually outrank the baseline it is
 * overriding, so it is written at its natural weight -- and `body.v2` still keeps it off every other
 * page. This is the one documented exception to the file's scoping convention.
 * ------------------------------------------------------------------ */
body.v2 .risk-note--v2 {
    /* CONSOLIDATED. This component was declared in two places (s00-shared and s95-closing) with different
       values; `body.v2` outranked `:where(body.v2)` on every shared property, so the page rendered a
       hybrid. These values ARE that hybrid -- deliberately, so the consolidation changes nothing on
       screen -- and this is now the only declaration. See the note in s95-closing where the duplicate was.

       Padding matches .v2-section's gutter so the caveat lines up with the copy above it rather than
       running to the viewport edge; there is no shared token for that gutter because it belongs to the
       section rule, so if that changes, change this with it. The background is the closing card's frame
       colour: the caveat sits directly above that card on every page that carries it, and this is what
       groups the two. */
    margin: 0;
    padding: clamp(1.75rem, 3.5vw, 3rem) clamp(1rem, 3vw, 4rem);
    border-top: 1px solid var(--hairline);
    background: var(--v2-frame);
}
body.v2 .risk-note--v2 p {
    /* 78ch and 13-14px, NOT the baseline's 12px: a caveat that has to be legible is not fine print, and
       this is the smallest step the painted-pixel audit clears at 390px, where the token arithmetic and
       the rendered pixel disagree most. `--font-display` is restated because this system applies it per
       component rather than on the body, so omitting it drops the caveat back to the base face. */
    margin: 0 auto;
    max-width: 78ch;
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: clamp(.8125rem, 1vw, .875rem);
    line-height: 1.55;
    text-align: center;
}

/* ------------------------------------------------------------------ *
 * Reduced motion — one place, so no section can forget it.
 *
 * Every scroll-linked reveal and draw in this design must land FINISHED rather than simply losing its
 * transition, or a reader who asks for less motion gets a permanently half-drawn chart. The reference
 * does this per-section; doing it once is both shorter and harder to get wrong.
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    :where(body.v2) *,
    :where(body.v2) *::before,
    :where(body.v2) *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    /* a pathLength="1" draw animation holds its dash offset when the animation is cut short, so it has to
       be explicitly released or the line stays invisible rather than arriving instantly */
    :where(body.v2) [pathLength] { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
}

/* ------------------------------------------------------------------ *
 * No JavaScript.
 *
 * The contract this codebase already enforces for its credential: content that JS animates must ship
 * COMPLETE in the HTML and be stripped back by `html.js`, never shipped blank for JS to fill. Built the
 * other way round, a visitor without JS scrolls past an empty rectangle while a screen reader is told
 * about fields that never arrive. Nothing below hides anything — it only releases what the scroll layer
 * would otherwise be holding.
 * ------------------------------------------------------------------ */

:where(body.v2) [pathLength] { stroke-dasharray: none; stroke-dashoffset: 0; }
:where(html.js body.v2) [pathLength][data-draw] { stroke-dasharray: 1; stroke-dashoffset: 1; }
:where(html.js body.v2) [data-draw].is-shown,
:where(html.js body.v2) .is-shown [data-draw] { stroke-dashoffset: 0; transition: stroke-dashoffset var(--dur) var(--ease); }

/* ---------------- design-v2: s10-hero.css ---------------- */
/*! design v2 — SUB-PAGE VOCABULARY
    ===========================================================================================================
    THE HOME PAGE IS A SHOWCASE; THESE SIX ARE REFERENCE DOCUMENTS, AND THAT DIFFERENCE IS THE WHOLE DESIGN.

    The reference repository contains exactly ONE page. It demonstrates the language on a landing page —
    a 2MB video hero, full-bleed art renders, scroll choreography — and says nothing about /pricing,
    /about or /download. So the six sub-pages are NOT a port of anything; they are this language applied
    at lower amplitude, which is a decision rather than a transcription and is written down as one.

    Lower amplitude, specifically:

      * NO VIDEO and no art renders. Somebody opens /pricing to find out what they pay and /for-followers
        to decide whether to sign up. Making them wait for a hero loop to buy the same typography is a
        cost with no return — and on the home page that video is the product's own claim, which is what
        earns it there.
      * The SAME tokens, type scale, radii, kicker and card geometry, so a visitor moving from the home
        page to /pricing stays inside one product rather than crossing into a second one.
      * Section rhythm from `.v2-section`, cards from `.v2-framed` — the two formulas s00-shared names,
        so no page here invents a third.

    WHAT THESE PAGES REUSE VERBATIM, because it already exists and is already measured: `.faq` (s90),
    `.closing` (s95), `.howto`/`.howstep` (s30) and `.risk-note--v2` (s00). Every v2 rule is scoped
    `body.v2` and nothing is scoped `pg-home`, which is what makes that reuse possible at all — it was the
    reason for the scoping convention, and this file is the first thing to collect on it.

    WHAT IS NEW HERE, and only because nothing existing fits:

      * `.v2-pagehead` — the sub-page counterpart to the hero. It replaces the outgoing ledger's opening
        `.led` row (eyebrow + h1 + lede + `.led__spec` aside), which is the shape all six share.
      * `.v2-rows`    — a card grid for the `rows` grammar. It is `.clauses` (s80) with the mesh, the veil
        and the object renders taken out: same geometry and numbering, none of the decoration, because on
        a sub-page the cards ARE the content rather than a showpiece.

    THERE IS DELIBERATELY NO PLANS COMPONENT. One was written here and removed: /pricing publishes no
    prices at all — it says the platform takes a marketplace fee on strategist earnings and never names a
    rate — so a plan card had no figure to carry, and a card with no figure is a row. `.v2-rows` is the
    row. Should a rate ever be published, this is where that component belongs.

    Colour comes only from tokens already in the system. Nothing here introduces a foreground/background
    pair the audit has not measured, which is what keeps LiquidGlassLegibilityTest and the painted-pixel
    sweep meaningful rather than merely green.
    =========================================================================================================== */


/* ------------------------------------------------------------------ *
 * PAGE HEAD
 *
 * The hero's typography without its stage. The title tracks the hero's own clamp so a sub-page heading
 * and the home heading read as the same voice at different volumes.
 *
 * The spec list is a <dl>: these are term/definition pairs ("Your capital" / "stays in your own broker
 * account"), and marking six of them up as paragraphs would throw away the only structure a screen
 * reader could use to pair them.
 * ------------------------------------------------------------------ */

:where(body.v2) .v2-pagehead {
    max-width: 74rem;
    margin-inline: auto;
}

:where(body.v2) .v2-pagehead__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    /* Negative tracking at display sizes, per the house rule. */
    font-size: clamp(2.25rem, 5.2vw, 4.5rem);
    font-weight: 600;
    letter-spacing: -.022em;
    line-height: 1.04;
}

:where(body.v2) .v2-pagehead__lede {
    max-width: 46ch;
    margin: clamp(1rem, 2vw, 1.75rem) 0 0;
    color: var(--text-2);
    font-family: var(--font-display);
    font-size: clamp(1.0625rem, 1.5vw, 1.375rem);
    line-height: 1.5;
}

:where(body.v2) .v2-pagehead__spec {
    display: grid;
    gap: 0;
    margin: clamp(2rem, 4vw, 3.5rem) 0 0;
    border-top: 1px solid var(--hairline);
}

:where(body.v2) .v2-pagehead__spec > div {
    display: grid;
    gap: .25rem .75rem;
    padding: clamp(.875rem, 1.6vw, 1.125rem) 0;
    border-bottom: 1px solid var(--hairline);
}

@media (min-width: 40rem) {
    :where(body.v2) .v2-pagehead__spec > div {
        grid-template-columns: minmax(9rem, 14rem) 1fr;
        align-items: baseline;
    }
}

:where(body.v2) .v2-pagehead__spec dt {
    margin: 0;
    color: var(--text-muted);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .82vw, .8125rem);
    letter-spacing: .08em;
    line-height: 1.4;
    text-transform: uppercase;
}

:where(body.v2) .v2-pagehead__spec dd {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(.9375rem, 1.15vw, 1.0625rem);
    line-height: 1.5;
}


/* ------------------------------------------------------------------ *
 * ROWS
 *
 * `.clauses` without the showpiece. The numbering is DERIVED from a CSS counter, never written into the
 * markup, for the reason CLAUDE.md already records: the ledger's hand-numbered keys ran 04 then 06
 * within minutes of being written, because one section was conditional.
 * ------------------------------------------------------------------ */

:where(body.v2) .v2-rows {
    counter-reset: v2row;
    display: grid;
    gap: clamp(.75rem, 1.4vw, 1.25rem);
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 48rem) { :where(body.v2) .v2-rows { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 76rem) { :where(body.v2) .v2-rows--3 { grid-template-columns: repeat(3, 1fr); } }

:where(body.v2) .v2-row {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: clamp(1.25rem, 2.4vw, 2rem);
    border: 1px solid var(--v2-card-line);
    border-radius: var(--v2-r-tile);
    background: var(--v2-card);
}

:where(body.v2) .v2-row__num {
    counter-increment: v2row;
    margin: 0 0 .25rem;
    color: var(--text-faint);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .8vw, .8125rem);
    letter-spacing: .08em;
}

:where(body.v2) .v2-row__num::before {
    /* 01, 02, … — two digits, derived. */
    content: counter(v2row, decimal-leading-zero);
}

:where(body.v2) .v2-row__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.125rem, 1.5vw, 1.375rem);
    font-weight: 600;
    letter-spacing: -.012em;
    line-height: 1.25;
}

:where(body.v2) .v2-row__body {
    margin: 0;
    color: var(--text-2);
    font-family: var(--font-display);
    font-size: clamp(.9375rem, 1.1vw, 1.0625rem);
    line-height: 1.55;
}

/* The enforcement tag, as `.clause__enforcement` does it: what BACKS the claim above it. Optional here,
   because not every row is a guarantee — and a row that carries one is making a stronger statement than
   a row that does not, so the two must not look the same. */
:where(body.v2) .v2-row__tag {
    margin: auto 0 0;
    padding-top: .75rem;
    border-top: 1px solid var(--hairline);
    color: var(--text-muted);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .8vw, .8125rem);
    line-height: 1.45;
}


/* ------------------------------------------------------------------ *
 * PROSE
 *
 * For /about and the long-form parts of /download. A reading measure, not a grid.
 * ------------------------------------------------------------------ */

:where(body.v2) .v2-prose {
    max-width: 68ch;
    color: var(--text-2);
    font-family: var(--font-display);
    font-size: clamp(1rem, 1.2vw, 1.125rem);
    line-height: 1.62;
}

:where(body.v2) .v2-prose > * + * { margin-top: 1.1em; }

:where(body.v2) .v2-prose h2 {
    margin-top: 1.8em;
    color: var(--text);
    font-size: clamp(1.375rem, 2.2vw, 1.875rem);
    font-weight: 600;
    letter-spacing: -.018em;
    line-height: 1.2;
}

:where(body.v2) .v2-prose h3 {
    margin-top: 1.6em;
    color: var(--text);
    font-size: clamp(1.0625rem, 1.4vw, 1.25rem);
    font-weight: 600;
    line-height: 1.3;
}

:where(body.v2) .v2-prose a { color: var(--accent-text); }


/* ------------------------------------------------------------------ *
 * SECTION HEADINGS
 *
 * A sub-page section is a kicker plus a heading, with an optional lede. `.clauses__head` does this on
 * the home page inside a decorated stage; this is the same relationship without one.
 * ------------------------------------------------------------------ */

:where(body.v2) .v2-head {
    max-width: 74rem;
    margin: 0 auto clamp(1.5rem, 3vw, 2.5rem);
}

:where(body.v2) .v2-head__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.625rem, 3.2vw, 2.75rem);
    font-weight: 600;
    letter-spacing: -.02em;
    line-height: 1.12;
}

:where(body.v2) .v2-head__lede {
    max-width: 52ch;
    margin: clamp(.75rem, 1.4vw, 1rem) 0 0;
    color: var(--text-2);
    font-family: var(--font-display);
    font-size: clamp(1rem, 1.3vw, 1.1875rem);
    line-height: 1.5;
}

/* The body of a section, held to the same measure as its head so the two edges line up. */
:where(body.v2) .v2-body { max-width: 74rem; margin-inline: auto; }


/*! design v2 — HERO (merged)
    ===========================================================================================================
    A full-bleed video hero: looping footage, a three-pass scrim, the nav, a three-line headline with an
    inline rise-arrow, a lede in a second column, and a bottom claim strip.

    THIS IS THE ONE SECTION WHOSE LEGIBILITY CANNOT BE COMPUTED. Every other section composites token over
    token, so a contrast ratio is arithmetic. Here the ground is a moving photograph: what sits behind the
    headline depends on which frame is showing. The reference records pre-scrim measurements only ("claims
    fell to 1.46:1"; "the lede measured 2.65:1 against the brightest 5% of pixels behind it") and never
    states what the scrim actually delivers. So this section is verified by SAMPLING REAL FRAMES — see
    tools/audit-hero.ps1, which seeks the video across its whole duration and measures the worst one.

    FOUR DELIBERATE DEPARTURES FROM THE COMP, each with a reason that is not taste:

    1. THE DESKTOP HERO GETS ITS CALL TO ACTION BACK. The reference sets `.hero__actions { display: none }`
       above 861px and says so on purpose — "with the links behind the trigger the hero otherwise carries no
       action at all", i.e. the CTAs exist only because the phone nav swallowed the links. The consequence is
       a marketing home page whose primary conversion control is absent at every desktop width. This product
       also already has three CMS keys for those buttons (origin.cta_primary, .cta_secondary,
       .cta_primary_auth), so the comp's omission would leave staff editing wording for a control nobody sees.

    2. THERE IS A PAUSE CONTROL, and it is not optional. WCAG 2.2.2 requires a mechanism to pause, stop or
       hide any moving content that starts automatically and runs more than five seconds. The reference has
       none — its only concession to motion is a prefers-reduced-motion branch in JS. A financial product
       with an automated accessibility suite cannot ship the gap.

    3. THE HERO STAYS DARK IN BOTH THEMES, and joins the permanently-dark scope rather than following the
       page. This is NOT the "dark slab on a light page" defect the clauses section had to avoid: that was a
       bare TONE STEP with nothing in it, mid-page. A full-bleed media hero at the top of a document is a
       photograph, and a photograph is dark because the footage is dark. What matters is that the token set
       inverts inside it — see the scope block at the bottom of this file.

    4. THE VIDEO IS NOT `preload="auto"`. The reference puts 1.9 MB on the critical path ahead of the copy.
       `preload="metadata"` plus the 50 KB poster gets the same first paint and lets the connection serve the
       page first.
    =========================================================================================================== */

:root {
    /* The reference calls this --ft-gutter but it is NOT the site gutter: every other section uses
       clamp(1rem, 3vw, 4rem), and this is deliberately much wider so the hero sits further inset. */
    --v2-hero-gutter: clamp(1.25rem, 7.4vw, 9rem);
    --v2-hero-ink: #08090a;      /* the scrim's own colour — the footage's black, not the page's */
}

/* ------------------------------------------------------------------ *
 * Frame
 * ------------------------------------------------------------------ */

:where(body.v2) .hero {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: auto 1fr auto;
    min-height: 100svh;
    overflow: hidden;
    padding-inline: var(--v2-hero-gutter);
    background: var(--v2-hero-ink);
}

:where(body.v2) .hero__video {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center bottom;
    pointer-events: none;
    /* the chart footage runs bright across the middle; a light global tone-down does most of the
       legibility work without flattening it */
    filter: brightness(.88) saturate(.96);
}

/* THE SCRIM IS ENGINEERED AGAINST THIS SPECIFIC FOOTAGE — its horizon line and its brightest band. Three
   passes: top-down for the nav, bottom-up for the claim strip (the worst area, where claims fell to
   1.46:1 unprotected), and left-to-right for the headline column. It leaves the middle of the frame open
   on purpose, because nothing sits there. Swap the video and these numbers are arbitrary again. */
:where(body.v2) .hero__scrim {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        linear-gradient(180deg,
            rgb(8 9 10 / .55) 0%, rgb(8 9 10 / .22) 15%, rgb(8 9 10 / 0) 32%),
        linear-gradient(0deg,
            rgb(8 9 10 / .82) 0%, rgb(8 9 10 / .68) 13%, rgb(8 9 10 / .42) 29%,
            rgb(8 9 10 / .14) 42%, rgb(8 9 10 / 0) 54%),
        linear-gradient(90deg,
            rgb(8 9 10 / .5) 0%, rgb(8 9 10 / .24) 38%, rgb(8 9 10 / 0) 60%);
}

/* ------------------------------------------------------------------ *
 * Nav
 * ------------------------------------------------------------------ */

/* ONE column, because there is one child. The reference's three (`auto minmax(0,auto) 1fr`, becoming
   `1fr auto 1fr` above 861px to centre the links between the mark and a spacer) existed to balance a logo
   this port does not render -- see the note below. Left three-column, the links would sit in a narrow
   `auto` track with two empty ones beside them, and `.hero__links` scrolls horizontally, so the squeeze
   would show up as a scrollbar rather than as an obvious mistake.

   Start-aligned rather than centred: the title and lede below are start-aligned, and an anchor list is
   sub-navigation for the page it is on, not a masthead. */
:where(body.v2) .hero__nav {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: center;
    gap: clamp(1rem, 3vw, 3rem);
    padding-block: clamp(1.75rem, 8.5vh, 5.75rem) clamp(1rem, 2vh, 1.5rem);
}

/* .hero__logo IS GONE, and this note is here so nobody re-adds it from the reference.
   The reference's hero nav is a one-page site's header: mark on the left, anchors on the right. This app
   renders partials/site-header on every marketing page, so a mark here would be the second one on the
   document -- and the topbar's is the one that also carries the menu, the theme toggle and the auth
   links. The anchors survive as .hero__links, which duplicate nothing. */

:where(body.v2) .hero__links {
    display: flex;
    align-items: center;
    gap: clamp(.9rem, 1.7vw, 1.75rem);
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
:where(body.v2) .hero__links::-webkit-scrollbar { display: none; }

:where(body.v2) .hero__links a {
    color: var(--text);
    font-size: clamp(.8125rem, 1.2vw, 1.4375rem);
    font-weight: 500;
    letter-spacing: -.005em;
    white-space: nowrap;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease);
}
:where(body.v2) .hero__links a:hover { color: var(--accent); }
:where(body.v2) .hero__links a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: var(--r-xs);
}

/* ------------------------------------------------------------------ *
 * Headline + lede + actions
 * ------------------------------------------------------------------ */

:where(body.v2) .hero__content {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, clamp(18rem, 24.5vw, 30rem));
    column-gap: clamp(1.5rem, 4vw, 5rem);
    align-content: center;
    align-items: start;
    padding-block: clamp(1.5rem, 4vh, 3rem);
}

:where(body.v2) .hero__title {
    grid-column: 1;
    margin: clamp(.5rem, 6.5vh, 5.5rem) 0 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 5.2vw, 6.75rem);
    font-weight: 700;
    line-height: 1.28;
    letter-spacing: -.02em;
}

:where(body.v2) .hero__arrow {
    display: inline-block;
    width: 1.07em;
    height: 1.18em;
    vertical-align: -.2em;
    margin-inline-start: .05em;
}

:where(body.v2) .hero__lede {
    grid-column: 2;
    margin: 0;
    color: var(--text);
    font-size: clamp(.9375rem, 1.05vw, 1.3125rem);
    font-weight: 400;
    line-height: 1.52;
}

/* Departure 1. Present at every width, not just below 861px. */
:where(body.v2) .hero__actions {
    grid-column: 1;
    display: flex;
    flex-wrap: wrap;
    gap: .65rem;
    margin-block-start: clamp(1.25rem, 2.6vw, 2.25rem);
}

:where(body.v2) .hero__cta {
    display: grid;
    place-items: center;
    min-height: 3.25rem;
    padding-inline: 1.5rem;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--on-accent);
    font-size: 1rem;
    font-weight: 700;
    text-decoration: none;
    transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
:where(body.v2) .hero__cta:hover { background: var(--accent-focus); color: var(--on-accent); }
:where(body.v2) .hero__cta:active { transform: scale(.95); }

/* A fill-less control's border is the ONLY thing identifying it as a control (WCAG 1.4.11), so it is a
   real boundary rather than `transparent` — a defect this codebase has already had reported once. */
:where(body.v2) .hero__cta--ghost {
    border-color: var(--field-border);
    background: rgb(8 9 10 / .4);
    color: var(--text);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
:where(body.v2) .hero__cta--ghost:hover { background: rgb(8 9 10 / .62); color: var(--text); }

:where(body.v2) .hero__cta:focus-visible { outline: none; box-shadow: var(--ring); }

/* ------------------------------------------------------------------ *
 * Bottom claim strip
 * ------------------------------------------------------------------ */

/* DEPARTURE 5 — the strip carries FOUR guarantees, not two.
   The comp is claim | tagline | claim, which is elegant and holds exactly two. This product's home page
   is a CMS surface with origin.spec_1..spec_4, and two of the four would have had nowhere to render:
   HomeContentTest asserts bidirectional parity between the keys the Blade reads and the catalog, so a
   key with no slot is a failing test — and, more to the point, "scores are computed from recorded
   results, never from screenshots" is a load-bearing product claim to drop for a composition.
   The silhouette is preserved instead of the count: still a left block, the tagline, a right block —
   each side now stacks two short lines rather than one. */
:where(body.v2) .hero__strip {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: end;
    column-gap: clamp(1rem, 3vw, 3rem);
    padding-block-end: clamp(2.5rem, 19vh, 14rem);
}

:where(body.v2) .hero__claims {
    display: grid;
    gap: clamp(.35rem, .8vw, .7rem);
    margin: 0;
    padding: 0;
    list-style: none;
}
:where(body.v2) .hero__claims--end { justify-items: end; text-align: right; }

:where(body.v2) .hero__claim {
    margin: 0;
    color: var(--text);
    font-size: clamp(.875rem, 1.2vw, 1.4375rem);
    font-weight: 400;
    line-height: 1.45;
    max-width: 30ch;
}
:where(body.v2) .hero__claim--end { text-align: right; }

:where(body.v2) .hero__tagline {
    margin: 0;
    color: var(--text);
    font-size: clamp(1rem, 1.8vw, 2.25rem);
    font-weight: 400;
    letter-spacing: .16em;
    line-height: 1.2;
    white-space: nowrap;
}

/* ------------------------------------------------------------------ *
 * Departure 2 — the pause control (WCAG 2.2.2)
 *
 * Deliberately a real <button> in the document rather than a hover-revealed affordance: moving content
 * has to be pausable by keyboard, and a control that appears on hover is not reachable by one. It sits
 * in the bottom-left corner of the hero, out of the claim strip's way, and its accessible name changes
 * with state so a screen reader is told what pressing it will do.
 * ------------------------------------------------------------------ */

:where(body.v2) .hero__pause {
    position: absolute;
    left: var(--v2-hero-gutter);
    bottom: clamp(.75rem, 2vh, 1.5rem);
    z-index: 2;
    display: inline-grid;
    place-items: center;
    width: 44px;                    /* the smallest comfortable touch target, used rather than assumed */
    height: 44px;
    padding: 0;
    border: 1px solid var(--field-border);
    border-radius: var(--r-pill);
    background: rgb(8 9 10 / .55);
    color: var(--text);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    cursor: pointer;
}
:where(body.v2) .hero__pause:hover { background: rgb(8 9 10 / .78); color: var(--text); }
:where(body.v2) .hero__pause:focus-visible { outline: none; box-shadow: var(--ring); }
:where(body.v2) .hero__pause svg { width: 15px; height: 15px; display: block; fill: currentColor; }
/* one button, two glyphs — the state is what is showing, so it cannot disagree with the video */
:where(body.v2) .hero__pause .i-play { display: none; }
:where(body.v2) .hero__pause[data-paused="true"] .i-play { display: block; }
:where(body.v2) .hero__pause[data-paused="true"] .i-pause { display: none; }

/* ------------------------------------------------------------------ *
 * Reduced motion — the video holds on its poster frame and the control says so
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
    :where(body.v2) .hero__cta { transition: none; }
    :where(body.v2) .hero__cta:active { transform: none; }
    :where(body.v2) .hero__links a { transition: none; }
}

/* ------------------------------------------------------------------ *
 * Phone
 * ------------------------------------------------------------------ */

@media (max-width: 860px) {
    :where(body.v2) .hero {
        padding-inline:
            calc(var(--v2-hero-gutter) + env(safe-area-inset-left, 0px))
            calc(var(--v2-hero-gutter) + env(safe-area-inset-right, 0px));
    }

    :where(body.v2) .hero__nav {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
    }

    /* Stacked on a phone the copy runs from the nav down past mid-frame, and the footage is brightest
       exactly there — the reference measured the lede at 2.65:1 against the brightest 5% of pixels
       behind it. The top-down pass therefore holds cover through ~58% instead of fading out at 32%. */
    :where(body.v2) .hero__scrim {
        background:
            linear-gradient(to bottom,
                rgb(8 9 10 / .8) 0%, rgb(8 9 10 / .62) 22%, rgb(8 9 10 / .52) 42%,
                rgb(8 9 10 / .3) 58%, rgb(8 9 10 / .08) 70%, rgb(8 9 10 / 0) 80%),
            linear-gradient(to top,
                rgb(8 9 10 / .9) 0%, rgb(8 9 10 / .8) 18%, rgb(8 9 10 / .44) 34%,
                rgb(8 9 10 / .12) 48%, rgb(8 9 10 / 0) 60%);
    }

    :where(body.v2) .hero__content { grid-template-columns: minmax(0, 1fr); row-gap: 1rem; }
    :where(body.v2) .hero__title { grid-column: 1; line-height: 1.12; }
    :where(body.v2) .hero__lede { grid-column: 1; font-size: clamp(.9375rem, 3.9vw, 1.0625rem); line-height: 1.55; }

    /* the arrow was sized to a 1.28 line box and pushes the next line down at 1.12 */
    :where(body.v2) .hero__arrow { width: .92em; height: 1em; vertical-align: -.12em; }

    :where(body.v2) .hero__cta { flex: 1 1 11rem; }

    /* two short facts read as a pair, not as four stacked lines */
    :where(body.v2) .hero__strip {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        column-gap: 1.25rem;
        row-gap: .85rem;
        align-items: start;
        padding-block-end: calc(clamp(1.75rem, 7vh, 4rem) + env(safe-area-inset-bottom, 0px) + 3.5rem);
    }
    :where(body.v2) .hero__tagline { grid-column: 1 / -1; font-size: clamp(.9375rem, 4.2vw, 1.25rem); }
    /* the two stacked blocks become the two columns, so the four guarantees read as a 2x2 */
    :where(body.v2) .hero__claims--end { justify-items: start; text-align: left; }
    :where(body.v2) .hero__claim--end { text-align: left; }
}

/* Landscape phone — a 100svh hero is ~380px here, so it stops being a full screen and the strip has to
   give up its generous bottom padding or the headline is pushed off. */
@media (max-height: 520px) and (orientation: landscape) {
    :where(body.v2) .hero { min-height: auto; padding-block-end: 2rem; }
    :where(body.v2) .hero__nav { padding-block: 1rem .5rem; }
    :where(body.v2) .hero__title { font-size: clamp(1.75rem, 7vh, 3rem); }
    :where(body.v2) .hero__strip { padding-block-end: 1rem; }
}

/* ===========================================================================================================
   THE PERMANENTLY-DARK SCOPE.

   >>> AT STAGE-2 MERGE, DELETE THIS ENTIRE BLOCK and instead append `.hero` to the canonical selector list
   >>> in public/ft-ui.css (currently `.band--dark, .band--dark-2, .band--dark-3, .band--void, .auth-aside`).
   >>> It is duplicated here only because staging must not edit that file. Two lists that disagree is exactly
   >>> how `.auth-aside` came to render the light palette on a dark tile — the codebase records that failure.

   The second half is not optional and is the part that is easy to get wrong: a custom property is
   substituted WHERE IT IS DECLARED, so a var()-derived token keeps its :root value in here even though this
   rule overrides the very inputs it was built from. Each one is therefore RE-DECLARED as its formula.
   =========================================================================================================== */
:where(body.v2) .hero {
    --text: var(--on-dark); --text-2: var(--on-dark-2); --text-muted: #a1a1a6; --text-faint: #a1a1a6;
    --accent: #C7EA46; --accent-text: #C7EA46; --accent-focus: #DCF57E; --on-accent: #14210A;
    --hairline: #3a3a3c; --meter-track: #3a3a3c; --field-border: #858589;
    --up: #30d158; --down: #ff6961;
    --divider-soft: #2a2a2c;
    /* recomputed from this scope's own inputs */
    --ring: 0 0 0 3px var(--accent-focus);
    --info: var(--accent);
    --chart-verified: var(--accent);
    --chart-grid: var(--hairline);
}

/* ---------------- design-v2: s20-passport.css ---------------- */
/*! design v2 — TRADER PASSPORT (merged)
    ===========================================================================================================
    The signature figure of the whole design, and the only LIGHT surface in a reference that has no light
    theme at all. The reference argues for it in one line and the argument is good:

        "The only light surface on the site, and that is the argument: a passport is a physical thing you
         hold, everything around it here is screen."

    Everything on this page is a claim the product actually makes, which is why none of it is ornament that
    could be lifted off. The reference's own header note lists the mapping:

        portrait          the holder's settled equity record, not a photo
        date of expiry    "None", because there is nothing to renew
        stamps            one per broker connected; the page survives them
        machine-readable  the score is computed, never typed

    All four are true of this product, and each was checked rather than assumed. `trader_passports` is
    created with the account, its identity columns are immutable and the row is non-deletable — both
    enforced by a Postgres trigger, not by convention. `connected_brokers` is a separate mutable table, so
    connecting a broker genuinely does leave the page untouched. And the two broker stamps read
    "Copy-enabled" and "Read-only", which is exactly this product's own split: a follower connects a
    tradeable account, a strategist a read-only track-record one.

    The sample scores are self-consistent with the rule the ceiling section publishes, which is worth
    keeping that way: 0.6(79) + 0.4(88) = 82.6, and the ceiling I + 15 = 103 does not bind, so T = 83. An
    illustrative number that contradicted the published formula on the same page would be worse than no
    number at all.

    ---------------------------------------------------------------------------------------------------------
    THE ONE DECISION THIS SECTION HAD TO MAKE: DOES THE DOCUMENT THEME?
    ---------------------------------------------------------------------------------------------------------
    No. The SECTION themes; the DOCUMENT does not. Every --v2-pp-* token below is declared in both blocks
    with the same value, and that repetition is the point rather than an oversight: if a later edit changes
    one theme and not the other, the diff shows a document that is two different documents.

    The reasoning, in the order it was arrived at:

    1. The reference's argument is about the ARTEFACT, not about the page around it. A passport is paper
       because it is a thing you hold. That survives a theme switch; "it is the only light thing on a dark
       site" does not.

    2. THE PORT ALREADY MADE THIS DECISION ONCE. 00-tokens.css keeps --v2-wash-plate and --v2-on-wash
       IDENTICAL in both themes — a designed lime plate carrying ink, unchanged by the page it sits on —
       and says why: "the lime is the lime in both themes". The passport's header band is the same object.
       So the band takes the wash's own lime (#c9e92c, its top stop) and NOT --accent. Two limes exist in
       this port, deliberately: --accent for controls, the wash lime for designed lime plates. This adds no
       third one. A band that inverted with the theme would turn the document into chrome.

    3. THE ALTERNATIVE — keep the SECTION dark in both themes so the paper always blazes — is the exact
       defect this codebase has already found and fixed once: full-bleed near-black sections on a
       light-default page, measured at 2,188px of a 6,900px page. It is not available.

    ---------------------------------------------------------------------------------------------------------
    WHAT THAT COSTS ON LIGHT, MEASURED, AND WHAT PAYS FOR IT
    ---------------------------------------------------------------------------------------------------------
    Against the dark section the paper reads at 16.04:1 — the reference's drama, intact. Against the light
    section it reads at 1.13:1. There is no ground that fixes that, because the page is already an off-white
    and the paper is an off-white. Every candidate was measured and every one made it WORSE:

        paper #e8eae1 on --canvas          #F6F7F3   1.13:1     <- best available, and what ships
        paper on --canvas-alt              #EDEEE7   1.04:1
        paper on --chamber                 #ecede7   1.03:1
        paper on accent-tinted canvas 16%  #DBE3CF   1.09:1     <- and it breaks the section copy, below

    The lime tint is the interesting failure. It looks like the obvious move — the guarantees section uses
    exactly that recipe where its artwork is switched off — and it is wrong here twice over. The paper IS a
    lime-tinted off-white, so tinting the ground moves it TOWARD the paper rather than away; and at 16% the
    section's own --text-muted falls to 4.08:1 and --accent-text to 4.10:1, both under AA. A ground that
    hides its subject and fails its copy is not a ground.

    So the document's edge on light is not carried by tone at all. It is carried by --shadow-artifact, and
    that is not a liberty — it is the token's stated purpose. ft-ui.css: "The one shadow. Reserved for the
    figure that carries the claim — a Trust Score, A PASSPORT CREDENTIAL, a headline P&L. If it is used
    anywhere else, that is a bug." This is that figure. The reference's hand-rolled
    `0 32px 64px -40px rgb(0 0 0/.85)` is therefore replaced by the system token, which already carries a
    per-theme value (.16 alpha on light, .62 on dark) so the shadow deepens with the ground beneath it.

    THE SECTION PAINTS ITS OWN OPAQUE GROUND, AND THAT IS A CORRECTNESS FIX RATHER THAN A STYLE CHOICE.
    ft-ui.css's ambient field composites to as dark as #e8ece2 on light in its worst case, which is 1.01:1
    against the paper — so a section that let the page ground show through would have the document LOCALLY
    DISAPPEAR wherever a field pool happened to land. `background: var(--v2-frame)` covers the field, which
    makes the 1.13:1 above a floor rather than a hope.

    ---------------------------------------------------------------------------------------------------------
    WHAT WAS RE-MEASURED, AND WHERE THE REFERENCE'S OWN FIGURES WERE OPTIMISTIC
    ---------------------------------------------------------------------------------------------------------
    The reference annotates two of its ink weights. Both are close and both are slightly generous, so they
    are corrected here rather than copied — an inherited number nobody re-checked is worse than no number:

        --pp-ink-2   claimed "6.6:1 on the paper"    measured 6.32:1
        --pp-ink-3   claimed "5.0:1"                 measured 4.82:1
        lime as text on the paper, claimed 1.29:1    measured 1.23:1 for the reference's own #c4e01a, and
                                                     1.14:1 for the wash lime this port uses

    Both inks still clear AA, so both are kept at the reference's alphas. The lime figure moves the wrong
    way, which only strengthens the constraint the reference set itself and this port keeps: LIME IS NEVER
    TEXT ON THE PAPER. It is the header band (ink on lime, 14.08:1), the engraved security print, and the
    highlighter under the one field the section is arguing about. Nothing else.

    STAGE-2 NOTE: s00-shared.css now aliases `.pass__kicker` onto a general `.v2-kicker` treatment. This
    file keeps its own copy of that rule because the harness page loads three stylesheets and no more; at
    the merge, delete the copy here and keep the shared one.
    =========================================================================================================== */


/* =========================================================================================================
   THE DOCUMENT PALETTE.

   Eight tokens, declared twice, with identical values both times — see decision 1 above. They are
   --v2-pp-* rather than reused product tokens because none of the product's tokens mean "paper":
   --surface is the page's own material and it inverts, and this must not.

   --v2-pp-security is composed from the two tokens above it. That is normally the trap ft-ui.css documents
   at length (a var() reference is substituted WHERE THE TOKEN IS DECLARED, so a derived token keeps its
   :root value inside a scope that overrides its inputs). It is safe here only because its inputs are the
   same in both themes, and it is re-declared below as its formula anyway so nobody has to work that out to
   be sure of it.
   ========================================================================================================= */
:root {
    --v2-pp-paper:    #e8eae1;
    --v2-pp-paper-2:  #dcdfd2;                 /* the photo plate and the machine-readable strip */
    --v2-pp-ink:      #0b0d0e;                 /* 16.03:1 on the paper */
    --v2-pp-ink-2:    rgb(11 13 14 / .68);     /*  6.32:1 — labels, band meta, leader dots */
    --v2-pp-ink-3:    rgb(11 13 14 / .60);     /*  4.82:1 — notes and the /100 denominator */
    --v2-pp-line:     rgb(11 13 14 / .15);     /* document rules, and the paper's own edge */
    --v2-pp-lime:     #c9e92c;                 /* the ceiling wash's own top stop — see decision 2 */
    --v2-pp-security: color-mix(in oklab, var(--v2-pp-lime) 58%, var(--v2-pp-ink));
}

:root[data-theme="dark"] {
    --v2-pp-paper:    #e8eae1;
    --v2-pp-paper-2:  #dcdfd2;
    --v2-pp-ink:      #0b0d0e;
    --v2-pp-ink-2:    rgb(11 13 14 / .68);
    --v2-pp-ink-3:    rgb(11 13 14 / .60);
    --v2-pp-line:     rgb(11 13 14 / .15);
    --v2-pp-lime:     #c9e92c;
    --v2-pp-security: color-mix(in oklab, var(--v2-pp-lime) 58%, var(--v2-pp-ink));
}


/* =========================================================================================================
   THE SECTION — this half DOES theme, and takes the product's ordinary text tokens to do it.

   The reference paints white and three alphas of white on near-black. Every one of those becomes the token
   that already means the same thing, exactly as the guarantees section does: 15.64:1 body / 5.00:1 muted
   on light, and on dark the same copy on the same near-black the reference drew.
   ========================================================================================================= */

:where(body.v2) .pass {
    padding: clamp(2.5rem, 6vw, 7rem) clamp(1rem, 3vw, 4rem);
    /* opaque, not transparent — see the ambient-field note in the header. */
    background: var(--v2-frame);
}

/* The same chip + headtext rig as the guarantees section, and deliberately NOT right-aligned like that
   one: the reference styles no alignment here, so the head reads down the left edge with the document. */
:where(body.v2) .pass__head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: clamp(1.25rem, 4vw, 5rem);
    align-items: start;
}

:where(body.v2) .pass__kicker {
    margin: 0;
    padding: .5em .8em;
    border: 1px solid var(--field-border);    /* 3.37:1 light / 5.30:1 dark — WCAG 1.4.11 asks 3:1 */
    border-radius: var(--r-sm);
    color: var(--text-muted);
    font-size: clamp(.6875rem, .82vw, .875rem);
    letter-spacing: .08em;
    line-height: 1.3;
    text-transform: uppercase;
}

/* --font-display, not the inherited body stack: ft-ui.css ships no webfont and sets the system face on
   <body>, so a display heading that does not ask for Figtree silently does not get it. */
:where(body.v2) .pass__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.625rem, 3.2vw, 3.75rem);
    font-weight: 700;
    line-height: 1.14;
    letter-spacing: -.02em;
}

:where(body.v2) .pass__lede {
    margin: clamp(.6rem, 1.1vw, 1.1rem) 0 0;
    max-width: 60ch;
    color: var(--text-muted);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    line-height: 1.6;
}

:where(body.v2) .pass__figure { margin: clamp(1.75rem, 4vw, 3.25rem) 0 0; }

:where(body.v2) .pass__caption {
    margin: clamp(.9rem, 1.6vw, 1.35rem) 0 0;
    max-width: 64ch;
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, .9375rem);
    line-height: 1.55;
}


/* =========================================================================================================
   THE PAGE ITSELF.

   THE DOCUMENT IS ITS OWN CONTAINER, and the reference got this right in the place the ceiling section got
   it wrong. `container-type: inline-size` means the MRZ, the trust figure and the seal are sized from the
   CARD's width (cqw) rather than the viewport's — so nothing here can slide the way the ceiling's formula
   slid from 62% of its gradient to 90% once the copy wrapped. Preserved exactly.
   ========================================================================================================= */

:where(body.v2) .pp {
    position: relative;
    isolation: isolate;               /* keeps the z-index:-1 lathe behind the content but ON the paper */
    container-type: inline-size;
    display: grid;
    grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
    column-gap: clamp(1.25rem, 3.4vw, 3.25rem);
    overflow: hidden;                 /* the security print bleeds off the edge, as real plate work does */
    width: min(100%, 60rem);
    border: 1px solid var(--v2-pp-line);
    border-radius: clamp(.5rem, .9vw, .875rem);
    background: var(--v2-pp-paper);
    color: var(--v2-pp-ink);
    /* THE ONE SHADOW, on the one figure it is reserved for. The reference carries a hand-rolled
       `0 1px 0 rgb(255 255 255/.14) inset` above its drop shadow to catch a highlight on the top edge; it
       was doing the job of a border, which the document now has for real — on light, tone alone separates
       the paper from the page at only 1.13:1, so the edge has to be drawn rather than implied. */
    box-shadow: var(--shadow-artifact);
    /* --pp-pad is a local layout parameter, not a design token: it is this document's gutter, and every
       band, row and strip is padded to it so the page reads as one printed grid. */
    --pp-pad: clamp(1.1rem, 2.6vw, 2.5rem);
}

/* Rose-engine lathe work: two vectors of different speed traced against each other, the way a rose engine
   cuts guilloche into a banknote plate. Fourteen rotated phases of one polyline, bleeding off the corner
   the way security print does — which is what the overflow: hidden above is for. */
:where(body.v2) .pp__lathe {
    position: absolute;
    z-index: -1;
    right: -7rem;
    bottom: -8rem;
    width: clamp(20rem, 44cqw, 30rem);
    height: auto;
    opacity: .3;
}

:where(body.v2) .pp__label {
    margin: 0;
    color: var(--v2-pp-ink-2);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .78vw, .75rem);
    font-weight: 500;
    letter-spacing: .14em;
    line-height: 1.25;
    text-transform: uppercase;
}

/* --- header band ------------------------------------------------------------------------------------ */

:where(body.v2) .pp__band {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: clamp(.7rem, 1.6vw, 1.15rem);
    padding: clamp(.65rem, 1.3vw, 1rem) var(--pp-pad);
    background: var(--v2-pp-lime);
}

/* THE MARK IS AN INLAY, NOT A STICKER — the tile drops to paper so the monogram reads as printed into the
   band rather than pasted onto it. The reference does this by re-declaring --ft-lime on the element; here
   the fill and stroke are set as CSS PROPERTIES instead, which beat the SVG's presentation attributes. The
   pasted mark still carries `fill="var(--ft-lime)"` from the reference's global palette, and this port
   deliberately did not adopt those names (00-tokens.css says why) — painting from the document's own
   tokens means nothing here depends on a token that does not exist. */
:where(body.v2) .pp__mark {
    flex: none;
    width: clamp(1.5rem, 2.2vw, 1.95rem);
    height: auto;
    border-radius: .4rem;
}
:where(body.v2) .pp__mark rect { fill: var(--v2-pp-paper); }
:where(body.v2) .pp__mark path { stroke: var(--v2-pp-ink); }

:where(body.v2) .pp__wordmark {
    display: grid;
    gap: .25em;
    margin: 0;
    color: var(--v2-pp-ink);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .82vw, .8125rem);
    font-weight: 600;
    letter-spacing: .22em;
    line-height: 1;
    text-transform: uppercase;
}

:where(body.v2) .pp__wordmark span {
    font-weight: 400;
    letter-spacing: .15em;
    opacity: .72;
}

:where(body.v2) .pp__band-meta {
    display: flex;
    gap: clamp(.9rem, 2.6vw, 2.5rem);
    margin: 0 0 0 auto;
    text-align: end;
}

/* The reference dims these with a one-off rgb(8 9 10 / .62); the document's own ink scale says the same
   thing in a value this port can point at — 5.98:1 on the band. */
:where(body.v2) .pp__band-meta dt {
    color: var(--v2-pp-ink-2);
    font-family: var(--font-mono-v2);
    font-size: clamp(.625rem, .72vw, .6875rem);
    letter-spacing: .12em;
    text-transform: uppercase;
}

:where(body.v2) .pp__band-meta dd {
    margin: .2em 0 0;
    color: var(--v2-pp-ink);
    font-size: clamp(.75rem, .9vw, .875rem);
    font-weight: 700;
    letter-spacing: .04em;
}

:where(body.v2) .pp__band-meta dd.is-num {
    font-family: var(--font-mono-v2);
    font-weight: 500;
    letter-spacing: .02em;
}

/* --- portrait: the holder's settled record stands in for the photo ----------------------------------- */

:where(body.v2) .pp__portrait {
    padding: clamp(1.1rem, 2.4vw, 1.9rem) 0 clamp(1rem, 2vw, 1.6rem) var(--pp-pad);
}

/* Fields sit at the top of the column and the score falls to the bottom of it, which is what squares the
   page off against the portrait. */
:where(body.v2) .pp__data {
    display: grid;
    grid-template-rows: auto 1fr;
    gap: clamp(1.1rem, 2.4vw, 2rem);
    padding: clamp(1.1rem, 2.4vw, 1.9rem) var(--pp-pad) clamp(1rem, 2vw, 1.6rem) 0;
}

:where(body.v2) .pp__plate {
    overflow: hidden;
    aspect-ratio: 35 / 45;                    /* the ISO passport-photo ratio */
    border: 1px solid var(--v2-pp-line);
    border-radius: .2rem;
    background: var(--v2-pp-paper-2);
}

:where(body.v2) .pp__engraving { display: block; width: 100%; height: 100%; }

:where(body.v2) .pp__plate-cap {
    display: grid;
    gap: .25rem;
    margin: .7rem 0 0;
}

:where(body.v2) .pp__plate-val {
    font-size: clamp(.8125rem, .95vw, .9375rem);
    font-weight: 600;
    letter-spacing: .045em;
    text-transform: uppercase;
}

:where(body.v2) .pp__sig { flex: none; text-align: end; }

:where(body.v2) .pp__sig-mark {
    display: block;
    width: min(100%, 10.5rem);
    height: auto;
    margin-inline-start: auto;
}

:where(body.v2) .pp__sig-label {
    padding-block-start: .45rem;
    border-block-start: 1px solid var(--v2-pp-line);
}

/* --- data fields ------------------------------------------------------------------------------------- */

:where(body.v2) .pp__fields {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(.85rem, 1.9vw, 1.5rem) clamp(.9rem, 2vw, 1.75rem);
    align-content: start;
    margin: 0;
}

/* (0,1,1) beats .pp__label's (0,1,0), so this wins the margin the same way it does in the reference.
   :where() contributes nothing, so that relationship survives the scoping intact. */
:where(body.v2) .pp__field dt { margin-block-end: .32rem; }

:where(body.v2) .pp__field dd {
    display: grid;
    justify-items: start;
    gap: .3rem;
    margin: 0;
}

:where(body.v2) .pp__value {
    font-size: clamp(.9375rem, 1.1vw, 1.0625rem);
    font-weight: 600;
    letter-spacing: .045em;
    text-transform: uppercase;
}

:where(body.v2) .pp__field--lead .pp__value {
    font-size: clamp(1.0625rem, 1.35vw, 1.375rem);
    font-weight: 700;
    letter-spacing: .055em;
}

/* The one field the whole section is about, marked the way a person marks a document. The lime is a
   HIGHLIGHTER here and never the letterform: ink over 82% lime on paper measures 15.11:1, where the same
   lime AS text on the paper would be 1.14:1. */
:where(body.v2) .pp__value.is-marked {
    box-shadow: inset 0 -.34em 0 color-mix(in oklab, var(--v2-pp-lime) 82%, transparent);
}

:where(body.v2) .pp__note {
    color: var(--v2-pp-ink-3);
    font-family: var(--font-mono-v2);
    font-size: clamp(.625rem, .72vw, .6875rem);
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* --- endorsements + score ---------------------------------------------------------------------------- */

:where(body.v2) .pp__endorse {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: clamp(1.25rem, 3vw, 2.5rem);
    padding: clamp(1.2rem, 2.4vw, 1.75rem) var(--pp-pad) clamp(1.2rem, 2.2vw, 1.6rem);
    border-block-start: 1px solid var(--v2-pp-line);
}

:where(body.v2) .pp__stamps-wrap { display: grid; gap: clamp(.7rem, 1.4vw, 1rem); }

:where(body.v2) .pp__stamps {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* --pp-rot is a per-element PARAMETER, not a design token: it is the angle this one stamp landed at, and
   the keyframe further down has to read it back or a landing stamp un-rotates itself mid-flight. */
:where(body.v2) .pp__stamp {
    display: grid;
    gap: .18em;
    place-content: center;
    padding: .5rem .65rem;
    border: 1.5px solid currentColor;
    border-radius: .15rem;
    color: var(--v2-pp-ink-2);
    font-family: var(--font-mono-v2);
    font-size: clamp(.625rem, .72vw, .6875rem);
    letter-spacing: .06em;
    line-height: 1.3;
    text-align: center;
    text-transform: uppercase;
    transform: rotate(var(--pp-rot, 0deg));
}

/* Nobody lands four stamps at the same angle with the same ink. The reference varies the ink with an oklab
   mix toward the paper; here it is the document's own ink scale — an alpha over the paper — because that
   is a value this port can actually MEASURE. .68 is 6.32:1 and .62 is 5.15:1. The reference's lightest
   stamp sits at an effective 60%, which measures 4.82:1 and passes, but only just, on 11px type — so the
   range stops short of it. The 1.5px rule is currentColor, so the same figures carry WCAG 1.4.11's 3:1 for
   the stamp's own edge. */
:where(body.v2) .pp__stamp:nth-child(1) { --pp-rot: -7deg; }
:where(body.v2) .pp__stamp:nth-child(2) { --pp-rot: 3.2deg;  color: rgb(11 13 14 / .62); }
:where(body.v2) .pp__stamp:nth-child(3) { --pp-rot: -2.4deg; }
:where(body.v2) .pp__stamp:nth-child(4) { --pp-rot: 4.6deg;  color: rgb(11 13 14 / .65); }

:where(body.v2) .pp__stamp--seal {
    width: clamp(4.75rem, 9cqw, 5.5rem);
    aspect-ratio: 1;
    padding: .35rem;
    border-radius: 50%;
    box-shadow: 0 0 0 2.5px var(--v2-pp-paper), 0 0 0 4px currentColor;
}

:where(body.v2) .pp__stamp-main { font-weight: 700; letter-spacing: .04em; }

:where(body.v2) .pp__stamp-top,
:where(body.v2) .pp__stamp-mode,
:where(body.v2) .pp__stamp-date { font-size: .9em; opacity: .85; }

:where(body.v2) .pp__trust {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: .9rem clamp(1.5rem, 4vw, 3rem);
    align-self: end;
    padding-block-start: clamp(.9rem, 1.8vw, 1.4rem);
    border-block-start: 1px solid var(--v2-pp-line);
}

:where(body.v2) .pp__trust-head { flex: none; }

/* Sized from the CARD (cqw), not the viewport — see the container note on .pp. */
:where(body.v2) .pp__trust-value {
    margin: .15rem 0 .55rem;
    font-size: clamp(2.75rem, 6.6cqw, 4.5rem);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: .88;
    letter-spacing: -.045em;
}

:where(body.v2) .pp__trust-max {
    margin-inline-start: .08em;
    color: var(--v2-pp-ink-3);
    font-size: .27em;
    font-weight: 500;
    letter-spacing: .02em;
}

:where(body.v2) .pp__split {
    display: grid;
    flex: 1 1 15rem;
    gap: .4rem;
    max-width: 24rem;
    margin: 0 0 .4rem auto;
}

:where(body.v2) .pp__split-row {
    display: flex;
    align-items: baseline;
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .78vw, .75rem);
    letter-spacing: .1em;
    text-transform: uppercase;
}

/* leader dots: the ::after overruns its own dt and gets clipped */
:where(body.v2) .pp__split-row dt {
    flex: 1 1 auto;
    overflow: hidden;
    color: var(--v2-pp-ink-2);
    white-space: nowrap;
}

:where(body.v2) .pp__split-row dt::after {
    content: "";
    display: inline-block;
    width: 100%;
    margin-inline-start: .6em;
    border-block-end: 1px dotted var(--v2-pp-ink-3);
    vertical-align: .3em;
}

:where(body.v2) .pp__split-row dd {
    flex: none;
    margin: 0 0 0 .6em;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* --- machine-readable zone --------------------------------------------------------------------------- */

/* The reference's reason for printing the strip on the SECOND paper rather than on ink: "black-on-black
   would have let the page bleed into the site background and cost the document its bottom edge; a real MRZ
   is ink on the substrate anyway." On a light page the same choice answers the mirror image of that
   problem — paper bleeding into paper — with the same value, which is a fair sign it was the right one. */
:where(body.v2) .pp__mrz {
    grid-column: 1 / -1;
    display: grid;
    gap: .3em;
    overflow: hidden;
    margin: 0;
    padding: clamp(.6rem, 1.3vw, .95rem) var(--pp-pad);
    border-block-start: 1px solid var(--v2-pp-line);
    background: var(--v2-pp-paper-2);
    color: var(--v2-pp-ink);                  /* 14.40:1 on the substrate */
    font-family: var(--font-mono-v2);
    font-size: clamp(.5625rem, 2.6cqw, 1.5rem);
    letter-spacing: .055em;
    line-height: 1.2;
    white-space: nowrap;
}


/* =========================================================================================================
   MOTION — the record draws, then the stamps land.

   KEPT EXACTLY AS THE REFERENCE BUILT IT, because it already satisfies this codebase's no-JS contract by
   construction rather than by luck. THE DOCUMENT SHIPS COMPLETE in the markup: the dash offset that hides
   the equity trace and the opacity that hides the stamps exist ONLY inside the @supports block, so a
   browser without scroll-driven animation — or a visitor who has asked for reduced motion — is served a
   finished document, and no observer has to arrive to fill one in. That is the inverted build this repo had
   to adopt for its own credential card after the obvious one shipped several screen-heights of empty
   rectangle to anyone whose JS never ran.

   `both` fill closes the other end: a section already scrolled past, or one on a page too short to scroll
   at all, is beyond the animation range and therefore lands on the `to` state.

   The keyframes are renamed off the reference's `pp-stamp`, which collided with the .pp__stamp class —
   this codebase has recorded once already that a class and a keyframe sharing a name reads as a bug — and
   prefixed, because @keyframes are global and this file merges into ft-ui.css at stage 2.
   ========================================================================================================= */

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        :where(body.v2) .pp__trace {
            stroke-dasharray: 1;              /* pathLength="1", so one dash is the whole record */
            stroke-dashoffset: 1;
            animation: v2-pp-draw 1s linear both;
            animation-timeline: view();
            animation-range: entry 14% entry 62%;
        }

        :where(body.v2) .pp__stamp {
            animation: v2-pp-land 1s linear both;
            animation-timeline: view();
        }

        /* the seal first, then the brokers in the order they were connected */
        :where(body.v2) .pp__stamp:nth-child(1) { animation-range: entry 30% entry 42%; }
        :where(body.v2) .pp__stamp:nth-child(2) { animation-range: entry 36% entry 48%; }
        :where(body.v2) .pp__stamp:nth-child(3) { animation-range: entry 42% entry 54%; }
        :where(body.v2) .pp__stamp:nth-child(4) { animation-range: entry 48% entry 60%; }
    }
}

@keyframes v2-pp-draw {
    to { stroke-dashoffset: 0; }
}

/* Reads --pp-rot back, so the stamp lands at ITS angle instead of snapping to zero. */
@keyframes v2-pp-land {
    from { opacity: 0; transform: rotate(var(--pp-rot, 0deg)) scale(1.28); }
    to   { opacity: 1; transform: rotate(var(--pp-rot, 0deg)) scale(1); }
}


/* =========================================================================================================
   RESPONSIVE — the reference's breakpoints and its reasons, in its own source order, which is load-bearing:
   .pp's columns are set at 1100 and again at 680, and the later rule has to win at the narrower width.
   ========================================================================================================= */

@media (max-width: 900px) {
    :where(body.v2) .pass__head {
        grid-template-columns: minmax(0, 1fr);
        gap: clamp(.9rem, 3vw, 1.5rem);
    }
    :where(body.v2) .pass__kicker { justify-self: start; }
}

/* The portrait column narrows before the page gives up its two columns — collapsing early left a hole the
   width of the data column beside the photo plate. */
@media (max-width: 1100px) {
    :where(body.v2) .pp { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }
}

/* Three field columns hold until "issuing authority" would no longer fit on its own line. */
@media (max-width: 820px) {
    :where(body.v2) .pp__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 680px) {
    :where(body.v2) .pp { grid-template-columns: minmax(0, 1fr); }

    /* Plate and its caption side by side, centred on each other, so the leftover width reads as margin
       rather than as a gap. */
    :where(body.v2) .pp__portrait {
        display: grid;
        grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
        gap: .9rem 1.25rem;
        align-items: center;
        padding-inline: var(--pp-pad);
        padding-block-end: 0;
    }

    :where(body.v2) .pp__data {
        padding-inline: var(--pp-pad);
        padding-block-start: clamp(1rem, 3vw, 1.4rem);
    }

    :where(body.v2) .pp__endorse { align-items: flex-end; }
}

@media (max-width: 480px) {
    :where(body.v2) .pp__band { flex-wrap: wrap; }

    :where(body.v2) .pp__band-meta {
        width: 100%;
        margin-inline-start: 0;
        text-align: start;
    }
}

@media (max-width: 380px) {
    :where(body.v2) .pp__fields { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------- design-v2: s30-steps.css ---------------- */
/*! design v2 — HOW IT WORKS (merged)
    ===========================================================================================================
    Four numbered steps — passport issued, broker connected, record accrues, score follows you. Each step is a
    card whose ENTIRE ground is a photographic 3D render: black on the left where the copy sits, the object
    bleeding off the right edge. The reference's own note on the data array says it plainly: "Each render is
    the full card background."

    THE DECISION THIS SECTION HAD TO MAKE, and it is the opposite of the one s80-clauses made:

        THE CARDS STAY DARK IN BOTH THEMES.

    That looks at first like the defect this codebase already found and fixed once ("three full-bleed
    near-black slabs across an otherwise light page, measured 2,188px of 6,900px"), and like the one
    s80-clauses avoids by switching its artwork off on light. It is not the same thing, for four reasons —
    each of which is a property of THIS section rather than a preference:

      1. THE COPY SITS ON THE ARTWORK. There is no card colour underneath it to fall back to. In clauses the
         art is a decorative mesh BEHIND cards that carry their own ground, so turning it off dims a
         background and the section still reads. Here, turning it off does not dim anything — it deletes the
         section and leaves four paragraphs.

      2. LEGIBILITY BECOMES THEME-INVARIANT, WHICH IS THE PROPERTY WE WANT. Because the copy sits on the
         render plus a scrim, and neither inverts, the contrast of every string here is IDENTICAL in light
         and dark and cannot be moved by a theme change. That is the same guarantee s40-ceiling had to
         restructure its wash to obtain; this section gets it by keeping the card dark.

      3. A SLAB IS A BAND, NOT A PICTURE. The defect named above is a full-bleed tone step running edge to
         edge, which reads as the page having changed theme underneath you. These are four bounded, rounded,
         gutter-separated cards with page ground on all four sides, each containing a photograph. A dark
         photograph on a light page is ordinary; an unexplained dark expanse is not.

      4. THE REFERENCE'S OWN SECTION GROUND *IS* THAT DEFECT, and it is the part that was dropped.
         `.steps { background: var(--ft-ink) }` is a full-bleed near-black band wrapped around near-black
         cards. On light that band is exactly the slab. It is replaced by --v2-frame (the page canvas) in
         both themes, so on light the cards float on the page and on dark they sit on it as drawn.

    `v2-steps-framed` on <body> builds the other reading — the dark render inset inside a white card with a
    mat — so the two can be looked at rather than argued about, exactly as `v2-keep-art` does for clauses.
    One of the two blocks is deleted at stage 2.

    WHERE THE COPY SITS, AND HOW DARK IT IS THERE (the question the measurement pass will ask):
        The title's box is capped at 68% of the card width and the body's at 52% (58% when stacked). The
        scrim holds at least 0.90 alpha of the render's own ink across 0-70%, so the copy column never
        depends on the artwork happening to be black underneath it. WORST CASE — the render pure white
        behind every glyph, which it is not — the composite is #1C1C1E and the three colours used measure:
            title  var(--on-dark)      #ffffff ........... 16.98:1
            body   var(--on-dark-2)    #cccccc ........... 10.57:1
            number var(--accent-text)  #C7EA46 ........... 12.36:1
        TYPICAL — on the render's actual black, and on the flat card ground the mobile copy sits on:
            20.61:1 / 12.83:1 / 15.00:1.
        All AA and AAA, in both themes, by construction rather than by tuning.

    THE REFERENCE'S SCRIM WAS RESTRUCTURED FOR THE SAME REASON THE CEILING'S WASH WAS. Its stops fall to
    0.60 at 55% and 0.12 at 74% — i.e. only ~0.27 alpha where a title is still allowed to reach — and its
    own comment concedes what that leans on: "The renders are already black behind the copy; this only
    guarantees it." That is a guarantee about an ASSET, and the asset is replaceable (these four are already
    re-encoded once, and stage 2 makes the strings staff-editable). The stops now hold the copy column flat
    and fall off only after it, so nothing about legibility depends on which pixels a future render happens
    to put behind the words.

    WHAT WAS NOT PORTED, AND WHAT WAS RENAMED:
      • `.steps` / `.step` are NOT reused. `:where(body.ly-marketing) .steps` already exists in ft-ui.css —
        the ledger's numbered-entry grammar, a different component that means something else — and its
        `.steps h3` / `.steps p` are (0,1,1), so they OUTRANK a `.step__title` / `.step__body` at (0,1,0)
        whatever the source order. Ported unrenamed, every body paragraph would silently take --text-muted
        at 16px with a 64ch cap, and the section would inherit a border-top and a counter-reset. Source
        order cannot break a specificity tie, so the block is renamed `.howto` and the card `.howstep`.
        (`.step` collides a second time, with ly-onboarding's wizard chip.)
      • `--ft-lime` for the step number is not ported; the number takes this scope's remapped --accent-text.
        On light that token is the deep lime #49760e, which measures 3.81:1 on the render's ink — under AA
        for the 18px bold it can be at the small end of its clamp. The card is a permanently-dark surface,
        so it joins the vocabulary ft-ui.css already has for one (see the token remap below).
      • `rgb(255 255 255 / .92)` for the body is not ported as an alpha. Alpha text lets the artwork through
        the glyphs themselves; --on-dark-2 is the opaque equivalent this product already ships.
      • The reference's two ≤640px blocks are MERGED. The second is the author's correction of the first
        (their comment: "`right 34%` cut the subject off against the right edge"), so the corrected values
        are the ones kept.

    ONE CLAIM WAS NARROWED — see the harness. "A track record on FinTruvix is something that happened, not
    something you typed" is unqualified, and a strategist can now upload prior history for staff approval
    (`strategy_history_imports`). That record is deliberately NOT `trade_records`, drives graphs only, and
    moves no score — so the sentence is true of the SCORED record, and it now says so.
    =========================================================================================================== */

/* ---------------------------------------------------------------------------------------------------------
   TOKENS.

   COLOUR tokens are declared in both blocks, per the rule — including the ones whose value does not change.
   That sameness is a decision and has to be visible as one: each is pinned to the ARTWORK, which has no
   light variant, so a reader looking for "what is different on dark" finds an explicit answer rather than
   an omission. SHAPE tokens are :root-only, which is how 00-tokens.css already treats --v2-r-card and its
   neighbours.

   The scrim gradients repeat the ink LITERALLY rather than as var(--v2-howto-ink). A var() inside a custom
   property is substituted where the property is DECLARED, so nesting one would freeze the scrim to whichever
   block declared it — the trap 00-tokens.css spends a paragraph on. 00-tokens.css writes its own gradient
   stops literally for the same reason.
   --------------------------------------------------------------------------------------------------------- */
:root {
    /* The render's own black. Not --void (#000000) and not --canvas: it has to MATCH the asset, because it
       is what shows wherever the image does not — behind the copy band on mobile, and for the frame or two
       before the image decodes. A seam between card and render would be visible at any size. */
    --v2-howto-ink: #030305;

    /* The card rim. Not --hairline, which inverts (#e0e0e0 light / #38383a dark) — a near-white line drawn
       around a near-black card on the light theme would be a bright halo. Its JOB differs by theme even
       though its value does not: on dark it separates a #030305 card from a #0C0D0B page; on light it only
       keeps the render's edge from meeting the page as a hard cut. */
    --v2-howto-edge: rgb(255 255 255 / .12);

    /* THE SCRIM HAS TO PROTECT THE COPY WITHOUT ERASING THE PICTURE, and the first version of this port
       lost that trade badly enough to be worth recording.

       It held 0.90 alpha out to 70% of the card, on the reasoning that a flat run across the copy column
       is safest. It is — for the copy. Measured on the rendered page, the painted ground at 50% across
       the card was rgb(3,4,5): the render was gone, and four cards whose ENTIRE point is a photograph
       were flat black rectangles. The contrast audit passed at 12.83:1 and reported light and dark as
       identical, which looked like the theme-invariance this section argues for and was actually the
       signature of there being no image left to differ.

       So a contrast harness cannot be the only check on a section whose ground is a picture: it scores
       legibility, and "delete the artwork" scores perfectly.

       These are the reference's own stops, restored. They are safe here because the copy is constrained
       to the left of the card by design -- title 68%, body 52% -- so the ramp only has to be solid where
       those reach. Re-measured after the change: copy still clears AA at every width and the render is
       visible from ~55% across. */
    --v2-howto-scrim:
        linear-gradient(90deg,
            rgb(3 3 5 / .95) 0%,
            rgb(3 3 5 / .92) 38%,
            rgb(3 3 5 / .90) 56%,
            rgb(3 3 5 / .50) 68%,
            rgb(3 3 5 / .12) 80%,
            rgb(3 3 5 / 0) 90%),
        linear-gradient(180deg, rgb(3 3 5 / .30) 0%, rgb(3 3 5 / 0) 62%);

    /* One column: the card is full-width and the body is allowed to 58%, so the flat run widens to 74%. */
    --v2-howto-scrim-stack:
        linear-gradient(90deg,
            rgb(3 3 5 / .95) 0%,
            rgb(3 3 5 / .92) 44%,
            rgb(3 3 5 / .68) 62%,
            rgb(3 3 5 / .16) 80%,
            rgb(3 3 5 / 0) 90%),
        linear-gradient(180deg, rgb(3 3 5 / .55) 0%, rgb(3 3 5 / .18) 60%, rgb(3 3 5 / 0) 88%);

    /* Phone: the art has its own band and the copy sits below it, pulled up over the last of it. The copy
       starts about 75% down the band, so the ramp is solid by 84% — the reference reaches solid only at
       100%, which left the first line of every title on roughly 0.54 alpha. Covering the bottom of the
       render is also what its author wanted: "the subject matters more than the plinth". */
    --v2-howto-scrim-band:
        linear-gradient(180deg,
            rgb(3 3 5 / 0) 40%,
            rgb(3 3 5 / .55) 66%,
            #030305 84%);

    /* shape */
    --v2-howto-r: clamp(.875rem, 1.45vw, 1.75rem);
    --v2-howto-pad: clamp(1.25rem, 2.25vw, 3.25rem);
    --v2-howto-pad-b: clamp(2rem, 4vw, 5.5rem);
    --v2-howto-mat: clamp(.5rem, .9vw, .875rem);   /* comparison modifier only */
}

:root[data-theme="dark"] {
    /* Identical, deliberately: every value here is a property of the render, and the render does not have a
       light variant. The block exists so that is stated rather than inferred from its absence. */
    --v2-howto-ink: #030305;
    --v2-howto-edge: rgb(255 255 255 / .12);
    --v2-howto-scrim:
        linear-gradient(90deg,
            rgb(3 3 5 / .95) 0%,
            rgb(3 3 5 / .92) 38%,
            rgb(3 3 5 / .90) 56%,
            rgb(3 3 5 / .50) 68%,
            rgb(3 3 5 / .12) 80%,
            rgb(3 3 5 / 0) 90%),
        linear-gradient(180deg, rgb(3 3 5 / .30) 0%, rgb(3 3 5 / 0) 62%);
    --v2-howto-scrim-stack:
        linear-gradient(90deg,
            rgb(3 3 5 / .95) 0%,
            rgb(3 3 5 / .92) 44%,
            rgb(3 3 5 / .68) 62%,
            rgb(3 3 5 / .16) 80%,
            rgb(3 3 5 / 0) 90%),
        linear-gradient(180deg, rgb(3 3 5 / .55) 0%, rgb(3 3 5 / .18) 60%, rgb(3 3 5 / 0) 88%);
    --v2-howto-scrim-band:
        linear-gradient(180deg,
            rgb(3 3 5 / 0) 40%,
            rgb(3 3 5 / .55) 66%,
            #030305 84%);
}

/* ---------------------------------------------------------------------------------------------------------
   SECTION. The reference paints this --ft-ink; here it is the page ground in both themes (reason 4 in the
   header). The side padding is not decoration — on light it is the page ground showing around and between
   the cards that keeps four dark rectangles reading as media rather than as a band.
   --------------------------------------------------------------------------------------------------------- */
:where(body.v2) .howto {
    padding: clamp(2rem, 4vw, 5rem) clamp(1rem, 3vw, 4rem);
    background: var(--v2-frame);
}

:where(body.v2) .howto__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(.75rem, 2vw, 2.5rem);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* ---------------------------------------------------------------------------------------------------------
   THE CARD — a permanently-dark surface, so it joins the vocabulary this product already has for one.

   The remap below is ft-ui.css's `.band--dark, .auth-aside` block (line 623) reduced to what this section
   consumes. Two entries in it are not optional:

     • --ring IS RE-DECLARED AS ITS FORMULA. It is `0 0 0 3px var(--accent-focus)` on :root, so overriding
       --accent-focus alone leaves --ring holding the :root-resolved deep green — the exact bug ft-ui.css
       records ("overriding --accent alone left --ring, --info and --chart-verified light"). Nothing in this
       section is focusable today; it is here so that the day a card becomes a link, the scope is already
       whole rather than one declaration short.
     • --hairline is remapped even though the card's own border uses --v2-howto-edge, so that anything
       stage 2 drops inside a card draws its rules on the render rather than in near-white.

   The literals in it are the only ones in this file outside a token declaration, and they are unavoidable:
   a light-theme page holds no token containing the dark accent or the dark muted grey — those values exist
   only inside `:root[data-theme="dark"]`, which is not in scope here. They are copied from ft-ui.css:623
   rather than chosen, so a permanently-dark surface reads identically wherever the product draws one.

   Surfaces (--surface*, --glass*) are deliberately NOT remapped: this section paints no panel inside a
   card, and a half-copied glass palette is worse than none.
   --------------------------------------------------------------------------------------------------------- */
:where(body.v2) .howstep {
    --text: var(--on-dark);
    --text-2: var(--on-dark-2);
    --text-muted: #a1a1a6;
    --text-faint: #a1a1a6;
    --accent: #C7EA46;
    --accent-text: #C7EA46;
    --accent-focus: #DCF57E;
    --on-accent: #14210A;
    --hairline: rgb(255 255 255 / .12);
    --field-border: #858589;
    --ring: 0 0 0 3px var(--accent-focus);

    position: relative;
    /* LOAD-BEARING, and not left over from the blend-mode work README finding 5 describes. The media is a
       z-index:-1 child; without a stacking context here it paints behind this element's own background and
       disappears. Deleting this line renders four flat black cards. */
    isolation: isolate;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;

    /* The ratio is the drawn proportion, not a promise about the content. At stage 2 these strings become
       home_content keys, and a staff edit two lines longer would be cut off silently by the overflow above.
       min-height lets the card grow instead, and the grid row takes its sibling with it so the pair stays
       level. (A grid item's automatic minimum size does much of this already — declaring it means the
       guarantee does not depend on a reader remembering that it does.) */
    aspect-ratio: 16 / 8.5;
    min-height: fit-content;

    padding: var(--v2-howto-pad) var(--v2-howto-pad) var(--v2-howto-pad-b);
    border: 1px solid var(--v2-howto-edge);
    border-radius: var(--v2-howto-r);
    background: var(--v2-howto-ink);
}

:where(body.v2) .howstep__media {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}

/* next/image's `fill` supplied position/inset/width/height; a plain <img> does not, and without them the
   art lays out at its intrinsic 1620px and pushes the card open. Same family of breakage as the font tokens
   in README finding 1: what the framework was contributing is invisible until it is gone. */
:where(body.v2) .howstep__art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: right center;
}

:where(body.v2) .howstep__scrim {
    position: absolute;
    inset: 0;
    background: var(--v2-howto-scrim);
}

:where(body.v2) .howstep__title {
    margin: 0;
    max-width: 52%;   /* was 68% -- see the scrim note: the copy is pulled back so the ramp can be */
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.125rem, 2.05vw, 2.5rem);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -.015em;
    text-wrap: balance;
}

/* The visible number is content — it is what the comp counts with — but it is aria-hidden in the markup and
   the position is carried by the <ol> instead, the same split .clause__num takes. Reading "zero one" into
   the middle of a heading is noise a list already conveys. */
:where(body.v2) .howstep__number {
    margin-inline-end: .42em;
    color: var(--accent-text);
    font-variant-numeric: tabular-nums;
}

:where(body.v2) .howstep__body {
    margin: clamp(.75rem, 1.6vw, 2rem) 0 0;
    max-width: 52%;
    color: var(--text-2);
    font-size: clamp(.8125rem, 1.02vw, 1.25rem);
    font-weight: 400;
    line-height: 1.5;
}

/* ------------------------------------------------------------------ *
 * One column. The card is now full width, so the copy is given more of
 * it and the scrim's flat run is widened to match.
 * ------------------------------------------------------------------ */
@media (max-width: 1023px) {
    :where(body.v2) .howto__grid { grid-template-columns: minmax(0, 1fr); }
    :where(body.v2) .howstep { aspect-ratio: 16 / 9; }
    :where(body.v2) .howstep__body { max-width: 58%; }
    :where(body.v2) .howstep__scrim { background: var(--v2-howto-scrim-stack); }
}

/* ------------------------------------------------------------------ *
 * Phone. The art gets its own band and the copy moves below it, so the
 * copy stops sitting on the render at all — the one width where the
 * two-column composition genuinely cannot survive.
 * ------------------------------------------------------------------ */
@media (max-width: 640px) {
    :where(body.v2) .howto { padding-inline: clamp(1rem, 4vw, 1.5rem); }

    :where(body.v2) .howstep {
        display: block;
        aspect-ratio: auto;
        padding: 0;
    }

    :where(body.v2) .howstep__media {
        position: relative;
        inset: auto;
        z-index: 0;
        aspect-ratio: 16 / 9;
        width: 100%;
    }

    /* biased up and back toward the middle — the reference's own second-pass value */
    :where(body.v2) .howstep__art { object-position: 68% 34%; }

    :where(body.v2) .howstep__scrim { background: var(--v2-howto-scrim-band); }

    /* positioned, so the copy paints over the positioned media it is pulled up into. The pull is a
       percentage of the card WIDTH, which is why it tracks the band's height instead of drifting as the
       text reflows. */
    :where(body.v2) .howstep__copy {
        position: relative;
        margin-top: -14%;
        padding-inline: clamp(1.25rem, 5vw, 1.75rem);
        padding-block-end: clamp(1.5rem, 6vw, 2rem);
    }

    :where(body.v2) .howstep__title {
        max-width: 100%;
        font-size: clamp(1.125rem, 5vw, 1.5rem);
    }

    :where(body.v2) .howstep__body {
        max-width: 100%;
        font-size: clamp(.875rem, 3.7vw, 1rem);
        line-height: 1.55;
    }
}

/* ------------------------------------------------------------------ *
 * COMPARISON MODIFIER — the framed reading on light.
 *
 * Not a shipping feature, and the counterpart of clauses' `v2-keep-art`:
 * it answers the one question the default reading leaves open, which is
 * whether four full-bleed dark cards on a light page read as media or
 * as a slab. Here the render is inset inside a white card with a mat,
 * so it is unambiguously a picture IN a card rather than the card.
 *
 * It changes nothing about the copy: the words still sit on the render
 * and on the same scrim, so every ratio quoted in the header still
 * holds and the two readings are compared on composition alone.
 *
 * Scoped to >640px. Below that the art already has its own band and the
 * copy is already off it, so there is nothing left to frame.
 *
 * Whichever wins, one of these blocks is deleted at stage 2.
 * ------------------------------------------------------------------ */
@media (min-width: 641px) {
    /* --v2-card / --v2-card-line rather than their inputs: both were substituted on :root, so they still
       hold the PAGE's white and hairline even though this element remaps --hairline for its own contents.
       The substitution trap pointing the right way, for once. */
    :root:not([data-theme="dark"]) :where(body.v2).v2-steps-framed .howstep {
        border-color: var(--v2-card-line);
        background: var(--v2-card);
    }

    :root:not([data-theme="dark"]) :where(body.v2).v2-steps-framed .howstep__media {
        inset: var(--v2-howto-mat);
        overflow: hidden;
        border-radius: calc(var(--v2-howto-r) - var(--v2-howto-mat));
    }
}

/* ---------------- design-v2: s40-ceiling.css ---------------- */
/*! design v2 — TRUST CEILING (merged)
    ===========================================================================================================
    Chosen as one of the two stage-1 sections because it is the reference's signature moment AND the section
    that decides the light-variant question: a lime wash falling into near-black is the most dark-native thing
    in the whole design. If it works on a light page, the rest will.

    THE TRANSLATION, in one line: the wash keeps its lime, and stops descending into black — on light it
    climbs back into the card. The copy on it is ink in BOTH themes, which is simultaneously the WCAG fix the
    reference names for itself and the reason the section is theme-portable at all.

    Where a value could not be tokenised (the 9-stop gradient) it is declared once in 00-tokens.css per theme
    rather than being overridden here, so a reader looking for "what is different on light" has one place.
    =========================================================================================================== */

:where(body.v2) .ceiling {
    padding: var(--v2-frame-pad);
    background: var(--v2-frame);
}

:where(body.v2) .ceiling__card {
    position: relative;
    overflow: hidden;
    padding: 0 0 var(--v2-card-pad-bottom);
    border-radius: var(--v2-r-card);
    background: var(--v2-card);
}

/* THE WASH IS SIZED BY THE COPY IT CARRIES, not by the viewport.

   In the reference the wash is an absolutely-positioned box of viewport-derived height (42vw) sitting
   behind copy of content-derived height. The two disagree as soon as the text wraps, so the ramp position
   of any given line is not something the design controls — measured, the formula slid from 62% of the ramp
   at 1440 to ~90% at 768 and fell to 1.14:1.

   Here the intro OWNS the wash: it carries the card's top and side padding, the plate is its background,
   and its bottom padding is exactly --v2-wash-fade-h, which is also the height of the fade band. Copy can
   therefore never enter the fade, at any width, in either theme. */
:where(body.v2) .ceiling__intro {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--v2-card-pad-top) var(--v2-card-pad-x) var(--v2-wash-fade-h);
}

:where(body.v2) .ceiling__wash {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        var(--v2-wash-fade) 0 100% / 100% var(--v2-wash-fade-h) no-repeat,
        var(--v2-wash-plate);
}

:where(body.v2) .ceiling__texture {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

/* THE KICKER SITS ON THE WASH TOO, and was the one element on it still taking the generic treatment.
   `.v2-kicker` is `--text-muted` ink inside a `--field-border` outline, which is correct on a page
   ground and wrong on a bright lime plate: measured on the served page it came out at 3.49:1 on light
   and 1.65:1 on dark, against the 4.5:1 an 11.6px label needs. Everything else on this plate already
   takes --v2-on-wash — the title, the body and the formula, each with a comment saying why — so this is
   the same fix applied to the element that was missed rather than a new idea.

   The border moves with it. An outline at --field-border is 1.5:1 against the wash, so the pill it is
   supposed to draw is not really there; --v2-on-wash-2 is the same ink at 76%, which keeps the outline
   subordinate to the label inside it while staying visible.

   Found by the painted-pixel audit run against the SERVED page rather than the section harness — the
   harness lays this section out on its own, where the kicker does not land on the plate. */
:where(body.v2) .ceiling .v2-kicker {
    color: var(--v2-on-wash);
    border-color: var(--v2-on-wash-2);
}

:where(body.v2) .ceiling__title {
    margin: 0;
    max-width: 20ch;
    color: var(--v2-on-wash);
    font-family: var(--font-display);
    font-size: clamp(2.25rem, 5.5vw, 7rem);
    font-weight: 700;
    line-height: 1.29;
    letter-spacing: -.02em;
}

/* #fff here is the reference's own documented AA failure (2.02:1 body / 2.75:1 formula). --v2-on-wash is
   the fix it names, applied at every width rather than only below 860px. */
:where(body.v2) .ceiling__body {
    margin: clamp(.875rem, 2vw, 2.5rem) 0 0;
    max-width: min(38rem, 31.5vw);
    color: var(--v2-on-wash);
    font-size: clamp(.875rem, 1vw, 1.1875rem);
    font-weight: 500;
    line-height: 1.4;
}

:where(body.v2) .ceiling__formula {
    margin: clamp(.75rem, 1.3vw, 1.75rem) 0 0;
    color: var(--v2-on-wash);
    font-size: clamp(1.125rem, 2.2vw, 2.75rem);
    font-weight: 600;
    letter-spacing: .01em;
    line-height: 1.2;
    text-align: right;
    white-space: nowrap;
}

/* ------------------------------------------------------------------ *
 * The proof: ceiling chart + published weights
 * ------------------------------------------------------------------ */

/* The card's side padding lives on the intro now (so the plate can bleed to the card edge), so the proof
   supplies its own. */
:where(body.v2) .proof {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(1.5rem, 4vw, 5rem);
    margin: clamp(2rem, 5vw, 6rem) var(--v2-card-pad-x) 0;
    align-items: start;
}

:where(body.v2) .proof__figure { margin: 0; }

/* --text, not --text-muted, and it is not a style preference: a legend is the KEY to the chart, so it is
   primary content rather than secondary. It is also the text most exposed to the wash's tail — measured at
   4.38:1 (light) and 4.00:1 (dark) when it was muted, both under AA, on grounds the token arithmetic
   reports as passing because the arithmetic composites the CARD and the paint is the GRADIENT. */
:where(body.v2) .proof__legend {
    display: flex;
    gap: clamp(.75rem, 1.6vw, 1.5rem);
    margin: 0 0 clamp(.6rem, 1.2vw, 1rem);
    padding: 0;
    list-style: none;
    color: var(--text);
    font-size: clamp(.6875rem, .82vw, .8125rem);
}

:where(body.v2) .proof__legend li { display: flex; align-items: center; gap: .5em; }

:where(body.v2) .proof__swatch {
    width: 1.35em; height: 2px; border-radius: 2px; display: inline-block;
}
/* The two series are separated by VALUE, not by hue — one is the subject, one is the comparison. A second
   hue here would read as a categorical palette and invite "which colour is good". */
:where(body.v2) .proof__swatch--high { background: var(--text-muted); }
:where(body.v2) .proof__swatch--low  { background: var(--accent); }

:where(body.v2) .proof__plot { position: relative; }
:where(body.v2) .proof__plot svg { width: 100%; height: auto; display: block; overflow: visible; }

:where(body.v2) .proof__grid    { stroke: var(--hairline); stroke-width: 1; }
:where(body.v2) .proof__axis    { stroke: var(--field-border); stroke-width: 1; }
:where(body.v2) .proof__ceiling { stroke: var(--text-faint); stroke-width: 1.5; stroke-dasharray: 5 5; }

:where(body.v2) .proof__line { fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
:where(body.v2) .proof__line--high { stroke: var(--text-muted); }
:where(body.v2) .proof__line--low  { stroke: var(--accent); }

/* Labels live in HTML, not in the SVG, for the reason this codebase already recorded twice: font-size
   inside an SVG is in USER UNITS and magnifies with the drawing, so an 11-unit label rendered larger than
   17px body copy once the viewBox was scaled. Positioned by percentage of the plot box. */
:where(body.v2) .proof__labels { position: absolute; inset: 0; pointer-events: none; }
:where(body.v2) .proof__labels > * {
    position: absolute;
    left: var(--x); top: var(--y);
    transform: translate(-100%, -50%);
    font-size: clamp(.625rem, .78vw, .8125rem);
    color: var(--text-muted);
    white-space: nowrap;
}
:where(body.v2) .proof__tick { transform: translate(-100%, -50%); }
:where(body.v2) .proof__label--low { color: var(--accent-text); font-weight: 600; }

:where(body.v2) .proof__axis-title,
:where(body.v2) .proof__caption {
    margin: clamp(.5rem, 1vw, .9rem) 0 0;
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, .9375rem);
    line-height: 1.5;
}

:where(body.v2) .weights__title {
    margin: 0 0 clamp(.75rem, 1.5vw, 1.25rem);
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, .9375rem);
    font-weight: 400;
    line-height: 1.5;
}

:where(body.v2) .weights__list { margin: 0; }

:where(body.v2) .weights__row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 1rem;
    padding-block: clamp(.5rem, 1vw, .8rem);
}

:where(body.v2) .weights__row dt { color: var(--text); font-size: clamp(.8125rem, 1vw, 1.0625rem); }
:where(body.v2) .weights__row dd { margin: 0; color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }

/* A component declared at 0% is listed rather than omitted — "a weight table you cannot audit is just a
   claim". It is dimmed, never hidden. */
:where(body.v2) .weights__row.is-zero dt,
:where(body.v2) .weights__row.is-zero dd { color: var(--text-muted); font-weight: 400; }

/* The rule under each row doubles as its magnitude bar, so the table needs no second column of chart. */
:where(body.v2) .weights__track {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 1px;
    background: var(--hairline);
}
:where(body.v2) .weights__fill {
    display: block;
    height: 100%;
    background: var(--accent);
    transform-origin: left center;
}

@media (max-width: 860px) {
    :where(body.v2) .ceiling__body { max-width: 46ch; }
    :where(body.v2) .ceiling__formula { text-align: left; white-space: normal; }
    :where(body.v2) .proof { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------- design-v2: s50-engine.css ---------------- */
/*! design v2 — COPY ENGINE (merged)
    ===========================================================================================================
    Headline, lede, a two-hop fan-out diagram, two latency figures on plates, and a provenance caveat. The
    whole card is a full-bleed photographic ground (engine-bg, a glow render on black) with every string
    sitting directly on it.

    THIS SECTION IS ALREADY THE PRODUCT'S OWN COPY. Every string here exists as a shipped default in
    config/home_content.php under the `execution.*` keys — heading, lede, both figures, both captions and the
    provenance note — word for word, apostrophes included. So there was nothing to invent and nothing to
    correct: the honesty pass on this section found the reference and the catalog saying the same thing.
    (The one addition is the kicker, and it is not new copy either — see THE KICKER below.)

    FOUR THINGS HAD TO BE RE-DECIDED, and three of them are corrections rather than preferences.

    1. THE LEGIBILITY STRUCTURE IS THE CEILING'S, APPLIED TWICE.

       The reference gives `.engine__scrim` NO background at desktop and only introduces one below 1023px,
       where its own comment concedes the problem: "a tall card crops the source hard, pulling the petals in
       over the copy". So above 1023px the title, the lede and — worst — an 11px caveat at 55% white sit on
       whatever the render happens to put behind them. That is the same class of defect s40-ceiling had to
       restructure its wash for: legibility depending on a coincidence of layout and artwork rather than on
       anything anyone controls.

       The fix here is the ceiling's, in the same shape: a near-flat veil sized by THE COPY IT CARRIES, plus a
       fade band of FIXED HEIGHT, with the copy's padding on that side set to the same height so no line can
       ever enter the fade. There are two of them — `.engine__intro::before` over the head, fading downward,
       and `.engine__foot::before` under the figures and caveat, fading upward.
       `.engine__scrim` survives as a mild overall settle rather than as the thing holding legibility up.

       CORRECTION, MEASURED. This comment used to end "and between them the render is left almost unveiled,
       which is where the picture is actually worth looking at and where no text is". That is not true, and
       it is worth saying why rather than deleting it, because the mistake is a natural one: it counts the
       veils this rule owns and forgets the one the diagram brings with it.

       The window between the two bands is real — 23-27% of the card at every desktop width — but `.fan`
       lays its own plate across exactly that window (`.fan::before`, --v2-fan-plate, inset -30% -14%, so it
       bleeds well past the diagram). Transmission measured with a flat white plate in the render's place,
       sampled down a column clear of the copy, at 1440px dark:

           depth        all on     bands off     scrim off      both off
             4%         0.0061        0.2542        0.0129        1.0000
            29%         0.0085        0.4851        0.0129        1.0000
            54%         0.0953        0.0953        0.1620        0.1620   <- the "open" window
            62%         0.0976        0.0976        0.1620        0.1620
            88%         0.0069        0.3613        0.0128        1.0000

       Read the last column: with EVERY veil this file owns switched off, the middle still transmits 0.162,
       because the fan plate is not one of them. So the card's best case is ~10% transmission, and
       engine-bg.webp — which decodes fine and has real range (median 0.025, p90 0.504, 43% of it above
       L=0.05) — contributes essentially nothing at any width. `ratchet-bg.webp` is the same story: open
       only in thin bands at the card's top and bottom edges at 1440, fully covered at 390.

       This is NOT presented as a bug to be quietly patched. The fan plate is deliberate and has its own
       reasoning (see `.fan::before`), the legibility it buys is real (6.19:1 worst case here), and on the
       LIGHT theme both renders are switched off by design anyway. What it means is that the two renders are
       currently a design decision nobody has actually taken: 86KB downloaded and decoded to be ~2% visible
       on one theme and invisible on the other. Three honest resolutions, in README finding 8.

       Both bands bleed to the card edges through negative insets (the card already clips), so they read as
       the render darkening rather than as a rectangle drawn on it.

    2. THE ARTWORK CANNOT SIMPLY MOVE TO A LIGHT PAGE — R7, and the same answer s80-clauses reached. It is
       glow-on-black; on light it is a dark slab, the defect this codebase already found and fixed once. So
       the art is off on light via the shared --v2-art-opacity, the card carries --v2-art-ground's lime tint
       instead, and every veil token above resolves to `none`, because on a near-white card there is nothing
       to veil. `v2-keep-art` on <body> restores the dark reading for comparison exactly as it does for
       clauses. Note what this buys: with the art off, the copy is on a flat card, so on LIGHT the contrast
       of every string here is a token calculation the existing tests can already do.

    3. THE CAVEAT IS THE LOWEST-CONTRAST TEXT ON THE REFERENCE PAGE, and it is the one string that must not
       be. It states where the two figures came from; a performance number without its provenance is not
       worth publishing, so a caveat nobody can read is worse than no figure. The reference gives it
       `rgb(255 255 255 / .55)` at 11px on the brightest part of the glow, then admits below 720px that
       "11px at 55% white is under AA on this ground" and bumps it. That correction is applied at EVERY width
       here — the same move, and for the same reason, as the ceiling's ink-at-every-width — and it is now
       --text-muted on a band held at ~0.94 combined alpha. Its floor is 13px rather than 11px; the further
       reduction to 12px the reference makes under `pointer: coarse` was NOT ported, for the same reason.

    4. THE DIAGRAM'S EDGES ARE CONTENT, SO THEY ARE HELD TO 1.4.11. The fan's static edges carry the topology
       — which broker, which follower — so they are "graphical objects required to understand the content".
       The reference's `rgb(255 255 255 / .22)` measures 2.04:1 against its own panel; --v2-fan-edge is
       raised until it clears 3:1 in both themes. Same for the broker ring.

    THE PULSES ARE THE CLAIM, AND THEY ARE KEPT AS DRAWN. The section's sentence is that orders are placed
    CONCURRENTLY, so within a hop every branch fires in LOCKSTEP — no stagger, because a relay would be a
    diagram contradicting the paragraph above it. The offset that does exist is BETWEEN the hops: the six
    branch pulses take one delay, together, timed to leave as the three trunk pulses land. Each 3s cycle is
    ~46% travel and ~54% quiet, and the quiet is deliberate: a continuous stream would read as live traffic
    and there is none behind it.

    Worth recording for stage 2: ft-ui.css ALREADY has this primitive. `.ft-send` is
    `stroke-dasharray: .12 1; stroke-dashoffset: 1.12`, built for the home page's own two-hop fan-out, and
    `@keyframes ft-send-run` travels in the first 38% and holds off for the rest so a second hop can chain
    onto it — the same mechanism, arrived at independently, down to the numbers. It is not used here because
    it is `html.js`-gated and this harness runs no JS, so it would render nothing. At stage 2 the fan should
    BE `.ft-send` rather than a second keyframe with the same job.

    WHAT WAS DELIBERATELY NOT PORTED:

      • THE SCROLL-DRIVEN REVEAL. The reference rises the title, lede and each stat on `animation-timeline:
        view()`. s40-ceiling and s80-clauses both dropped their sections' equivalents, and the reason holds
        here: this product has its own entrance toolkit — `.reveal`, `.ft-seq`, `.ft-draw`, one shared
        IntersectionObserver in partials/theme.blade.php, with `html.js` gating and a reduced-motion
        counterpart that lands finished. A section bringing a second reveal mechanism would give one page two
        entrance systems with different curves and two places to get reduced motion wrong. At stage 2 this is
        `.ft-seq` on one element in the Blade, not CSS in here.

      • THE ≤1023px SCRIM. It exists in the reference because nothing else was holding the copy up at that
        width. The two content-sized bands do it at every width now, so a width-specific one would be a
        second, disagreeing answer to a question already settled.

      • `--ft-lime` for the figures. They take --accent-text, so the number agrees with every other accent
        string in the product: deep lime #49760e by day, lemon #C7EA46 by night.

    NOTE ON THE FRAME. `.engine`'s two declarations are exactly `.v2-framed` in s00-shared.css. They are
    restated here so this file stands alone, matching s40-ceiling; adopting the shared class is a one-line
    change at stage 2 if the frame is kept.

    NOTE ON THE TOKENS BELOW. Every one is a LITERAL in both themes — none is composed from another var() on
    :root — so the substituted-where-declared trap that 00-tokens.css documents cannot reach them. Keep it
    that way, or re-declare the formula in the dark block.
    =========================================================================================================== */

/* =========================================================================================================
   TOKENS — light. Staged here rather than in 00-tokens.css (that file is not mine to edit); promote them
   when the section lands. On light the art is off, so every veil resolves to `none` and the plates become
   the product's ordinary panel-on-a-card treatment.
   ========================================================================================================= */
:root {
    /* the mild overall settle on the render, and the two content-sized bands (see finding 1) */
    --v2-engine-scrim: none;
    --v2-engine-veil: none;
    --v2-engine-fade-down: none;
    --v2-engine-fade-up: none;
    /* GEOMETRY, not palette — the same in both themes, so it is declared once. It is simultaneously the
       height of each fade band and the padding that keeps copy out of it; they must not drift apart. */
    --v2-engine-fade-h: clamp(2rem, 4vw, 3.5rem);

    /* the figure plates. Opaque on purpose: the value chip overlaps the label plate, and two translucent
       panes would double their tint at the overlap and draw a seam across the join. #EDEEE7 is light
       --canvas-alt, so promotion is a rename. */
    --v2-engine-plate: #EDEEE7;
    /* On dark the two plates are the same near-black and merge into one silhouette, which is the comp's
       reading and is kept (transparent). On light they are within 1.09:1 of the card and would not read as
       plates at all, so they take the product's own device for that — a hairline. */
    --v2-engine-plate-line: #e0e0e0;

    /* --- the fan-out diagram ------------------------------------------------------------------------- */
    /* Its own ground. On dark the diagram crosses both the render and the glow, so it needs one; on light
       there is nothing to isolate it from. */
    --v2-fan-plate: none;
    /* The topology lines. WCAG 1.4.11: these are required to understand the diagram, so 3:1 against the
       ground they sit on. Measured: 3.11:1 on the light card, 3.45:1 on the darkest fan panel. */
    --v2-fan-edge: rgb(20 28 6 / .48);
    /* Broker and follower discs. On light the disc is punched out of the card and the ring carries the
       meaning; on dark it is the reference's near-black. */
    --v2-fan-node: #ffffff;
    --v2-fan-node-line: rgb(20 28 6 / .58);
}

/* =========================================================================================================
   TOKENS — dark. The reference's own reading, plus the veils it was missing above 1023px.

   The alphas are set against the WORST pixel the render could put behind a string — a white-hot glow core,
   which it does not have — so nothing here depends on which pixels this particular asset happens to carry.
   Composited over that worst case: the head band lands the copy on L≈0.013 and the foot band on L≈0.008,
   against the L≤0.162 that --text needs and the L≤0.031 that --text-muted needs. The middle of the card is
   left at ~0.25 alpha, because no text is there and the picture is the point.
   ========================================================================================================= */
:root[data-theme="dark"] {
    --v2-engine-scrim: linear-gradient(180deg,
        rgb(4 5 6 / .50) 0%, rgb(4 5 6 / .26) 32%, rgb(4 5 6 / .22) 64%, rgb(4 5 6 / .45) 100%);
    /* near-flat on purpose: a ramp under content of unknown height is the thing finding 1 exists to remove */
    --v2-engine-veil: linear-gradient(rgb(4 5 6 / .90), rgb(4 5 6 / .90));
    /* both fades start at the veil's own alpha, so the join is seamless whatever the band height resolves to */
    --v2-engine-fade-down: linear-gradient(to bottom, rgb(4 5 6 / .90), rgb(4 5 6 / 0));
    --v2-engine-fade-up: linear-gradient(to top, rgb(4 5 6 / .90), rgb(4 5 6 / 0));
    --v2-engine-fade-h: clamp(2rem, 4vw, 3.5rem);

    --v2-engine-plate: #050507;
    --v2-engine-plate-line: transparent;

    --v2-fan-plate: rgb(4 5 6 / .88);
    --v2-fan-edge: rgb(255 255 255 / .38);
    --v2-fan-node: #0b0d0e;
    --v2-fan-node-line: rgb(255 255 255 / .40);
}

/* =========================================================================================================
   THE SECTION
   ========================================================================================================= */

/* identical to .v2-framed in s00-shared.css — see NOTE ON THE FRAME above */
:where(body.v2) .engine {
    padding: var(--v2-frame-pad);
    background: var(--v2-frame);
}

:where(body.v2) .engine__card {
    /* The card's own padding, named so the two veil bands can bleed back out to its edges by exactly the
       amount that put them inside. Geometry, identical in both themes, so it lives on the component. */
    --v2-engine-pad-x: clamp(1.5rem, 3vw, 4.25rem);
    --v2-engine-pad-t: clamp(1.5rem, 3vw, 4.25rem);
    --v2-engine-pad-b: clamp(1.75rem, 3.4vw, 4.75rem);

    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* min-height, not aspect-ratio — the card must be able to grow past the comp's proportion rather than
       clip its own content (the reference's note, and it still holds) */
    min-height: 56vw;
    padding: var(--v2-engine-pad-t) var(--v2-engine-pad-x) var(--v2-engine-pad-b);
    border: 1px solid var(--v2-card-line);
    border-radius: clamp(.875rem, 1.45vw, 1.75rem);
    background: var(--v2-card);
    /* second declaration, not folded into the shorthand: on light this is the lime tint that stands in for
       the artwork, on dark it is `none` and the render below is what shows */
    background-image: var(--v2-art-ground);
}

:where(body.v2) .engine__art {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    opacity: var(--v2-art-opacity);
}

/* Kept as an element because the reference has one, but demoted: it settles the whole render rather than
   carrying any string's legibility. That is the intro and foot bands' job now. */
:where(body.v2) .engine__scrim {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: var(--v2-engine-scrim);
}

/* ------------------------------------------------------------------ *
 * Head — kicker, headline, lede.
 *
 * THE KICKER is not in the reference's CopyEngine; two of its other sections have one, and s00-shared.css
 * makes it the general treatment so that every `*.key` in the home_content catalog keeps rendering. The
 * word here is `execution.key`'s own shipped default, so it is neither invented nor borrowed.
 * ------------------------------------------------------------------ */

/* The intro OWNS its band, the way .ceiling__intro owns the wash. Its bottom padding IS the fade height,
   so the last line of the lede can never enter the fade at any width, in either theme, by construction. */
:where(body.v2) .engine__intro {
    position: relative;
    padding-block-end: var(--v2-engine-fade-h);
}

:where(body.v2) .engine__intro::before {
    content: "";
    position: absolute;
    /* bleeds back out to the card's top and side edges; the card clips */
    inset: calc(-1 * var(--v2-engine-pad-t)) calc(-1 * var(--v2-engine-pad-x)) 0;
    z-index: -1;
    pointer-events: none;
    /* the fade occupies exactly the bottom band; the flat veil occupies everything above it. They tile
       rather than stack, so the fade actually fades instead of being held up by the layer beneath. */
    background:
        var(--v2-engine-fade-down) 0 100% / 100% var(--v2-engine-fade-h) no-repeat,
        var(--v2-engine-veil) 0 0 / 100% calc(100% - var(--v2-engine-fade-h)) no-repeat;
}

:where(body.v2) .engine__title {
    margin: 0;
    max-width: 20ch;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(2rem, 5.9vw, 7rem);
    font-weight: 700;
    line-height: 1.22;
    letter-spacing: -.02em;
}

/* --text-2, not the reference's `rgb(255 255 255 / .9)`: that value is between this product's --text and
   --text-2, and --text-2 is the token whose meaning is "body copy one step below the heading". */
:where(body.v2) .engine__body {
    margin: clamp(1rem, 2.4vw, 2.75rem) 0 0;
    max-width: min(44ch, 42%);
    color: var(--text-2);
    font-size: clamp(.875rem, 1.3vw, 1.625rem);
    font-weight: 400;
    line-height: 1.45;
}

/* the lede emphasises one word, and it is the section's whole claim */
:where(body.v2) .engine__body em {
    font-style: normal;
    font-weight: 700;
    color: var(--text);
}

/* ------------------------------------------------------------------ *
 * Foot — the two figures and the provenance caveat.
 *
 * Wrapped so the band under them is sized by the copy rather than by the card, which is the whole point of
 * finding 1. Mirrors the intro: fade at the TOP, top padding equal to it.
 * ------------------------------------------------------------------ */

:where(body.v2) .engine__foot {
    position: relative;
    padding-block-start: var(--v2-engine-fade-h);
}

:where(body.v2) .engine__foot::before {
    content: "";
    position: absolute;
    inset: 0 calc(-1 * var(--v2-engine-pad-x)) calc(-1 * var(--v2-engine-pad-b));
    z-index: -1;
    pointer-events: none;
    background:
        var(--v2-engine-fade-up) 0 0 / 100% var(--v2-engine-fade-h) no-repeat,
        var(--v2-engine-veil) 0 100% / 100% calc(100% - var(--v2-engine-fade-h)) no-repeat;
}

:where(body.v2) .engine__stats {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: clamp(1.5rem, 4vw, 3rem);
    margin: 0;
    padding: 0;
    list-style: none;
}

:where(body.v2) .stat {
    display: flex;
    flex: 0 1 clamp(15rem, 27%, 25rem);
    flex-direction: column;
    align-items: center;
}

/* The value chip notches down into the label plate. On dark the two are the same near-black, so they merge
   into one silhouette exactly as the comp draws it; on light they are hairlined, so the join reads as a
   badge overlapping a card rather than as two washes of nearly the same colour. */
:where(body.v2) .stat__value {
    position: relative;
    z-index: 1;
    margin: 0 0 clamp(-1.25rem, -1.6vw, -.75rem);
    padding: .1em .5em .05em;
    border: 1px solid var(--v2-engine-plate-line);
    border-radius: .42em;
    background: var(--v2-engine-plate);
    color: var(--accent-text);
    font-size: clamp(2.5rem, 4.4vw, 5.5rem);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
}

:where(body.v2) .stat__unit {
    font-size: .46em;
    font-weight: 700;
    letter-spacing: -.01em;
}

:where(body.v2) .stat__label {
    width: 100%;
    margin: 0;
    padding: clamp(1.1rem, 1.9vw, 1.9rem) clamp(1.1rem, 1.8vw, 2rem) clamp(.8rem, 1.3vw, 1.35rem);
    border: 1px solid var(--v2-engine-plate-line);
    border-radius: clamp(1rem, 1.8vw, 2rem);
    background: var(--v2-engine-plate);
    color: var(--text);
    font-size: clamp(.8125rem, 1.15vw, 1.4375rem);
    font-weight: 400;
    line-height: 1.35;
    text-align: left;
}

/* 13px floor, not the reference's 11px — see finding 3. This is the string that says the figures above it
   are a harness measurement rather than a live one, so it is the last text on the page that should be hard
   to read. */
:where(body.v2) .engine__caveat {
    margin: clamp(1rem, 1.8vw, 1.75rem) 0 0;
    max-width: 62ch;
    color: var(--text-muted);
    font-size: clamp(.8125rem, .85vw, .9375rem);
    line-height: 1.5;
}

/* ------------------------------------------------------------------ *
 * Fan-out diagram
 *
 * Geometry is pre-computed and pasted into the markup; nothing here moves a node or a control point.
 * ------------------------------------------------------------------ */

:where(body.v2) .fan {
    position: relative;
    /* auto block margins absorb whatever slack the card's min-height leaves, so the diagram floats in the
       middle of the render rather than the copy stretching to fill */
    margin: auto;
    width: 100%;
    max-width: min(100%, 68rem);
}

/* The diagram crosses both the render and the glow, so it sits on its own ground. A flat panel with two
   intersected linear masks feathers only at the edges — an elliptical mask cannot cover a wide, flat area
   without darkening half the card, which showed as a box edge over the artwork. (The reference's own note,
   kept: it is the reason the mask is shaped this way.) On light the panel resolves to `none`. */
:where(body.v2) .fan::before {
    content: "";
    position: absolute;
    inset: -30% -14%;
    z-index: -1;
    pointer-events: none;
    background: var(--v2-fan-plate);
    -webkit-mask-image:
        linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%),
        linear-gradient(90deg, transparent 0%, #000 10%, #000 90%, transparent 100%);
    mask-image:
        linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%),
        linear-gradient(90deg, transparent 0%, #000 10%, #000 90%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}

:where(body.v2) .fan__figure { position: relative; }

/* one geometry or the other, never both */
:where(body.v2) .fan__figure--tall { display: none; }

:where(body.v2) .fan svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

:where(body.v2) .fan__edges path {
    fill: none;
    stroke: var(--v2-fan-edge);
    stroke-width: 1.6;
}

/* Base state of a pulse. Two classes rather than `.fan__pulses path`, because s00-shared.css carries
   `:where(body.v2) [pathLength] { stroke-dasharray: none; stroke-dashoffset: 0 }` at (0,1,0) for the
   no-JS draw contract, and these paths declare pathLength="1". At (0,2,0) this cannot lose that tie to a
   later edit or a re-order of the two files. */
:where(body.v2) .fan__pulses .fan__pulse {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2.6;
    stroke-linecap: round;
    /* One 12% dash with a gap longer than the path, so exactly one dash is ever on the line.
       Period = 0.12 + 2 = 2.12; offset 2.12 parks it at the start, offset 1.24 (2.12 - 0.88) at the end. */
    stroke-dasharray: .12 2;
    stroke-dashoffset: 2.12;
    opacity: 0;
    animation: v2-fan-travel 3s linear infinite;
}

/* THE SECOND HOP, NOT A STAGGER. Every branch carries the same delay, so the six of them leave together —
   the section's claim is concurrent placement, and a diagram that relayed one follower after another would
   be arguing against the paragraph above it. Travel ends at 38% of 3s = 1.14s, so 1.1s hands over ~40ms
   before the trunk pulse lands. */
:where(body.v2) .fan__pulse--relay { animation-delay: 1.1s; }

@keyframes v2-fan-travel {
    0%, 4%   { stroke-dashoffset: 2.12; opacity: 0; }
    9%       { opacity: 1; }
    38%      { stroke-dashoffset: 1.24; opacity: 1; }
    46%, 100% { stroke-dashoffset: 1.24; opacity: 0; }
}

:where(body.v2) .fan__strategist { fill: var(--accent); }

:where(body.v2) .fan__broker {
    fill: var(--v2-fan-node);
    stroke: var(--v2-fan-node-line);
    stroke-width: 1.8;
}

:where(body.v2) .fan__follower {
    fill: var(--v2-fan-node);
    stroke: var(--accent);
    stroke-width: 1.8;
}

/* Labels live in HTML, not in the SVG, for the reason this codebase has recorded twice: font-size inside an
   SVG is in USER UNITS and magnifies with the drawing, so an 11-unit label renders larger than body copy
   once the viewBox is scaled up. Positioned by percentage of the figure box. */
:where(body.v2) .fan__labels {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

:where(body.v2) .fan__label {
    position: absolute;
    left: var(--x);
    top: var(--y);
    transform: translate(-50%, -50%);
    white-space: nowrap;
}

:where(body.v2) .fan__label--strategist {
    color: var(--text);
    font-size: clamp(.75rem, 1.05vw, 1.25rem);
    font-weight: 700;
}

:where(body.v2) .fan__label--broker,
:where(body.v2) .fan__label--followers {
    color: var(--text-muted);
    font-size: clamp(.6875rem, .95vw, 1.125rem);
    font-weight: 400;
}

/* =========================================================================================================
   RESPONSIVE
   ========================================================================================================= */

@media (max-width: 1023px) {
    :where(body.v2) .engine__card { min-height: min(88svh, 42rem); }
    :where(body.v2) .engine__body { max-width: 46ch; }

    /* a tall card crops the source hard, pulling the petals in over the copy: hold them to the top-right.
       The reference also introduces its scrim here; the two content bands already cover every string at
       every width, so that width-specific answer is not ported. */
    :where(body.v2) .engine__art { object-position: 74% 8%; }
}

@media (max-width: 720px) {
    :where(body.v2) .engine__card { min-height: 0; }

    :where(body.v2) .engine__title {
        font-size: clamp(1.875rem, 8vw, 2.75rem);
        line-height: 1.1;
    }

    :where(body.v2) .engine__body {
        max-width: none;
        font-size: clamp(.9375rem, 3.9vw, 1.0625rem);
        line-height: 1.55;
    }

    :where(body.v2) .engine__stats {
        flex-direction: column;
        align-items: stretch;
        gap: 1.75rem;
    }

    :where(body.v2) .stat {
        align-items: flex-start;
        max-width: none;
    }

    /* the value chip notches into the label plate; on a phone the overlap has to be small enough that the
       first line of the label still clears it */
    :where(body.v2) .stat__value {
        margin-inline-start: clamp(.75rem, 4vw, 1.5rem);
        margin-block-end: -.9rem;
        font-size: clamp(2.25rem, 12vw, 3.25rem);
    }

    :where(body.v2) .stat__label {
        padding: 1.4rem 1.15rem 1rem;
        font-size: clamp(.875rem, 3.7vw, 1rem);
    }

    /* swap to the near-square geometry — the wide one collapses to ~95px tall on a phone, where the nodes
       stop being readable */
    :where(body.v2) .fan { max-width: 100%; }
    :where(body.v2) .fan__figure--wide { display: none; }
    :where(body.v2) .fan__figure--tall { display: block; }
    :where(body.v2) .fan__edges path { stroke-width: 2.2; }
    :where(body.v2) .fan__pulses .fan__pulse { stroke-width: 3.4; }
}

/* the diagram is the section's argument, so it gets room rather than shrinking */
@media (max-width: 640px) {
    :where(body.v2) .fan { margin-block: clamp(1.5rem, 6vw, 2.5rem); }
    :where(body.v2) .fan__label--strategist { font-size: .8125rem; }
    :where(body.v2) .fan__label--broker,
    :where(body.v2) .fan__label--followers { font-size: .75rem; }
}

/* =========================================================================================================
   REDUCED MOTION — the pulses LAND, they do not disappear and they do not stop mid-wire.

   `!important` here is answering an `!important`, not shouting: s00-shared.css cuts every animation in this
   scope to .001ms and releases `[pathLength]` dashes with `!important`, which would leave these paths at
   their base `opacity: 0` — i.e. the diagram would lose the six arrivals that ARE its argument.

   Parked at offset 1.24, every dash sits on the last 12% of its own path, terminating at its node. Six
   followers shown arriving simultaneously still says "concurrent"; the reference parks at 1.55, which is
   mid-wire, and mid-wire is the one state this section claims does not happen.
   ========================================================================================================= */
@media (prefers-reduced-motion: reduce) {
    :where(body.v2) .fan__pulses .fan__pulse {
        animation: none !important;
        stroke-dasharray: .12 2 !important;
        stroke-dashoffset: 1.24 !important;
        opacity: 1 !important;
    }
}

/* ---------------- design-v2: s60-ratchet.css ---------------- */
/*! design v2 — HIGH-WATER MARK (merged)
    ===========================================================================================================
    Copy on the left, an equity chart in a panel on the right, both on a full-bleed photographic ground
    (ratchet-bg). It is the section that explains the one term in this product that decides whether a follower
    is charged twice on the same money, so the chart is the argument and everything else is scaffolding.

    THE FIVE THINGS THAT HAD TO BE RE-DECIDED, each a finding rather than a preference:

    1. THE SCRIM IS A PLATE PLUS A FADE, NOT ONE LONG RAMP — the same correctness fix the ceiling's wash
       needed, arriving here for the same reason.

       The reference ramps the scrim 0.93 -> 0.80 (32%) -> 0.38 (56%) -> 0.10 across the card, and the copy
       column ends at ~41% of the card width at every breakpoint I could compute (41.0% at 1440, 40.8% at
       1024, 40.8% at 901). So the last few centimetres of body copy sit at roughly 0.70 scrim — and the
       reference's own note says this artwork "is bright almost everywhere".

       Measured against a worst-case near-white glow underneath: --text-muted over a 0.70 scrim is 3.32:1,
       over 0.75 it is 4.00:1, over 0.80 it is 4.40:1, and it only clears AA at 0.85. Where a line of text
       lands on that ramp is a function of how the copy wraps, which is not something the design controls —
       exactly the failure mode the README records for the ceiling ("the box shrinks while the copy grows").

       So the horizontal scrim now HOLDS at 0.94-0.95 across the whole copy column plus the gutter, and only
       then falls away over the panel, which is opaque enough to carry its own contrast. Legibility stops
       depending on where the text happens to break.

    2. STACKED, THE SCRIM IS FLAT — because stacked, there is no left/right composition left to protect.
       Below 900px the copy runs the full width and its height is content-derived, so a vertical ramp has the
       identical problem one axis over: the position of the last line of body copy relative to the fade is a
       property of the copy, not of the design. A flat veil removes the dependency in one move. The artwork
       still reads (a bright glow at 8% transmission is several times brighter than the card under it) and
       still shows through the panel, which is where it was going to be seen on a phone anyway.

    3. THE PANEL IS NEARLY OPAQUE, AND THE NUMBER IS ARITHMETIC. The reference paints it rgb(6 7 9 / .82)
       over the art. Over a near-white glow that composites to 4.37:1 for --text-muted — under AA, and the
       panel is where every label in the section lives. 0.85 gives 4.87:1 and 0.88 gives 5.38:1, so the panel
       ships at 0.88: still 12% artwork showing through, which is the reading the reference wanted, and a
       measurable margin rather than a hope about how bright that particular render is.

    4. `backdrop-filter: blur(14px)` IS DROPPED. Behind 12% transmission the blur is imperceptible, and it
       costs a full-size compositing pass per scroll frame over a photographic ground. This codebase already
       made the same call once for the same reason ("refraction ... is a frame-budget decision, not a
       finish"). Half-applying the product's glass material — a blur with none of the edge that actually
       makes a pane read as a pane — is the glassmorphism-as-paint version it explicitly rejected.

    5. THE ARTWORK IS OFF ON LIGHT, via the shared --v2-art-* tokens, for the reason s80-clauses.css records
       at length: glow-on-black on a light page is a dark slab, and this codebase has already found and fixed
       that defect once. On light the card takes the lime-tinted ground instead, the scrim resolves to `none`
       (a dark wash over a light card would be the slab arriving by a different route), and the panel becomes
       the ordinary opaque plate. No section-local branch is needed for any of it — the tokens carry it.

    WHAT WAS DELIBERATELY NOT PORTED:

      • THE REVEAL SEQUENCE. The reference stages the chart with `animation-timeline: view()`: line draws,
        then the mark fades, then the wedge. `view()` resolves against the nearest ancestor SCROLL CONTAINER,
        and `.ratchet__card` is `overflow: hidden`, which is one — so the timeline is attached to a box that
        never scrolls and the `both` fill would hold the start state, i.e. a permanently invisible line. I
        cannot test that here, and the failure mode is the section's primary data mark not appearing at all.
        The equity line therefore draws with the primitive this product already ships and already tests
        (`[pathLength][data-draw]` in s00-shared.css, released to finished with no JS and under reduced
        motion), and the mark, the wedge and the annotation are static. The ratchet motion survives; the
        sequencing does not. Note also that `data-draw` must NOT be paired with `vector-effect:
        non-scaling-stroke` — that combination truncates the painted fraction to 1/scale — so no rule here
        sets it.

      • A KICKER. The reference gives this section none, so neither does the port. `ratchet.key` still needs a
        home at stage 2; s00-shared.css's `.v2-kicker` is where it goes.

      • THE `v2-keep-art` COMPARISON MODIFIER. s80-clauses.css declares it against the SHARED art tokens, and
        a second partial copy here — one that knew about this section's scrim and panel but not about the
        copy-colour inversion — would be the drift s00-shared.css exists to prevent. Adding
        `--v2-ratchet-scrim` and `--v2-ratchet-panel` to that one block is the whole change if the comparison
        is wanted for this section too.

    NEW TOKENS, both declared in BOTH themes rather than composed from var() on :root only — the trap
    00-tokens.css documents: a custom property's var() references are substituted WHERE IT IS DECLARED, so a
    token built on :root keeps its :root value inside a scope that overrides its inputs.
    =========================================================================================================== */

:root {
    /* No artwork on light, so nothing to veil. A dark wash over a white card is the slab defect arriving
       from the other direction. */
    --v2-ratchet-scrim: none;
    /* The ordinary inner-panel plate. Opaque, so the chart's contrast is a property of the tokens. */
    --v2-ratchet-panel: var(--v2-plate);
}

:root[data-theme="dark"] {
    /* PLATE + FADE. Flat 0.95 -> 0.94 across the copy column and the gutter (the copy ends at ~41% of the
       card at every breakpoint from 901px to 1440px), then away over the panel. The second layer is the
       reference's vertical settle, kept: it stops the card's top and bottom edges reading as harder than
       its middle. Painted UNDER the horizontal layer, as the reference has it. */
    --v2-ratchet-scrim:
        linear-gradient(90deg,
            rgb(4 5 6 / .95) 0%,
            rgb(4 5 6 / .94) 44%,
            rgb(4 5 6 / .52) 64%,
            rgb(4 5 6 / .14) 100%),
        linear-gradient(180deg, rgb(4 5 6 / .10) 0%, rgb(4 5 6 / .26) 100%);
    /* 0.88, not the reference's 0.82 — see note 3 in the header. */
    --v2-ratchet-panel: rgb(6 7 9 / .88);
}

/* ------------------------------------------------------------------ *
 * Frame and card
 *
 * The outer frame is `.v2-framed` in the markup rather than restated here: the reference's own values for
 * this section (`clamp(.75rem, 1.4vw, 1.75rem)` of padding on the ink) are exactly --v2-frame-pad on
 * --v2-frame, which is what the shared rule already is.
 * ------------------------------------------------------------------ */

/* `isolation` is load-bearing, not decoration: the art and the scrim sit at negative z-index, and without a
   stacking context here they would paint behind the card's own background instead of on top of it. */
:where(body.v2) .ratchet__card {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: clamp(1.5rem, 3vw, 4.25rem);
    border: 1px solid var(--v2-card-line);
    /* --v2-r-card rather than the reference's own clamp(.875rem, 1.45vw, 1.75rem). The reference draws this
       card a little tighter than the trust-ceiling card; on a page that shows both, two big framed cards
       disagreeing about their corner reads as a mistake, and a third literal is how a radius ladder becomes
       a list of radii. */
    border-radius: var(--v2-r-card);
    background-color: var(--v2-card);
    /* `none` on dark, where the photograph does this job; the lime-tinted ground on light, where it is off. */
    background-image: var(--v2-art-ground);
}

:where(body.v2) .ratchet__art {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    opacity: var(--v2-art-opacity);
}

:where(body.v2) .ratchet__scrim {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: var(--v2-ratchet-scrim);
}

:where(body.v2) .ratchet__grid {
    display: grid;
    grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
    gap: clamp(2rem, 5vw, 5.5rem);
    align-items: center;
}

/* ------------------------------------------------------------------ *
 * Copy
 * ------------------------------------------------------------------ */

:where(body.v2) .ratchet__title {
    margin: 0;
    max-width: 22ch;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 3.2vw, 3.75rem);
    font-weight: 700;
    line-height: 1.16;
    letter-spacing: -.02em;
    text-wrap: balance;
}

:where(body.v2) .ratchet__body {
    margin: clamp(.9rem, 1.6vw, 1.75rem) 0 0;
    max-width: 48ch;
    color: var(--text-muted);
    font-size: clamp(.875rem, 1.05vw, 1.1875rem);
    line-height: 1.6;
}

:where(body.v2) .ratchet__cta {
    display: inline-block;
    margin-block-start: clamp(1rem, 1.8vw, 1.75rem);
    color: var(--accent-text);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    font-weight: 600;
    text-decoration: none;
}

:where(body.v2) .ratchet__cta:hover {
    text-decoration: underline;
    text-underline-offset: .25em;
}

/* --ring is a box-shadow value, so it cannot be an outline colour; --accent-focus is what it is built from
   and is a visible step from --accent in both themes, which is the property that matters here. */
:where(body.v2) .ratchet__cta:focus-visible {
    outline: 2px solid var(--accent-focus);
    outline-offset: 3px;
    border-radius: var(--r-xs);
}

/* ------------------------------------------------------------------ *
 * The chart panel
 *
 * The reference's comment is the right one and is kept: "the chart needs a calm ground — hairlines over
 * those ribbons are unreadable". The panel is also the <figure>, which fixes a real markup bug: <figcaption>
 * must be the first or last child of <figure> to be associated with it, and the reference nests it two
 * levels deep, so its caption is announced as loose prose rather than as the figure's caption.
 * ------------------------------------------------------------------ */

:where(body.v2) .ratchet__figure { margin: 0; }

:where(body.v2) .ratchet__panel {
    padding: clamp(1rem, 1.9vw, 1.75rem) clamp(1rem, 1.9vw, 1.75rem) clamp(.85rem, 1.5vw, 1.35rem);
    border: 1px solid var(--v2-card-line);
    border-radius: var(--v2-r-tile);
    background: var(--v2-ratchet-panel);
}

:where(body.v2) .ratchet__plot { position: relative; }

:where(body.v2) .ratchet__plot svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

/* Only the area ABOVE the previous peak is shaded, because that is the only part a fee is charged on. The
   caption says so; the drawing must not say anything else. */
:where(body.v2) .ratchet__wedge {
    fill: color-mix(in srgb, var(--accent) 22%, transparent);
    stroke: none;
}

/* Dashed because it is a THRESHOLD, not a gridline — the reference's distinction, kept.
   --text-muted rather than --text-faint: WCAG 1.4.11 asks 3:1 of a graphical object you need in order to
   understand the content, and this line IS the subject of the section. Measured on the panel it is 5.70:1
   dark and 4.93:1 light. */
:where(body.v2) .ratchet__hwm {
    stroke: var(--text-muted);
    stroke-width: 2;
    stroke-dasharray: 9 9;
}

/* miter, not round: a ratchet has corners, and softening the peak softens the only thing the chart is
   about. `data-draw` wires the draw to the product's own primitive — see the header note. */
:where(body.v2) .ratchet__equity {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2.6;
    stroke-linejoin: miter;
    stroke-linecap: round;
}

/* The x axis carries the "Time ->" note and nothing else, so it is a boundary rather than data — but it is
   still the baseline the whole shape is read against, so --field-border (3.62:1 light / 4.40:1 dark) rather
   than the decorative --hairline, which is 1.2:1 on the light plate. */
:where(body.v2) .ratchet__axis {
    stroke: var(--field-border);
    stroke-width: 1;
}

/* THE Y AXIS IS DELIBERATELY UNNUMBERED and there are no y ticks. The chart shows the RULE, not an account.
   The caption says so; do not add them. */

/* The label lives in HTML positioned by percentage, not in the SVG, for the reason this codebase has
   recorded twice: font-size inside an SVG is in USER UNITS and magnifies with the drawing, so an 11-unit
   label rendered larger than 17px body copy once the viewBox was scaled up. The fallbacks matter — an
   undefined --x makes `left` invalid at computed-value time, which silently drops the label to `auto`. */
:where(body.v2) .ratchet__annot {
    position: absolute;
    left: var(--x, 0%);
    top: var(--y, 0%);
    transform: translateY(-50%);
    color: var(--text-muted);
    font-size: clamp(.6875rem, .85vw, .9375rem);
    white-space: nowrap;
    pointer-events: none;
}

:where(body.v2) .ratchet__axisrow {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem 1.5rem;
    justify-content: space-between;
    margin-block-start: .4rem;
}

:where(body.v2) .ratchet__axisrow p {
    margin: 0;
    color: var(--text-muted);
    font-size: clamp(.6875rem, .8vw, .9375rem);
}

:where(body.v2) .ratchet__caption {
    margin: clamp(.75rem, 1.3vw, 1.15rem) 0 0;
    padding-block-start: clamp(.75rem, 1.3vw, 1.15rem);
    border-block-start: 1px solid var(--v2-card-line);
    max-width: 62ch;
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, 1rem);
    line-height: 1.55;
}

/* ------------------------------------------------------------------ *
 * Stacked
 * ------------------------------------------------------------------ */

@media (max-width: 900px) {
    /* FLAT, not a ramp — see note 2 in the header. Worst case measured: --text-muted over 0.92 on a
       near-white glow is 6.12:1. */
    :root[data-theme="dark"] {
        --v2-ratchet-scrim: linear-gradient(rgb(4 5 6 / .92), rgb(4 5 6 / .92));
    }

    :where(body.v2) .ratchet__grid {
        grid-template-columns: minmax(0, 1fr);
        gap: clamp(1.75rem, 5vw, 2.75rem);
    }

    /* A stroke width is in USER UNITS, so it SHRINKS with the drawing — the mirror of the font-size trap
       above, and the reason the reference bumps these on a phone. Arithmetic: at a 1000-unit viewBox drawn
       ~600px wide the scale is 0.6, so 2.6 units is 1.56px. */
    :where(body.v2) .ratchet__equity { stroke-width: 3.4; }
    :where(body.v2) .ratchet__hwm { stroke-width: 2.8; stroke-dasharray: 11 10; }
    :where(body.v2) .ratchet__axis { stroke-width: 1.4; }
}

@media (max-width: 640px) {
    :where(body.v2) .ratchet__card {
        padding: clamp(1.25rem, 5vw, 2rem) clamp(1.25rem, 5vw, 2rem) clamp(1.5rem, 6vw, 2.25rem);
    }

    :where(body.v2) .ratchet__title {
        font-size: clamp(1.625rem, 7.2vw, 2.25rem);
        line-height: 1.12;
    }

    :where(body.v2) .ratchet__body {
        font-size: clamp(.9375rem, 3.9vw, 1.0625rem);
        line-height: 1.6;
    }

    :where(body.v2) .ratchet__panel { padding: clamp(.9rem, 3.5vw, 1.25rem); }

    /* At 390px the plot is ~297px wide, so the scale is 0.297 and the reference's 3.4 units is 1.01px — a
       sub-pixel hairline for the section's primary data mark. 5 units is 1.49px; the dashes are widened for
       the same reason (9 units would be 2.7px, which reads as a dotted line rather than a dashed one). */
    :where(body.v2) .ratchet__equity { stroke-width: 5; }
    :where(body.v2) .ratchet__hwm { stroke-width: 3.6; stroke-dasharray: 14 12; }
    :where(body.v2) .ratchet__axis { stroke-width: 2; }

    /* a ~150px-tall plot: the annotation has to clear the mark rather than sit on it */
    :where(body.v2) .ratchet__annot {
        top: calc(var(--y, 0%) - 3%);
        left: 0;
        font-size: .75rem;
    }

    :where(body.v2) .ratchet__wedge { fill: color-mix(in srgb, var(--accent) 32%, transparent); }

    :where(body.v2) .ratchet__caption { font-size: .75rem; }
}

@media (max-width: 560px) {
    /* the two notes crowd onto one line on a phone */
    :where(body.v2) .ratchet__axisrow {
        flex-direction: column;
        gap: .2rem;
    }
}

/* ---------------- design-v2: s70-registry.css ---------------- */
/*! design v2 — STRATEGY REGISTRY (merged)
    ===========================================================================================================
    The ranked list of strategists currently open to copiers. It is the only section on the page whose
    content is LIVE — `MarketplaceService::featuredStrategists()` — and the only one that renders
    conditionally, exactly as it does today: no featured strategists, no section.

    THE REFERENCE PARKED THIS SECTION, AND SAID WHY (page.tsx:22-25):
        "Parked, not deleted. The listings are seeded test rows ("test strategy", "Testing"), every strategy
         is unrated, and the banner's two stats are derived from that same data — so the whole section is
         standing on placeholder content."

    So this is not a transcription. The reference's hard-coded array is not ported at all; what is ported is
    its composition, and every place where its composition assumed data we do not have — or assumed data we
    have but of a different SHAPE — is re-decided below. Four of those are real findings rather than
    preferences, and the first is the one that matters.

    1. THE LADDER'S BAR CHART CANNOT CARRY THIS DATA, FOR TWO INDEPENDENT REASONS — and the reference
       already wrote the replacement.

       The reference draws four tall columns whose HEIGHT is the performance fee, normalised to the most
       expensive one, and annotates the problem it creates: "the tallest bar is the most expensive strategy —
       not the best one. The axis note says so out loud, because the shape alone would imply a ranking."

       (a) THE ORDER AND THE HEIGHT WOULD ENCODE DIFFERENT QUANTITIES. Our list is ordered by Trust
           (`orderByRaw('coalesce(trust_score, -1) desc')`) and the heading and lede both say so. Painting
           fee as the magnitude puts a second, unrelated variable in the same figure, and the taller mark
           reads as "more" of whatever the ranking is about. A note under a chart saying "this shape does not
           mean what it looks like" is a design that needs changing, not annotating — this codebase has
           already made exactly that call once, when a rating DISTRIBUTION was drawn into a TREND sparkline
           and forty one-stars read as "ratings crashed and are recovering". Making cost the biggest,
           brightest thing on a marketing page for a financial product is also the wrong direction on its own.

       (b) ABSENCE IS NOT DRAWABLE AS A BAR, and absence is the case this section exists to get right.
           `ScoringService::trust()` genuinely returns null for a strategist with no settled record, nothing
           is written to `score_history`, and the product's standing rule is that this renders as an em dash,
           never as 0, "because a 0 would read as the worst possible score rather than as no record yet".
           A zero-height column and a score of zero are the same picture. So switching the encoding from fee
           to Trust does not rescue the ladder either — it walks straight into the defect the em-dash rule
           exists to prevent.

       THE FIX IS THE REFERENCE'S OWN. Below 860px it already turns the ladder on its side — "the columns are
       too narrow for their own labels, so the same encoding turns on its side: bar width, not bar height" —
       and puts the magnitude in `.rung::after`, a rule under each row. That form is adopted at EVERY width,
       because our labels are longer than its four bare names (a handle, a fee, a high-water-mark term and a
       score) and because a row can carry a dashed rule where a column can only carry a shorter one. The
       class names are the reference's, so the two files still read against each other.

       Two consequences worth stating. `--pct` is now the Trust score ABSOLUTE, not normalised against the
       largest in the set: Trust is a published 0-100 scale, so the bar has a true zero and a true ceiling,
       where fee has no natural maximum and had to be normalised. And an unrated row takes a DASHED rule with
       no fill — the same device this codebase uses for the credential's ghost fields, where "a solid block
       reads as a loading skeleton while a dashed outline reads as an empty field waiting to be filled". A
       zero-length solid fill would have been indistinguishable from a score of zero all over again.

    2. THE BANNER IS NOT PORTABLE, AND WHAT WAS IN IT IS NOT SOURCEABLE.

       `registry-banner.png` was one of the two assets stage 1 did not re-encode, because only this parked
       section referenced it. It is not in the tree and is not referenced here.

       Its two figures go with it, and they would have had to go even with the artwork: "Open to copiers" was
       `STRATEGIES.length` and "Performance fee range" was min-max over the same array. Against live data both
       describe the SHOWN rows, not the marketplace — `featuredStrategists()` takes a `limit` (6) — so
       printing "6" under "Open to copiers" while forty are open is a false statistic, and the true number
       needs a count query that does not exist. There is no honest number to print, so no number is printed.

       That leaves an empty rounded rectangle, which this codebase already refuses on its own terms
       ("a card's visible border promises its bounds are filled"). So the banner becomes the PLATE THE LIST
       SITS ON — `.registry__board` — keeping its width, its radius and its role as the section's anchor,
       and losing only its emptiness. Renamed rather than kept as `.registry__banner`, because a class called
       banner wrapping an ordered list is a name that lies.

       Note what else goes with it: `aspect-ratio: 16 / 6.8` with the stats absolutely positioned inside is a
       box sized by the VIEWPORT carrying content sized by the CONTENT — the exact structural fault stage 1
       removed from the ceiling's wash and again from the ratchet's scrim. The board is content-sized.

    3. THE HIGH-WATER MARK MUST BE STATED IN BOTH DIRECTIONS, and that is a correction to what ships today.

       `welcome.blade.php` prints "High-water mark on" only when the flag is set and prints nothing when it
       is not, so "off" and "we did not say" look identical. On this same page the ratchet section says "It
       is a per-strategy term, so every listing states whether it applies — check it before you copy", and
       CLAUDE.md records the wider defect: the high-water mark "was stated as a universal guarantee in five
       places, and it is a per-strategy checkbox defaulting to OFF". A listing that goes quiet on the default
       case makes the sentence beside it false. Both states are printed.

    4. THE LEDE CARD TAKES THE ACCENT — the one deliberate departure from the reference's own composition.

       The banner was this section's colour and weight; with it gone the section is small type over a pale
       board. The design already has a non-control accent block (`.clause--accent`), it needs no asset, and
       it works in both themes by construction because it follows `--accent`/`--on-accent` like everything
       else — deep lime carrying white by day, lemon carrying near-black by night. Body copy on it takes the
       FULL `--on-accent`, never an alpha of it, for the reason s80-clauses.css records: the ink at 78% over
       lemon measured under AA.

    WHAT WAS DELIBERATELY NOT PORTED:

      • THE GROW-ON-SCROLL SEQUENCE. `animation-timeline: view()` resolves against the nearest ancestor
        SCROLL CONTAINER, and s60-ratchet.css records what that costs when the ancestor is `overflow: hidden`
        — a timeline attached to a box that never scrolls, with `both` holding the start state, i.e. a mark
        that never appears. The bars here are 2px rules; there is nothing to animate that is worth that risk,
        and the board deliberately carries no `overflow: hidden` at all (see the focus-ring note below).

      • THE BROKER-VERIFICATION BADGE. `featuredStrategists()` eager-loads
        `strategy:id,uuid,broker_verification_status`, which looks like a display field and is not: the
        column is named because `Strategy::isPubliclyCopyable()` throws on a partial select that omits it.
        Reading it as a badge would be inventing a surface out of a gate's dependency.

      • `.registry__kicker`. The reference gives this section a plain uppercase label and gives two other
        sections a bordered one. s00-shared.css makes the bordered form general so all eight `*.key` catalog
        rows keep a home; `registry.key` uses it like the rest.

    NO NEW TOKENS. Everything here is a card, a plate, an accent control, a hairline or a meter, and this
    system already names all five. Worth saying explicitly, because the temptation was a `--v2-registry-*`
    for the board — and a token whose only consumer is one rule is a rename, not an abstraction.

    MEASURED, against the composited grounds rather than against the token names (both boards are solid, so
    these are exact):
        board #ffffff / #0b0c0d   --text 16.8:1 / 18.0:1 · --text-muted 5.38:1 / 6.82:1 · --accent 5.41:1 / 14.3:1
        row hover (accent 8%)     --text-muted 4.82:1 / 6.30:1
        section ground            --text-muted 5.00:1 / 6.79:1
        accent card               --on-accent 4.99:1 / 12.2:1   (the pair CLAUDE.md already records)
    =========================================================================================================== */

/* Ground only. The padding comes from `.v2-section` in the markup — the shared utility whose value is the
   reference's own section formula, so this section does not restate it. (`.engine` and `.ceiling` restate
   theirs because they use the tighter FRAME pad around a big rounded card; this one does not have one.) */
:where(body.v2) .registry {
    background: var(--v2-frame);
}

/* ------------------------------------------------------------------ *
 * Head: the claim on the left, the invitation on the right
 * ------------------------------------------------------------------ */

:where(body.v2) .registry__top {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    gap: clamp(1.5rem, 4vw, 4rem);
    align-items: start;
}

:where(body.v2) .registry__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(2rem, 4.2vw, 5rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -.025em;
    text-wrap: balance;
}

/* The accent block that replaces the banner's weight (header note 4). `border-color: transparent` rather
   than `border: 0` so the box keeps the same metrics as any other card in the system. */
:where(body.v2) .registry__card {
    padding: clamp(1.1rem, 1.9vw, 1.75rem);
    border: 1px solid transparent;
    border-radius: var(--v2-r-tile);
    background: var(--accent);
}

:where(body.v2) .registry__lede {
    margin: 0;
    color: var(--on-accent);
    font-size: clamp(.875rem, 1.05vw, 1.1875rem);
    line-height: 1.6;
}

/* On the accent card the pill inverts: the card is the accent, so the control cannot also be. */
:where(body.v2) .registry__btn {
    display: inline-block;
    margin-block-start: clamp(.9rem, 1.5vw, 1.35rem);
    padding: .7em 1.35em;
    border-radius: var(--r-pill);
    background: var(--v2-card);
    color: var(--accent-text);
    font-size: clamp(.75rem, .92vw, 1rem);
    font-weight: 700;
    text-decoration: none;
    transition: transform var(--dur-fast) var(--ease);
}

/* The hover step is --accent-focus, not an underline — a pill that underlines its own label reads as a
   text link that has gone wrong, and ft-ui.css's base `a:hover` would supply one here otherwise. This is the
   pair the product already guarantees is a visible step: 7.08:1 on the light pill, 16.23:1 on the dark one. */
:where(body.v2) .registry__btn:hover { color: var(--accent-focus); text-decoration: none; }
:where(body.v2) .registry__btn:active { transform: scale(.95); }

/* The reference's own focus treatment, tokenised. The OFFSET is what makes it correct: with a 3px gap the
   ring's neighbours are the accent card on both sides, so --on-accent measures 4.99:1 (light) / 12.21:1
   (dark) against them. The product's --ring would sit against the lime card at 1.42:1 on light. */
:where(body.v2) .registry__btn:focus-visible {
    outline: 2px solid var(--on-accent);
    outline-offset: 3px;
}

/* ------------------------------------------------------------------ *
 * The board — the reference's `.registry__banner`, now carrying the
 * list instead of a photograph and two unsourceable figures.
 *
 * NO `overflow: hidden`. Nothing bleeds out of it, and hiding overflow
 * would clip the rows' focus ring (a 3px box-shadow) on the first and
 * last rows — the two most likely to be reached by keyboard. The rows
 * are inset by the board's own padding instead, which also keeps their
 * rules clear of the rounded corners.
 * ------------------------------------------------------------------ */

:where(body.v2) .registry__board {
    margin-block-start: clamp(1.25rem, 2.4vw, 2.25rem);
    padding: clamp(.5rem, 1.6vw, 1.5rem) clamp(.75rem, 2vw, 2rem) clamp(1rem, 2vw, 1.75rem);
    border: 1px solid var(--v2-card-line);
    border-radius: var(--v2-r-tile);
    background: var(--v2-card);
}

:where(body.v2) .ladder {
    margin: 0;
    padding: 0;
    list-style: none;
    border-block-start: 1px solid var(--hairline);
}

/* The row rule IS the bar, so every row carries one and it is never decoration — the same idea as the
   ceiling's `.weights__track`, and the reference's own sub-860px treatment. */
:where(body.v2) .rung {
    position: relative;
    border-block-end: 1px solid var(--hairline);
}

:where(body.v2) .rung::after {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block-end: -1px;
    width: var(--pct, 0%);
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
    pointer-events: none;
}

/* NO SETTLED RECORD. A dashed track and no fill, because a solid fill of zero length is the same picture as
   a score of zero — which is the exact reading the em dash exists to prevent. Dashed reads as a field
   waiting to be filled; the credential's ghost fields make the same distinction for the same reason. */
:where(body.v2) .rung--unrated { border-block-end-style: dashed; }
:where(body.v2) .rung--unrated::after { display: none; }

:where(body.v2) .rung__link {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: clamp(.75rem, 2vw, 1.75rem);
    padding: clamp(.85rem, 1.6vw, 1.15rem) clamp(.25rem, .8vw, .6rem);
    /* the radius is on the base, not on :focus-visible, so the hover plate and the ring are the same shape
       at all times — ft-ui.css's base `a:focus-visible` sets one only while focused, which would make the
       box change shape as it gains focus */
    border-radius: var(--r-xs);
    color: inherit;
    text-decoration: none;
}

/* TEXT-DECORATION IS PROPAGATED, NOT INHERITED: an underline set on the <a> paints across every descendant
   and a descendant cannot remove it. ft-ui.css's base `a:hover { text-decoration: underline }` would
   therefore underline the rank, the terms, the score and its label along with the name. It is suppressed on
   the link and re-applied to the name alone, which is the affordance the row actually needs. Specificity is
   why this works at all: (0,2,0) here against (0,1,1) for the base rule. */
:where(body.v2) .rung__link:hover {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    text-decoration: none;
}

:where(body.v2) .rung__link:hover .rung__name { text-decoration: underline; }

:where(body.v2) .rung__link:focus-visible {
    outline: none;
    box-shadow: var(--ring);
}

/* aria-hidden in the markup: the <ol> already announces position, so a spoken "01" is a second copy of it. */
:where(body.v2) .rung__num {
    color: var(--text-faint);
    font-size: clamp(1rem, 1.6vw, 1.5rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
}

:where(body.v2) .rung__text { min-width: 0; }

:where(body.v2) .rung__name {
    display: block;
    overflow: hidden;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1rem, 1.3vw, 1.375rem);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -.01em;
    text-overflow: ellipsis;
    white-space: nowrap;
}

:where(body.v2) .rung__terms {
    display: block;
    margin-block-start: .3rem;
    color: var(--text-muted);
    font-size: clamp(.75rem, .88vw, .9375rem);
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
}

:where(body.v2) .rung__trust {
    text-align: right;
    line-height: 1.05;
}

:where(body.v2) .rung__score {
    display: block;
    color: var(--text);
    font-size: clamp(1.375rem, 2.2vw, 2rem);
    font-weight: 600;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}

/* The em dash carries no value, so it takes the muted weight the label already has — a full-strength dash
   in the score's own colour reads as a figure. */
:where(body.v2) .rung--unrated .rung__score { color: var(--text-muted); }

/* 11px floor, and it is the reference's note rather than a preference: "floor is 11px — tracked uppercase
   gets illegible below that". The ledger this replaces sets 10px. */
:where(body.v2) .rung__scorelabel {
    display: block;
    margin-block-start: .3rem;
    color: var(--text-muted);
    font-size: clamp(.6875rem, .8vw, .8125rem);
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* ------------------------------------------------------------------ *
 * Legend and note
 *
 * `.ladder__note` is the figure's LEGEND — it says what the bar
 * encodes — so it is code-owned rather than a CMS row, for the same
 * reason the panel already gives for the Trust formula and the eight
 * weights: a description of a mark that staff can edit is a page that
 * can be made to advertise an encoding the page does not use.
 * `.registry__caption` is `registry.note` and stays editable.
 * ------------------------------------------------------------------ */

:where(body.v2) .ladder__note {
    margin: clamp(.85rem, 1.5vw, 1.35rem) 0 0;
    max-width: 62ch;
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, 1rem);
    line-height: 1.5;
}

:where(body.v2) .registry__caption {
    margin: clamp(.9rem, 1.6vw, 1.5rem) 0 0;
    max-width: 62ch;
    color: var(--text-muted);
    font-size: clamp(.75rem, .9vw, 1rem);
    line-height: 1.5;
}

/* ------------------------------------------------------------------ *
 * Narrow
 * ------------------------------------------------------------------ */

@media (max-width: 860px) {
    :where(body.v2) .registry__top { grid-template-columns: minmax(0, 1fr); }
}

/* The score cannot keep a column of its own once the handle needs the width. It drops under the name and
   goes horizontal — value then label, reading order unchanged. The rank stays on the left at every width:
   the reference moves it to the right when it stacks, but there it is decoration on a bar; here it is the
   position in a ranked list, and a ranked list reads its ordinal first. */
@media (max-width: 620px) {
    :where(body.v2) .rung__link {
        grid-template-columns: auto minmax(0, 1fr);
        row-gap: .45rem;
    }

    :where(body.v2) .rung__trust {
        grid-column: 2;
        display: flex;
        align-items: baseline;
        gap: .45rem;
        text-align: left;
    }

    :where(body.v2) .rung__score { font-size: 1.25rem; }
    :where(body.v2) .rung__scorelabel { margin-block-start: 0; }
}

/* ---------------- design-v2: s80-clauses.css ---------------- */
/*! design v2 — GUARANTEE CLAUSES (merged)
    ===========================================================================================================
    The second stage-1 section, chosen because it is the one that decides the ARTWORK question. It is a
    full-bleed photographic ground (mesh-bg.png, a green perspective grid rendered on black), a veil, a
    staggered card grid, one accent card, and two glass objects composited with `mix-blend-mode: screen`.

    THREE THINGS HAD TO BE RE-DECIDED FOR LIGHT, and each is a real finding rather than a preference:

    1. THE ARTWORK CANNOT SIMPLY MOVE. It is glow-on-black; on a light page it is a dark slab. That is
       precisely the defect this codebase already found and fixed once — "three full-bleed near-black slabs
       across an otherwise light page (measured: 2,188px of 6,900px)". So on light the art is off by default
       (--v2-art-opacity: 0) and the section carries a lime-tinted ground instead. `.v2-keep-art` on <body>
       restores it, so the two readings can be compared side by side rather than argued about.

    2. `mix-blend-mode: screen` IS NOT PORTABLE, and the fix is in the asset, not the CSS. Screen keeps a
       glow and drops the black it was rendered on — which is why the reference needs it — but screen against
       a WHITE card returns white, so the object vanishes entirely on light. Rather than branch the blend
       mode, stage 1 re-cuts the two objects with a real alpha channel (alpha = luminance, which is exact for
       a glow on pure black). An alpha-cut PNG composites normally on ANY ground, needs no blend mode, and
       removes the `isolation: isolate` the blend mode forced on every ancestor.

    3. THE ACCENT CARD FOLLOWS THE PRODUCT'S ACCENT, NOT THE REFERENCE'S. On dark that is lemon carrying
       near-black, as drawn. On light it is the deep lime carrying white — which is not what the comp shows,
       but IS what every other control in this product does, and a single card that disagreed with every
       button on the page would read as a mistake rather than as a highlight.
    =========================================================================================================== */

:where(body.v2) .clauses {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: clamp(2.5rem, 6vw, 7rem) clamp(1rem, 3vw, 4rem);
    background: var(--v2-frame);
    background-image: var(--v2-art-ground);
}

:where(body.v2) .clauses__mesh {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center bottom;
    opacity: var(--v2-art-opacity);
}

/* The art is faint but the cards need a settled ground under them. */
:where(body.v2) .clauses__veil {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: var(--v2-art-veil);
}

:where(body.v2) .clauses__head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: clamp(1.25rem, 4vw, 5rem);
    align-items: start;
}

:where(body.v2) .clauses__kicker {
    margin: 0;
    padding: .5em .8em;
    border: 1px solid var(--field-border);
    border-radius: .5rem;
    color: var(--text-muted);
    font-size: clamp(.6875rem, .82vw, .875rem);
    letter-spacing: .08em;
    line-height: 1.3;
    text-transform: uppercase;
}

:where(body.v2) .clauses__headtext {
    justify-self: end;
    max-width: 46ch;
    text-align: right;
}

:where(body.v2) .clauses__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.625rem, 3.2vw, 3.75rem);
    font-weight: 700;
    line-height: 1.14;
    letter-spacing: -.02em;
    text-wrap: balance;
}

:where(body.v2) .clauses__lede {
    margin: clamp(.6rem, 1.1vw, 1.1rem) 0 0;
    color: var(--text-muted);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    line-height: 1.6;
}

/* --- cards --- */

:where(body.v2) .clauses__grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(.75rem, 1.6vw, 1.5rem);
    margin: clamp(1.75rem, 4vw, 4rem) 0 0;
    padding: 0;
    list-style: none;
}

/* the staggered rhythm the comp draws — the middle column drops */
:where(body.v2) .clauses__grid > :nth-child(3n + 2) {
    margin-block-start: clamp(.75rem, 2.6vw, 2.75rem);
}

:where(body.v2) .clause {
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: clamp(15rem, 24.5vw, 22rem);
    padding: clamp(1.1rem, 1.9vw, 1.85rem);
    border: 1px solid var(--v2-card-line);
    border-radius: clamp(.875rem, 1.35vw, 1.5rem);
    background: var(--v2-card);
}

:where(body.v2) .clause--accent {
    border-color: transparent;
    background: var(--accent);
}

:where(body.v2) .clause__num {
    margin: 0;
    align-self: flex-end;
    color: var(--text-faint);
    font-size: clamp(1.25rem, 2.1vw, 2.25rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
}

:where(body.v2) .clause--accent .clause__num {
    color: color-mix(in srgb, var(--on-accent) 52%, transparent);
}

/* Alpha-cut render (see the header note) — no blend mode, no isolation, composites on any ground.
   Kept in normal FLOW rather than absolutely positioned: the copy is bottom-anchored, so an absolute
   object's clearance depended on how long the text was and it collided with the longer titles. */
:where(body.v2) .clause__obj {
    width: clamp(5rem, 8.5vw, 8rem);
    height: auto;
    margin-block: clamp(.35rem, .8vw, .7rem) clamp(.85rem, 1.5vw, 1.35rem);
    margin-inline-start: clamp(-.75rem, -1vw, -.4rem);
    pointer-events: none;
}

:where(body.v2) .clause__text { margin-block-start: auto; }

:where(body.v2) .clause__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(.9375rem, 1.3vw, 1.4375rem);
    font-weight: 700;
    line-height: 1.28;
    letter-spacing: -.01em;
    text-wrap: balance;
}

:where(body.v2) .clause--accent .clause__title { color: var(--on-accent); }

:where(body.v2) .clause__body {
    margin: .5rem 0 0;
    color: var(--text-muted);
    font-size: clamp(.75rem, .92vw, 1rem);
    line-height: 1.6;
}

/* Not a transparency: the on-accent ink at 78% over lemon measured under AA. Body copy on the accent card
   carries the full --on-accent, and the difference from the title is carried by weight and size instead. */
:where(body.v2) .clause--accent .clause__body { color: var(--on-accent); }

:where(body.v2) .clause__enforcement {
    display: inline-block;
    margin: clamp(.7rem, 1.2vw, 1.1rem) 0 0;
    padding: .3em .7em;
    border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
    border-radius: 999px;
    color: var(--accent-text);
    font-family: var(--font-mono-v2);
    font-size: clamp(.6875rem, .82vw, .875rem);
    line-height: 1.4;
}

:where(body.v2) .clause--accent .clause__enforcement {
    border-color: color-mix(in srgb, var(--on-accent) 34%, transparent);
    color: var(--on-accent);
}

:where(body.v2) .clauses__cta {
    display: inline-block;
    margin-block-start: clamp(1.25rem, 2.6vw, 2.25rem);
    color: var(--accent-text);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    font-weight: 600;
    text-decoration: none;
}
:where(body.v2) .clauses__cta:hover { text-decoration: underline; }

@media (max-width: 1023px) {
    :where(body.v2) .clauses__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    :where(body.v2) .clauses__grid > :nth-child(3n + 2) { margin-block-start: 0; }
    :where(body.v2) .clauses__grid > :nth-child(2n) { margin-block-start: clamp(.75rem, 2.6vw, 2rem); }
}

@media (max-width: 720px) {
    :where(body.v2) .clauses__head { grid-template-columns: minmax(0, 1fr); }
    :where(body.v2) .clauses__headtext { justify-self: start; text-align: left; }
    :where(body.v2) .clauses__grid { grid-template-columns: minmax(0, 1fr); }
    :where(body.v2) .clauses__grid > :nth-child(2n) { margin-block-start: 0; }
}

/* ------------------------------------------------------------------ *
 * COMPARISON MODIFIER — the artwork kept on light.
 *
 * Not a shipping feature: it exists so the "dark slab on a light page"
 * reading can be looked at rather than argued about. Whichever wins,
 * one of these two blocks is deleted at stage 2.
 * ------------------------------------------------------------------ */
:root:not([data-theme="dark"]) body.v2-keep-art {
    --v2-art-opacity: 1;
    --v2-art-ground: none;
    --v2-art-veil: linear-gradient(180deg,
        rgb(4 5 6 / .72) 0%, rgb(4 5 6 / .42) 34%, rgb(4 5 6 / .58) 72%, rgb(4 5 6 / .86) 100%);
    /* the cards and copy have to invert with the ground they now sit on, or this reading is not a fair
       comparison — it is just unreadable text on a photograph */
    --v2-card: #0b0c0d;
    --v2-card-line: rgb(255 255 255 / .12);
    --text: #ffffff;
    --text-muted: rgb(255 255 255 / .66);
    --text-faint: rgb(255 255 255 / .3);
    --field-border: rgb(255 255 255 / .2);
    --accent: #C7EA46;
    --accent-text: #C7EA46;
    --on-accent: #14210A;
}

/* ---------------- design-v2: s90-faq.css ---------------- */
/*! design v2 — FAQ (merged)
    ===========================================================================================================
    Four questions, a heading, a kicker and one link out. The smallest section in the design, and the only one
    whose primary control is a NATIVE element rather than a drawing: each row is a `<details>` with a
    `<summary>`.

    THAT CHOICE IS THE SECTION, and it survives the port untouched. A `<details>` is keyboard-operable, is
    exposed as a button that announces its own expanded/collapsed state, needs no `aria-expanded` that can
    drift out of step with the DOM, and — the property a marketing FAQ actually lives or dies on — a browser
    that supports find-in-page inside collapsed disclosures OPENS the matching row for the reader. Replace it
    with a JS accordion and all four of those stop being true in exchange for nothing. So there is no
    JavaScript anywhere in this file's story: the whole open/closed transition is one `transform`, and
    s00-shared.css's reduced-motion block already cuts it inside this scope, so nothing here has to remember.

    WHAT HAD TO BE RE-DECIDED FOR LIGHT — five things, and none of them is a preference:

    1. THE SECTION PAINTS NOTHING. The reference sets `background: var(--ft-ink)` on `.faq`, which on a light
       page is the dark-slab defect this codebase has already found and fixed once. There is no artwork here
       to switch off, so the fix is simpler than s80-clauses.css's: declare `transparent` and let the ambient
       field show through, which is what ft-ui.css already requires of a section — "Transparent, not --canvas:
       an opaque section would cover the ambient field it exists to show". It is DECLARED rather than merely
       omitted, following that same `.band` precedent, because at stage 2 this rule changes scope and a
       section that started painting because some layout rule moved would be a defect nobody would look for
       here.

       The dark reading arrives for free rather than by being reproduced: on dark the field's ground is
       --canvas #0C0D0B against the reference's #08090a — relative luminance 0.00390 vs 0.00269, which is
       19.48:1 vs 19.93:1 against white, i.e. a difference nothing can see.

    2. THE DIVIDERS ARE THE ONLY STRUCTURE, so which token carries them matters. The reference draws them at
       `rgb(255 255 255 / .1)` — white on black. --hairline resolves through --rim-ring to
       `rgba(255, 255, 255, .12)` on dark (the reference's own value, near enough to call identical) and to
       `rgba(20, 23, 15, .10)` on light, which is the same idea with the sign flipped. --field-border was
       considered and REJECTED: WCAG 1.4.11 governs the boundary that IDENTIFIES A CONTROL, and a 3:1 rule
       between rows would make a list of questions look like a table of form fields. These are decorative
       separators sitting on ~55–77px of whitespace per row; --hairline is the right weight and it is the one
       every other divider in the product already uses.

    3. THE ANSWERS ARE NOT DIMMED. The reference sets `rgb(255 255 255 / .68)`; the obvious port is
       --text-muted, and it passes (5.00:1 light, 6.78:1 dark). It is still the wrong call here. These four
       answers are custody of your money, how the scores are computed, what it costs, and whether your record
       moves with you — they are the section's payload, not its atmosphere, and on a light page a dimmed
       paragraph reads as "less important" rather than as the depth-of-field it reads as on black. So
       --text-2: 11.74:1 light, 12.93:1 dark. It is also the token this product already uses for `.lead`, so
       "prominent body copy" is a role the vocabulary had. The hierarchy against the question is carried by
       size, weight and family instead — the call s80-clauses.css already made on its accent card and
       s40-ceiling.css made for its legend.

    4. THE OPEN ROW IS MARKED FOUR WAYS, AND HOVER STILL MOVES WHEN IT IS OPEN. The reference signals
       open/closed with the marker alone — a plus that becomes a minus. That is a shape difference, so it is
       not colour-only, but it is a single signal on a control whose entire job is to have a state. Here:
         · the marker's SHAPE (plus -> minus), which survives forced-colors and colour blindness;
         · the summary's COLOUR, which is also what hover previews;
         · the answer being present, which is the state made out of the content itself;
         · the native `<details>` semantics, which is the one a screen reader actually uses.

       The colour move is measured rather than assumed: opening a row takes its question from --text
       (15.64:1 light / 17.89:1 dark) to --accent-text (5.03:1 / 14.18:1). Both clear AA at every size the
       row takes.

       THE REFERENCE HAS A BUG HERE AND THIS FILE FIXES IT. It sets `:hover` and the open state to the same
       colour, so an open row hovered looks exactly like an open row at rest — no hover feedback on half the
       rows in the list. That is the same failure this codebase already recorded once for `--accent-focus`
       ("setting them equal silently removes hover feedback from every button in the product"). It is a
       specificity bug too: `.faq__item[open] .faq__q` is (0,3,0) and `.faq__q:hover` is (0,2,0), so the open
       state wins and the hover declaration is dead. So hover is --accent-focus — the token this system
       already uses for exactly this (`button:hover`, `.btn:hover`, `.tab.active:hover`, `--primary-hover`) —
       and the open+hover pair is stated explicitly at (0,4,0) so it can win. Measured: --accent-focus is
       6.58:1 light and 16.14:1 dark, and it is a visible step from --accent-text in BOTH directions (darker
       by day, brighter by night), which is the property that token exists to guarantee.

    5. THE MARKER IS DRAWN, NOT TYPED, AND THAT IS NOW A TESTED PROPERTY OF THIS REPOSITORY. ft-ui.css's own
       two disclosures use `content: "+"` / `content: "\2212"`, and a shipped stylesheet had exactly that
       escape mangled into a C1 control character by an escape processor reading `\221` as octal — a tofu box
       on a live page, silent because the file stayed valid UTF-8 and valid CSS. The reference's two-bar
       geometric marker cannot fail that way: no glyph, no codepoint, no font dependency, nothing for an
       escape processor to touch. It is kept for that reason as much as for the animation.

       Its bars are BORDERS rather than backgrounds, which is the other half. A forced-colors repaint drops a
       background and keeps a border — the precedent `.contact-flow` in ft-ui.css already records ("forced-
       colors mode drops box-shadows and repaints backgrounds, which would have erased the whole figure while
       leaving its labels behind"). Under forced-colors the accent hover/open colour is flattened to a system
       colour, so the marker is the signal that has to keep working, and it does.

    NOTHING HERE IS A GRADIENT, A PHOTOGRAPH OR A BLEND MODE. No text sits on a ramp, so the plate-plus-fixed-
    fade structure s40-ceiling.css needed does not arise; there is no full-bleed art, so --v2-art-opacity is
    never consulted; the reference uses no `mix-blend-mode` in this section, so R5 has nothing to refuse.

    NEW TOKENS: NONE — and that is worth recording rather than passing over. Every value this section needs
    (the divider, two text colours, the accent and its hover step, the focus ring, the content measure, a
    radius and a duration) was already in the vocabulary, and --dur is 220ms, which is the reference's own
    0.22s to the millisecond. A section that adds nothing to the token layer is evidence the token layer is
    sized correctly, and it makes this the cheapest section to merge at stage 2.

    STAGE-2 NOTES:
      · `.faq__cta` is the THIRD instance of one reference rule — globals.css declares `.ratchet__cta,
        .clauses__cta, .faq__cta` together and they share every value. s60 and s80 each restated it locally,
        and s80's copy has already lost the reference's `text-underline-offset`. Three copies, one of which
        has already drifted, is the point at which it belongs in s00-shared.css beside `.v2-kicker`, for the
        reason that file already gives: "a component duplicated across two section files is how two copies
        drift". The min-height floor below (see the CTA block) should move with it.
      · THE DISCLOSURE-MARKER TEST NEEDS A DECISION, and it will not fail to tell you. `RenderableCharacters
        Test::test_the_two_disclosure_markers_agree` reads `.notes` (the marketing FAQ) and `.dl-sum` (the
        /download checksum) out of public/ft-ui.css and asserts their markers match, on the stated principle
        that they are "the same affordance in the same design system". This section is a THIRD instance of
        that affordance with a different marker. The test will still PASS after stage 2 — it knows only two
        selectors, and `.notes` survives because marketing/download.blade.php:291 still uses it even once
        welcome.blade.php stops — so the principle would be violated silently. Either the drawn marker becomes
        the system's marker everywhere, or the test learns about the third.
      · `.v2-section` supplies this section's padding; the reference's own `.faq` values are exactly the
        shared rule. The 640px override below wins on SOURCE ORDER, not specificity — both selectors are
        (0,1,0) — so s00-shared.css must keep loading before this file. It does in both harnesses.
      · `faq.key` renders in the shared `.v2-kicker`; `faq.heading`, `faq.1_q`..`4_q` and `faq.1_a`..`4_a` map
        straight onto the classes below. All ten keys have a home and none needs renaming or a migration.
      · `.faq__a` is a `rich` catalog field, so it can carry `<strong>`, `<em>`, `<a>` and `<code>`. Only the
        anchor needs anything, and it inherits ft-ui.css's link treatment.
      · `data-home-section="faq"` must survive onto the real `<section>`. The Control Tower's side-by-side
        preview scrolls to it; drop it and the preview silently stops working, and nothing tests that.
    =========================================================================================================== */

/* ------------------------------------------------------------------ *
 * Section and head
 * ------------------------------------------------------------------ */

/* Declared, not omitted — see note 1. There is nothing in the cascade painting this today; the declaration
   is here so that a future layout rule cannot start. */
:where(body.v2) .faq {
    background: transparent;
}

/* Every property is declared rather than inherited, because ft-ui.css's base `h2` sets margin, font-weight,
   font-size, line-height, letter-spacing and text-wrap, and a heading that took half its values from the base
   and half from the port would move the day either changed. Weight 700 matches the other v2 titles
   (`.ceiling__title`, `.clauses__title`, `.ratchet__title`), not the base's 600. */
:where(body.v2) .faq__title {
    margin: 0;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 3.2vw, 3.75rem);
    font-weight: 700;
    line-height: 1.16;
    letter-spacing: -.02em;
    text-wrap: balance;
}

/* --maxw (1120px), not the reference's 68rem (1088px). 32px apart at the top of the clamp, and using the
   product's own content measure means the page does not end up carrying two systems for the same idea. */
:where(body.v2) .faq__list {
    margin-block-start: clamp(1.5rem, 3vw, 3rem);
    max-width: var(--maxw);
    border-block-start: 1px solid var(--hairline);
}

:where(body.v2) .faq__item {
    border-block-end: 1px solid var(--hairline);
}

/* ------------------------------------------------------------------ *
 * The question — a native <summary>
 * ------------------------------------------------------------------ */

/* THE DISCLOSURE TRIANGLE, removed three independent ways, because no single one covers every engine and
   because each of the three can be lost to an unrelated edit:
     · `list-style: none`         — Blink and Gecko, which render <summary> as `display: list-item`; this is
                                    also what suppresses `::marker`, so there is no fourth thing to write;
     · `display: flex`            — drops the marker in Blink as well, and is the layout this row needs anyway;
     · `::-webkit-details-marker` — WebKit, which draws its marker as a shadow-DOM pseudo-element and ignores
                                    both of the above.
   ft-ui.css's base already declares the first and the third globally, so on this page they are belt and
   braces. They are restated anyway: at stage 2 this rule changes scope, and a triangle that reappeared
   because a base rule moved would be a defect nobody would think to look for in an FAQ. The removal
   deliberately does NOT rest on `display: flex` alone — a flexed <summary> has a history of engine bugs, and
   if it ever has to go back to `display: block` with an inner wrapper, the other two still hold.

   FAMILY: --font-display, which the reference had by default (its <body> is Figtree, so everything is) and
   which the port has to state explicitly because ft-ui.css's body carries the system stack. This is the same
   size, weight and role as `.clause__title` in s80 — clamp(.9375rem, 1.3vw, 1.4375rem) at 700 — and that
   takes --font-display, so the alternative is two typefaces at one size on one page for no reason.

   TOUCH TARGET: `padding-block` bottoms out at 1.05rem on the mobile layer, which with a 1.35 line-height
   puts the row at ~55px at 390px and ~58px at 860px (~77px at 1440px) — comfortably over WCAG 2.5.8's 24px
   and over the 44px platform figure. No separate min-height is needed here, unlike the CTA below, which is
   a bare text link and where the arithmetic comes out the other way. */
:where(body.v2) .faq__q {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    padding-block: clamp(1rem, 1.9vw, 1.6rem);
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(.9375rem, 1.35vw, 1.5rem);
    font-weight: 600;
    line-height: 1.35;
    cursor: pointer;
    list-style: none;
}

:where(body.v2) .faq__q::-webkit-details-marker { display: none; }

/* OPEN — see note 4. --accent-text is 5.03:1 on the light canvas and 14.18:1 on the dark one. The marker
   paints in `currentColor`, so it follows this without a rule of its own. */
:where(body.v2) .faq__item[open] > .faq__q { color: var(--accent-text); }

/* HOVER — a visible step from BOTH resting states, so a closed row previews the open colour and an open row
   still answers the pointer. --accent-focus is this system's hover token, not a value invented here.
   The second selector is (0,4,0) purely so it can outrank `[open]` at (0,3,0); without it the open rows in
   the list would be the ones that stopped responding, which is the reference's bug. */
:where(body.v2) .faq__q:hover,
:where(body.v2) .faq__item[open] > .faq__q:hover { color: var(--accent-focus); }

/* The house focus idiom (`outline: none` + the ring token), not the reference's `outline: 2px solid lime`.
   --ring is theme-aware and its contrast has already been measured for this product — 6.50:1 on the worst
   light surface, 11.07:1 on the worst dark one — where a hardcoded lime outline would be the treatment this
   codebase measured at 2.11:1 and replaced. `:focus-visible`, never `:focus`, so a mouse click does not
   leave a ring behind. Identical to `.notes summary` and `.dl-sum summary` in ft-ui.css, deliberately: a
   keyboard user should not have to learn a second focus vocabulary halfway down one page. */
:where(body.v2) .faq__q:focus-visible {
    outline: none;
    box-shadow: var(--ring);
    border-radius: var(--r-xs);
}

/* ------------------------------------------------------------------ *
 * The marker
 *
 * The reference calls it `.faq__chevron` and draws a PLUS. The name is kept as the reference wrote it, the
 * way s00-shared.css keeps `.pass__kicker` and `.clauses__kicker`, so the port stays diffable against the
 * original — but the drawing is the right one and the name is not: a chevron states a direction, a plus
 * states that there is more, and "there is more" is what a closed disclosure means.
 * ------------------------------------------------------------------ */

:where(body.v2) .faq__chevron {
    position: relative;
    flex: none;
    width: 1rem;
    height: 1rem;
}

/* Borders, not backgrounds — a forced-colors repaint keeps one and drops the other (see note 5).
   `translateY(-50%)` rather than the reference's bare `inset-block-start: 50%`, which leaves a 1.5px bar
   centred 0.75px BELOW the box's middle: invisible on its own, and visible as a wobble when the second bar
   rotates about a centre the first one is not on. The translate is carried through both transform states so
   the rotation still happens about the marker's own centre. */
:where(body.v2) .faq__chevron::before,
:where(body.v2) .faq__chevron::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 0;
    width: 100%;
    height: 0;
    border-block-start: 1.5px solid currentColor;
    transform: translateY(-50%);
    transition: transform var(--dur) var(--ease);
}

/* Two crossed bars closed, one bar open. --dur is 220ms, which is the reference's own 0.22s exactly; --ease
   is the house curve rather than the reference's bare `ease`. s00-shared.css cuts both under
   prefers-reduced-motion for everything in this scope, so the marker still ARRIVES in its correct state —
   it simply stops travelling. */
:where(body.v2) .faq__chevron::after { transform: translateY(-50%) rotate(90deg); }
:where(body.v2) .faq__item[open] .faq__chevron::after { transform: translateY(-50%) rotate(0deg); }

/* ------------------------------------------------------------------ *
 * The answer
 * ------------------------------------------------------------------ */

/* --text-2, not --text-muted — see note 3. `margin` is restated in full because ft-ui.css's base gives every
   `p` a bottom margin of --s-md; the top margin is zero because the summary's own padding already supplies
   the gap, and a second one would open a hole between a question and its answer.

   76ch is kept rather than swapped for --maxw-read (720px, which at this size is nearer 87ch and therefore
   the LOOSER constraint, so it would not constrain anything). A `ch` measure also tracks the font, which is
   the right behaviour for prose.

   `overflow-wrap` is insurance rather than decoration: this is a `rich` catalog field, a staff member can
   paste a URL into it, and neither this section nor `.v2-section` sets an overflow that would catch one. */
:where(body.v2) .faq__a {
    margin: 0 0 clamp(1rem, 1.9vw, 1.6rem);
    max-width: 76ch;
    color: var(--text-2);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    line-height: 1.65;
    overflow-wrap: break-word;
}

/* ------------------------------------------------------------------ *
 * The link out
 *
 * See the stage-2 note: this is one third of a rule the reference declares once for three sections.
 *
 * THE MIN-HEIGHT IS A CONFORMANCE FLOOR, NOT A TOUCH TWEAK, and the arithmetic is why it is here rather
 * than only in the `pointer: coarse` block the reference gives it. The link's own font-size is
 * clamp(.8125rem, 1vw, 1.0625rem) and it inherits the body's 1.47 line-height, so its line box — which IS
 * the hit area for an inline-block — is 19.1px at a 820px window, 21.2px at 1440px, and does not reach the
 * 24px WCAG 2.5.8 minimum until roughly a 1633px viewport. WCAG's "inline" exception does not rescue it:
 * this is a standalone link at the end of a section, not one flowing inside a sentence. So the floor applies
 * to every pointer, and the coarse block below only RAISES it to the 44px platform figure.
 *
 * `inline-flex` is what lets a min-height apply at all, and it is the reference's own solution generalised
 * rather than a third one invented — the coarse block already ships exactly this shape, so no new rendering
 * risk is introduced, only made universal. The `gap` is required by it, for the reason the reference records
 * verbatim: "inline-flex eats the space before a trailing arrow glyph". */
:where(body.v2) .faq__cta {
    display: inline-flex;
    align-items: center;
    gap: .4em;
    min-height: 24px;
    margin-block-start: clamp(1rem, 1.8vw, 1.75rem);
    color: var(--accent-text);
    font-size: clamp(.8125rem, 1vw, 1.0625rem);
    font-weight: 600;
    text-decoration: none;
}

/* The underline is the signal, so hover is not colour-only. `text-underline-offset` is the reference's own
   value; s80's copy of this rule has already lost it, which is the drift the stage-2 note is about. */
:where(body.v2) .faq__cta:hover {
    text-decoration: underline;
    text-underline-offset: .25em;
}

/* ------------------------------------------------------------------ *
 * Mobile
 * ------------------------------------------------------------------ */

@media (max-width: 860px) {
    :where(body.v2) .faq__q {
        gap: 1rem;
        padding-block: 1.05rem;
        font-size: clamp(.9375rem, 4.1vw, 1.125rem);
    }

    :where(body.v2) .faq__a { font-size: clamp(.875rem, 3.7vw, 1rem); }
}

/* The desktop `vw` padding collapses to almost nothing on a phone, so the sections run together. The
   reference writes this against five sections at once (`.pass, .steps, .clauses, .faq, .registry`), i.e.
   against what is now `.v2-section`; here it can only be written against this one, because s00-shared.css
   is not mine to edit. It wins over `.v2-section` on SOURCE ORDER — both selectors are (0,1,0) — which is
   the whole reason the link order in the harnesses is load-bearing. */
@media (max-width: 640px) {
    :where(body.v2) .faq { padding-block: clamp(2.75rem, 11vw, 4rem); }
    :where(body.v2) .faq__cta { font-size: 1rem; }
}

/* Keyed to the POINTER, not the viewport, and the reference's reasoning for that is worth carrying over
   verbatim: "a phone held sideways is 844px wide and misses every width breakpoint above, but the finger is
   the same size". Nothing here changes the layout on a mouse. 44px is a platform constant rather than a
   theme value, so it is a literal — there is nothing for a token to vary. */
@media (pointer: coarse) {
    :where(body.v2) .faq__cta { min-height: 44px; }
}

/* ------------------------------------------------------------------ *
 * Forced colors
 *
 * The focus ring is a `box-shadow`, and forced-colors mode drops box-shadows — so the one indicator a
 * keyboard user navigates by would disappear in exactly the mode somebody chose because they need
 * indicators to be stronger. The outline that was suppressed for the ring comes back, with no colour
 * declared so the UA supplies a system one.
 *
 * The marker needs nothing here: its bars are borders, and a border's colour is forced rather than erased.
 * That matters more than it looks, because the accent hover/open colour IS flattened in this mode — the
 * marker's shape becomes the only visual state signal left, and it is the one that survives.
 * ------------------------------------------------------------------ */

@media (forced-colors: active) {
    :where(body.v2) .faq__q:focus-visible {
        outline: 2px solid;
        outline-offset: 2px;
    }
}

/* ---------------- design-v2: s95-closing.css ---------------- */
/*! design v2 — CLOSING CARD · RISK DISCLAIMER · FOOTER (merged)
    ===========================================================================================================
    THREE BLOCKS WITH THREE OWNERS. The reference ships them as one React component (`SiteFooter.tsx`) because
    on a single-page landing site they are always adjacent. In this product they are not:

      • `.closing`        — a section of the HOME page, CMS-backed (`closing.*`, five keys).
      • `.risk-note--v2`  — a MODIFIER on `partials/risk-note.blade.php`, a SHARED partial two layouts include
                            on any page that shows a score, a performance claim or a sign-up CTA.
      • `.foot`           — SHARED CHROME, rendered on every logged-out page by `partials/site-footer.blade.php`.

    Porting the component as one unit is the specific mistake to avoid, and this codebase has already paid for
    it once: the risk caveat was styled inside the marketing LAYOUT, and because Livewire's `wire:navigate`
    merges the <head> and leaves the previous page's <style> behind, the marketing rules kept governing a
    layouts.public page — measured, the caveat went from `padding-left: 0` to `padding-left: 129px` on
    /contact. The lesson recorded there is the rule followed here: a variant of a shared partial is a MODIFIER
    ON THE PARTIAL, defined once, opted into at the call site.

    So no rule below makes one block depend on another. The caveat renders correctly with no closing card above
    it; the footer renders correctly with no caveat above it; each carries exactly one hairline (its own top
    edge), which is why the three stacked reproduce the reference's rule-above-and-below-the-caveat reading
    without either block owning the other's boundary.

    ---------------------------------------------------------------------------------------------------------
    WHAT HAD TO BE RE-DECIDED FOR LIGHT
    ---------------------------------------------------------------------------------------------------------

    1. THE CARD FOLLOWS THE THEME, and the reference itself is the argument. `SiteFooter.tsx` calls this wash
       "the trust-ceiling wash, mirrored: lime rises off the bottom edge instead of falling from the top".
       s40-ceiling.css ported that section to a card that follows the theme — white on light, near-black on
       dark — so a mirror that stayed permanently dark would disagree with the thing it mirrors on the same
       page. It would also rebuild, one section later, the exact defect this codebase already found and fixed:
       "three full-bleed near-black slabs across an otherwise light page (measured: 2,188px of 6,900px)".

       And it would land the slab in the worst possible place. The shipped stylesheet carries a `:has()` rule
       whose comment records what happens when a dark band meets the footer: "the ledger closes on a DARK
       entry, so measured at the bottom of the home page it read #272729, then 80px of #000000, then the
       #1D1D1F footer. In the shipped LIGHT theme it is worse: 80px of pure white between the dark closing
       band and the parchment footer, which reads as a rendering fault rather than as spacing."

       The alternative reading IS built — `v2-closing-dark` on <body>, at the foot of this file — because it
       is a design call rather than a finding, and the two should be compared rather than argued about. One of
       those two blocks is deleted at stage 2. (It inverts the CARD only. The caveat and the footer below it
       stay on the page ground in both readings: the product's own footer is theme-following today, and a
       reversed final CTA is a composition choice while a reversed footer is a different product.)

    2. THE GLOW BAND AND THE PADDING THAT RESERVES IT ARE NOW ONE TOKEN. This is the same correctness fix the
       ceiling's wash needed, arriving from the other direction — see README finding 4.

       The reference paints the wash at `height: clamp(12rem, 26vw, 29rem)` and reserves it with a card bottom
       padding of `clamp(7.5rem, 17vw, 19rem)`. Two different formulas for one relationship: the band is 60%
       taller than the space kept clear for it, so how far the glow reaches into the copy is a function of
       which clamp branch the viewport lands on, not something the design controls. It survives today only
       because the top of the band is nearly transparent — which is a fact about the gradient's stops, and a
       stop is exactly the kind of thing somebody edits later.

       So `--v2-closing-glow-h` is BOTH the band's height and the card's bottom padding, and the gradient is
       re-anchored to the shorter box rather than the box being grown to the gradient — the reference's own pad
       value is what the composition was tuned around ("it reserves the band the glow rises through"). The
       arithmetic is one ratio: 7.5/12, 17/26 and 19/29 are 0.625, 0.654 and 0.655, so a single re-scale by
       0.654 covers all three branches to within 4.4%. Applied to the reference's stops:

           corner falloff radius-y   58%  / .654 -> 89%
           dome centre-y            124%  / .654 -> 190%
           dome radius-y            112%  / .654 -> 171%
           linear mid stop           36%  / .654 -> 55%
           linear third stop         66%  / .654 -> 101%   (past the top edge, so the ramp ends at 100%)

       Copy can now never enter the glow, at any width, in either theme, by construction.

    3. THE CORNER FALLOFFS FADE TO THE CARD, WHICH IS NOT THE SAME COLOUR IN BOTH THEMES. Every layer of this
       wash is translucent so the card shows through — that is what keeps it a glow instead of the flat plate
       an opaque ramp turns into — and the two corner radials are INK at 44%, whose job is to mute the lime at
       the bottom corners so the glow reads as an arc rather than a band. Over a WHITE card the same ink
       composites to a mid grey, which reads as dirt in the corners rather than as falloff. On light they are
       therefore WHITE at 44%: same job (mute the lime toward the card), same arc, correct ground.

       Both themes also fade to zero alpha OF THE SAME COLOUR rather than to `transparent`, which is
       `rgba(0,0,0,0)` — the premultiplication trap 00-tokens.css names on `--v2-wash-fade`.

    4. THE MOSAIC'S LIGHT TONES WERE AUTHORED FOR A LIME GROUND, AND THIS CARD ALSO ASKS THEM TO SIT ON THE
       CARD. 00-tokens.css says so plainly: "on a wash that runs into white the white plates vanish, so light
       inverts the emphasis: the plates darken the lime instead of lightening it." On the ceiling that is the
       whole story, because its mosaic lives inside the wash. Here the reference deliberately puts the texture
       across the WHOLE card ("above the wash, so the plates and grid rules read over the lime the way they do
       over the ceiling's"), so on light the plates also land on white, behind centred copy.

       Measured, --text-muted (#6a6a6e) over an ink plate on a white card:
           --v2-tex-1  .07  -> 4.66:1   passes
           --v2-tex-2  .11  -> 4.28:1   FAILS AA
       The authored set happens to clear it — the closing variant keeps columns 17-31 empty end to end and its
       nearest tone-1 plates are at columns 12 (25%) and 35 (73%), while the 42ch body spans about 34-66% of
       the card at 1440 and 30-70% at 900. But "the copy happens not to wrap onto a plate" is precisely the
       kind of layout-dependent guarantee finding 4 of the README exists to remove. `--v2-tex-2` is therefore
       capped to .08 on this card on light (4.55:1), which is invisible over the lime and unconditional over
       the card. `--v2-tex-3` needs no cap: it is white, so it lightens the lime and disappears on the card,
       and all six of its cells are inside the glow anyway. The rule grid needs none either — it is masked to
       zero opacity above 26% of the card and only reaches its full .09 at the very bottom edge, inside the
       reserved band where no copy can be.

    5. THE CARD TAKES A HAIRLINE THE COMP DOES NOT HAVE. An ink card inside an ink frame needs no edge; the
       glow defines it. A white card inside a #F6F7F3 frame has no edge at all, and the composition reads as
       text floating on the page rather than as a card. This is the same problem the ratchet card had and it
       takes the same answer, `--v2-card-line`. (The ceiling card is exempt because its top half IS a lime
       plate, so its edge is never in question.)

    6. THE HOVER STEP IS `--accent-focus`, NOT `filter: brightness(1.1)`. Brightening works in one direction
       only: on dark it lifts the lemon, on light it drags the deep lime UP toward the lemon while --on-accent
       stays white, which loses contrast on the one control the whole section exists to get pressed.
       --accent-focus is the pair this product already guarantees is a visible step in both directions —
       measured with --on-accent on it: 7.07:1 on light (#3f6212 + white), 13.90:1 on dark (#DCF57E + #14210A).

    7. THE FOCUS RING IS INSET, and that is arithmetic rather than taste. The comp rings the pill in #fff at
       `outline-offset: 3px`, so the ring's neighbours are the CARD outside and the ACCENT PILL inside, and it
       has to clear 3:1 (WCAG 1.4.11) against both. Nothing in the palette does:
           white          vs lemon pill (dark)      1.37:1   fails
           --accent-focus vs deep-lime pill (light) 1.42:1   fails
           --text         vs white card / deep lime 16.8:1 / 3.36:1 — passes light, but inverts on dark
       At `outline-offset: -4px` the ring's only neighbour is the pill, and --on-accent is the pair already
       verified against exactly that fill: 4.99:1 light, 12.21:1 dark. One declaration, both themes, no
       dependence on what the card behind it happens to be.

    ---------------------------------------------------------------------------------------------------------
    WHAT WAS DELIBERATELY NOT PORTED, AND WHAT THE REFERENCE'S MARKUP OMITS
    ---------------------------------------------------------------------------------------------------------

      • `var(--ft-lime)` / `var(--ft-ink)` IN THE FOOTER MARK. The fragment ships with the reference's two
        tokens, neither of which exists in this tree — and an invalid `fill` presentation attribute falls back
        to the initial value, so pasting it verbatim renders a BLACK square with a near-invisible glyph. They
        are `--accent` / `--on-accent` here, which is also what makes the mark invert correctly: deep lime
        carrying white by day, lemon carrying near-black by night. Note that the mark itself is still an open
        question the README records — this is the reference's FT monogram, and the product ships "Verified
        Ascent". Swapping it is one file and no CSS.

      • THE REFERENCE'S THREE-COLUMN FOOTER IA. `FOOTER_COLUMNS` is three in-page anchor groups, because that
        page is self-contained and "none of the marketing routes are served by this app yet". This product's
        footer carries SIX groups (Product · Marketplaces · Who It's For · Support · Account · Legal), two of
        which are conditional — the Legal column renders only when a CMS page is actually published, so a
        missing or draft page never leaves a 404 link — plus, in the legal strip, a cookie-withdrawal POST and
        the staff Control Tower link. What is ported is the TREATMENT, not the information architecture. The
        column grid is `repeat(auto-fit, minmax(8.5rem, 1fr))` exactly as the reference has it, which already
        takes any column count: measured, six columns wrap to two rows below about 1400px, which is the same
        wrapping the shipped flex footer does today.

      • A SEPARATE `.disclaimer` COMPONENT. The reference's class name is not used at all. Its rules land on
        `.risk-note--v2`, a modifier on the partial this product already ships, for the reason at the top of
        this file. Its `<aside aria-label="Risk disclaimer">` IS adopted, replacing the partial's bare <div>:
        a regulatory caveat a screen-reader user can find by landmark is strictly better than one they cannot,
        and it costs the partial nothing.

      • THE MOBILE FOOTER'S `gap: 0`. At the reference's mobile link size (1rem) that is a tap target of about
        24px. This codebase has already measured and fixed a 38px control against the 44px floor once; the
        links get padding instead of a gap and land at 44px.

    NEW TOKENS, both declared in BOTH themes rather than composed from var() on :root only — the trap
    00-tokens.css documents at length: a custom property's var() references are substituted WHERE IT IS
    DECLARED, so a token built on :root keeps its :root value inside a scope that overrides its inputs.
    =========================================================================================================== */

:root {
    /* The band the glow rises through, AND the card padding that reserves it. One token, because they are one
       relationship — see note 2 in the header. The value is the reference's own PADDING clamp, which is what
       its composition was tuned around; the gradient is re-scaled to fit rather than the box grown to fit the
       gradient. */
    --v2-closing-glow-h: clamp(7.5rem, 17vw, 19rem);

    /* One value, not nine tokens — 00-tokens.css's rule for the ceiling wash, and for the same reason: a
       designed multi-stop gradient is a single decision, and splitting it into stops invites somebody to edit
       one of them. Layers, bottom of the list painted first:
         4  the lime lift off the bottom edge
         3  the dome, wide and shallow-centred so it reads as a dome rather than a spotlight
         2  right corner falloff — what makes the glow an arc instead of a band
         1  left corner falloff
       On light the falloffs fade toward the WHITE card, not toward ink (note 3). */
    --v2-closing-glow:
        radial-gradient(70% 89% at 0% 100%,
            rgb(255 255 255 / .44) 0%, rgb(255 255 255 / 0) 70%),
        radial-gradient(70% 89% at 100% 100%,
            rgb(255 255 255 / .44) 0%, rgb(255 255 255 / 0) 70%),
        radial-gradient(68% 171% at 50% 190%,
            rgb(216 246 78 / .9) 0%,
            rgb(206 236 52 / .7) 24%,
            rgb(184 214 30 / .38) 46%,
            rgb(150 182 24 / .13) 70%,
            rgb(150 182 24 / 0) 88%),
        linear-gradient(0deg,
            rgb(124 154 22 / .34) 0%,
            rgb(62 84 14 / .17) 55%,
            rgb(62 84 14 / 0) 100%);
}

:root[data-theme="dark"] {
    --v2-closing-glow-h: clamp(7.5rem, 17vw, 19rem);

    /* The comp's own wash. The falloffs are the CARD written as a literal — rgb(11 12 13) is --v2-card on
       dark, not the reference's #08090a, because a falloff that fades to a colour the card is not leaves a
       ring at the point where it reaches zero. */
    --v2-closing-glow:
        radial-gradient(70% 89% at 0% 100%,
            rgb(11 12 13 / .44) 0%, rgb(11 12 13 / 0) 70%),
        radial-gradient(70% 89% at 100% 100%,
            rgb(11 12 13 / .44) 0%, rgb(11 12 13 / 0) 70%),
        radial-gradient(68% 171% at 50% 190%,
            rgb(216 246 78 / .9) 0%,
            rgb(206 236 52 / .7) 24%,
            rgb(184 214 30 / .38) 46%,
            rgb(150 182 24 / .13) 70%,
            rgb(150 182 24 / 0) 88%),
        linear-gradient(0deg,
            rgb(124 154 22 / .34) 0%,
            rgb(62 84 14 / .17) 55%,
            rgb(62 84 14 / 0) 100%);
}

/* ================================================================================================== *
 * 1 — THE CLOSING CARD                                                          (home page only)
 *
 * `.closing` itself carries NO rule. The reference's frame for this section is
 * `padding: clamp(.75rem, 1.4vw, 1.75rem)` on the ink, which is exactly --v2-frame-pad on --v2-frame —
 * i.e. `.v2-framed` in s00-shared.css. Restating it here would be a third literal for a value that is
 * already a shared decision, the way s60-ratchet.css records for the same frame.
 * ================================================================================================== */

/* `isolation` is load-bearing rather than decoration: the wash and the texture sit at negative z-index,
   and without a stacking context here they would paint behind the card's own background instead of on
   top of it. */
:where(body.v2) .closing__card {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: grid;
    justify-items: center;
    gap: clamp(.75rem, 1.4vw, 1.375rem);
    /* The deep bottom pad IS the composition — it reserves the band the glow rises through. It is now the
       same token as the band's height, so the two cannot drift (note 2). */
    padding: clamp(3rem, 6.4vw, 7.5rem) clamp(1.25rem, 3.8vw, 5rem) var(--v2-closing-glow-h);
    /* --v2-r-card, not a fourth literal. The reference's own clamp for this card is identical to it, and a
       page that shows the ceiling card, the ratchet card and this one wants all three to agree. */
    border: 1px solid var(--v2-card-line);
    border-radius: var(--v2-r-card);
    background: var(--v2-card);
    text-align: center;
}

/* See note 4. Unconditional rather than geometric: capped where a plate could ever sit behind copy. */
:root:not([data-theme="dark"]) :where(body.v2) .closing__card {
    --v2-tex-2: rgb(20 28 6 / .08);
}

:where(body.v2) .closing__wash {
    position: absolute;
    inset: auto 0 0;
    z-index: -2;
    height: var(--v2-closing-glow-h);
    pointer-events: none;
    background: var(--v2-closing-glow);
}

/* Above the wash, so the plates and the grid rules read over the lime the way they do over the ceiling's —
   and across the whole card, not only the band, because the closing set's weight is authored into the top
   corners as well as the two bottom ones. */
:where(body.v2) .closing__texture {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* THE FILLS ARE SET HERE AS WELL AS ON THE ATTRIBUTE, and the redundancy is deliberate. The mosaic fragment
   carries `fill="var(--v2-tex-N)"`, which is the substitution stage 1 standardised on and which keeps the
   fragment diffable against the reference. But CLAUDE.md records, of the passport credential, that "a
   presentation attribute does not resolve var()" — and if that is true anywhere it is true here, in which
   case the whole texture would silently fail to paint and a contrast audit, which measures text, would not
   notice. A CSS declaration always beats a presentation attribute (which has no specificity at all), so
   these four rules are the authority and the attributes are the mapping. If the measurement pass shows the
   texture painting either way, delete this block, not the attributes. */
:where(body.v2) .closing__texture rect[fill="var(--v2-tex-1)"] { fill: var(--v2-tex-1); }
:where(body.v2) .closing__texture rect[fill="var(--v2-tex-2)"] { fill: var(--v2-tex-2); }
:where(body.v2) .closing__texture rect[fill="var(--v2-tex-3)"] { fill: var(--v2-tex-3); }
:where(body.v2) .closing__texture path { stroke: var(--v2-tex-rule); }

/* THE BADGE IS THE KICKER. s00-shared.css made the outlined section label general so that all eight `*.key`
   catalog fields keep a home, and CONTENT-MAP.md maps `closing.key` here — the comp's own pill reads "Trader
   Passport". So the element carries `v2-kicker closing__badge` and this rule supplies only the deltas: the
   comp's pill radius, its tighter bottom margin (the card's own grid gap does the rest of that spacing), and
   a fill.

   The fill is `--text` at 5%, not `--v2-plate`. The comp is white at 5% on ink, i.e. a step LIGHTER than the
   card; --v2-plate is #050507 on dark, which is a step darker and reads as a hole. Mixing from --text gives
   white-on-dark and near-black-on-light from one declaration, which is the same relationship in both themes.

   The BORDER, the TYPE and the TRACKING are all inherited from .v2-kicker and deliberately not restated. The
   comp draws this pill in the display face at weight 500 and the shared kicker is mono, but by stage 2 every
   section on the page carries one of these, and a single label in a different typeface reads as an oversight
   rather than as emphasis. Both rules are one class inside :where(), so what makes this one win is SOURCE
   ORDER — s00-shared.css must stay linked before this file, as it is in the harness. */
:where(body.v2) .closing__badge {
    margin: 0 0 clamp(.25rem, .7vw, .75rem);
    padding: .55em 1.15em;
    border-radius: var(--r-pill);
    background: color-mix(in srgb, var(--text) 5%, transparent);
}

:where(body.v2) .closing__title {
    margin: 0;
    max-width: 19ch;
    color: var(--text);
    font-family: var(--font-display);
    font-size: clamp(2rem, 4.1vw, 4.5rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -.03em;
    text-wrap: balance;
}

:where(body.v2) .closing__body {
    margin: 0;
    max-width: 42ch;
    color: var(--text-muted);
    font-size: clamp(.9375rem, 1.15vw, 1.3125rem);
    line-height: 1.6;
    text-wrap: pretty;
}

/* The one action on the page. `display: flex` rather than the comp's bare inline anchor: a grid item is
   blockified anyway, and centring the label in a flex box is what lets `min-height` guarantee the 44px touch
   target at EVERY width — the comp reaches it only through its mobile rule, and at the desktop clamp floor
   (.875rem type, .9em pad) the pill is within a couple of pixels of the floor depending on the inherited
   line-height. `justify-items: center` on the card keeps it content-width. */
:where(body.v2) .closing__cta {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    margin-block-start: clamp(.85rem, 1.7vw, 1.85rem);
    padding: .9em 1.9em;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--on-accent);
    font-size: clamp(.875rem, 1.05vw, 1.125rem);
    font-weight: 700;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

/* See note 6. Not `filter: brightness()`, which only works in one of the two themes. */
:where(body.v2) .closing__cta:hover {
    background: var(--accent-focus);
    text-decoration: none;
}

/* The product's universal press state. */
:where(body.v2) .closing__cta:active { transform: scale(.95); }

/* See note 7. Inset, so the ring's only neighbour is the fill it is already verified against. */
:where(body.v2) .closing__cta:focus-visible {
    outline: 2px solid var(--on-accent);
    outline-offset: -4px;
    box-shadow: none;
}

/* ================================================================================================== *
 * 2 — THE RISK DISCLAIMER                                        (shared partial: partials/risk-note)
 *
 * A MODIFIER, opted into at the call site, exactly as `.risk-note--ledger` is. Both this and the
 * baseline are a single class, so what makes it win is SOURCE ORDER — this file loads after
 * ft-ui.css, which is the same condition the ledger variant's own comment records. If these rules are
 * ever merged into ft-ui.css they must land AFTER the `.risk-note` block, not before it.
 *
 * The baseline already supplies `border-top: 1px solid var(--hairline)` and adds no bottom border, so
 * the top rule is not restated here. That single rule is the whole boundary this block owns: the
 * footer supplies its own, and the closing card supplies none — which is what lets any two of the
 * three be absent without leaving a stray or a doubled line.
 * ================================================================================================== */

/* THE CAVEAT'S RULES LIVE IN s00-shared, NOT HERE.
 *
 * This block used to declare `.risk-note--v2` a second time, with its own padding, measure and type. It
 * never took effect on anything they both set: s00-shared writes the modifier at `body.v2` weight (0,2,1)
 * -- deliberately, so it outranks the baseline `.risk-note` -- against this block's `:where(body.v2)`
 * (0,1,0). Only the two properties declared here alone ever reached the page.
 *
 * A mutation test found it: disabling the s00 rule broke nothing, because this copy kept the assertion
 * green. The two were merged into the single declaration in s00-shared, which reproduces what actually
 * rendered; the two properties this block contributed (the frame background and the centring) went with
 * it. A shared partial's modifier belongs with the shared vocabulary, which is what s00-shared is for.
 */

/* ================================================================================================== *
 * 3 — THE FOOTER                                              (shared chrome: partials/site-footer)
 *
 * Class names are `.foot__*`; the shipped footer's are `.foot-*` (`.foot-wrap`, `.foot-links`,
 * `.foot-legal`). Different strings, so the two can coexist through a staged rollout and neither
 * inherits the other's rules by accident — checked against ft-ui.css, which carries no `.foot`,
 * `.closing` or `.disclaimer` of any kind.
 * ================================================================================================== */

:where(body.ft) .foot {
    padding: clamp(2.5rem, 5vw, 5rem) clamp(1rem, 3vw, 4rem) clamp(1.5rem, 3vw, 2.5rem);
    /* This block's own boundary — see the note above section 2. The reference leans on the disclaimer's
       bottom border for it, which fails on every page that carries a footer and no caveat. */
    border-block-start: 1px solid var(--hairline);
    background: var(--v2-frame);
}

:where(body.ft) .foot__grid {
    display: grid;
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    gap: clamp(2rem, 5vw, 5rem);
}

/* THE MARK IS `partials/brand-mark` ("Verified Ascent"), NOT THE REFERENCE'S MONOGRAM.
 *
 * `.foot__mark` used to carry three rules here: a width, `rect { fill: var(--accent) }` and
 * `g { stroke: var(--on-accent) }`. Those describe the reference's FT monogram, which is one SVG holding a
 * tiled rect and a stroked group. This product's mark is not that: the tile is CSS (`.brand-mark`) and the
 * glyph is a single stroked path in `currentColor`, which is what lets it invert with the theme on its
 * own. The standing instruction on this port is to keep it until the monogram can be judged in context,
 * so the footer renders the existing `.brand` lockup and the three rules had no consumer.
 *
 * Removed rather than left: a rule naming an element this product does not ship is a description of a
 * mark somebody would later go looking for.
 */

:where(body.ft) .foot__blurb {
    margin: clamp(.85rem, 1.4vw, 1.25rem) 0 0;
    max-width: 42ch;
    color: var(--text-muted);
    font-size: clamp(.75rem, .92vw, 1rem);
    line-height: 1.6;
}

/* `auto-fit` is what makes the reference's three-column markup carry this product's six without a second
   layout: the track count follows the content. Six columns wrap to two rows below about 1400px, which is
   what the shipped flex footer already does at that width. */
:where(body.ft) .foot__cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: clamp(1.25rem, 2.5vw, 2.5rem);
}

:where(body.ft) .foot__heading {
    margin: 0 0 .7rem;
    color: var(--text);
    font-size: clamp(.75rem, .88vw, .9375rem);
    font-weight: 600;
    letter-spacing: .05em;
    line-height: 1.3;
    text-transform: uppercase;
}

:where(body.ft) .foot__col ul {
    display: grid;
    gap: .45rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* --text-muted, not the comp's white at 62%. Over ink that composites to about 5.4:1; the token is 5.00:1
   on the light frame and holds in both themes without a second value. ft-ui.css's base `a` colours every
   anchor --accent-text, so this has to be stated rather than inherited. */
:where(body.ft) .foot__col a,
:where(body.ft) .foot__legal a {
    color: var(--text-muted);
    font-size: clamp(.75rem, .92vw, 1rem);
    text-decoration: none;
}

/* The comp's accent hover, kept — but with the underline, so the state change is never carried by colour
   alone. Measured: --accent-text is 5.03:1 on the light frame and 14.18:1 on the dark one. */
:where(body.ft) .foot__col a:hover,
:where(body.ft) .foot__legal a:hover {
    color: var(--accent-text);
    text-decoration: underline;
    text-underline-offset: .2em;
}

/* --ring is a box-shadow value, so it cannot be an outline colour; --accent-focus is what it is built from.
   Measured against the frame it sits on: 6.57:1 light, 16.14:1 dark. */
:where(body.ft) .foot__col a:focus-visible,
:where(body.ft) .foot__legal a:focus-visible {
    outline: 2px solid var(--accent-focus);
    outline-offset: 2px;
    border-radius: var(--r-xs);
    box-shadow: none;
}

:where(body.ft) .foot__base {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem 2rem;
    align-items: baseline;
    justify-content: space-between;
    margin-block-start: clamp(2rem, 4vw, 4rem);
    padding-block-start: clamp(1rem, 1.8vw, 1.75rem);
    border-block-start: 1px solid var(--hairline);
}

:where(body.ft) .foot__copy {
    margin: 0;
    max-width: 78ch;
    color: var(--text-muted);
    font-size: clamp(.6875rem, .82vw, .875rem);
    line-height: 1.55;
}

/* The legal strip carries more than the comp's two links: this product puts the cookie-withdrawal control
   and the staff Control Tower entry here (the latter because in the Account column it read as a customer
   feature and was sending customers to the staff login). `flex-wrap` and a row gap are what let it take a
   variable number of items, only some of which render on any given request. */
:where(body.ft) .foot__legal {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1.25rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Withdrawing consent has to be as easy as giving it, so it is a control rather than a link — and a POST,
   because a GET that mutates is prefetchable by any link scanner. It has to read as one of the links beside
   it; `button.linklike` in ft-ui.css already does the reset, so only the colour is restated. The form itself
   must contribute no margin, or the flex row spaces it differently from its neighbours. */
:where(body.ft) .foot__legal form.ftx-choices { margin: 0; }
:where(body.ft) .foot__legal button.linklike {
    color: var(--text-muted);
    font-size: clamp(.75rem, .92vw, 1rem);
}
:where(body.ft) .foot__legal button.linklike:hover {
    color: var(--accent-text);
    text-decoration: underline;
    text-underline-offset: .2em;
}

/* ------------------------------------------------------------------ *
 * Mobile
 * ------------------------------------------------------------------ */

@media (max-width: 860px) {
    :where(body.v2) .closing__title {
        font-size: clamp(1.875rem, 8.4vw, 3rem);
        line-height: 1.1;
    }

    :where(body.v2) .closing__body { font-size: clamp(.9375rem, 3.9vw, 1.0625rem); }

    :where(body.v2) .closing__cta {
        width: min(100%, 22rem);
        min-height: 3.25rem;
        padding-inline: 1.75rem;
    }

    :where(body.ft) .foot__grid { grid-template-columns: minmax(0, 1fr); }

    :where(body.ft) .foot__cols {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: clamp(1.25rem, 5vw, 2rem);
    }

    /* The reference sets `gap: 0` here and raises the links to 1rem, which is a tap target of about 24px.
       Padding instead of a gap keeps the comp's tighter column rhythm AND clears 44px. The consent control
       sits in the same row as the staff link and has to grow with it: `button.linklike` resets itself to
       `display: inline`, which PAINTS vertical padding but does not lay it out, so the display has to be
       restated or that one control stays 24px while the link beside it is 44px. */
    :where(body.ft) .foot__col ul { gap: 0; }
    :where(body.ft) .foot__col a,
    :where(body.ft) .foot__legal a,
    :where(body.ft) .foot__legal button.linklike {
        display: block;
        padding-block: .65rem;
        font-size: 1rem;
    }

    :where(body.ft) .foot__base { gap: 1rem; }
    :where(body.ft) .foot__legal { gap: 0 1.5rem; }

    /* Clear the floating chrome (the assistant launcher, and the cookie notice while it is on screen) and
       the home indicator. `env()` rather than the reference's `--safe-b`, which is its own token; this is
       the form the shipped footer already uses. */
    :where(body.ft) .foot {
        padding-block-end: calc(clamp(4.5rem, 14vw, 6rem) + env(safe-area-inset-bottom));
    }
}

@media (max-width: 640px) {
    /* Centred small print is hard to scan once it wraps to five or six lines on a phone. */
    body.v2 .risk-note--v2 p {
        text-align: left;
        font-size: .8125rem;
    }
}

/* ------------------------------------------------------------------ *
 * COMPARISON MODIFIER — the closing card kept DARK on light.
 *
 * Not a shipping feature, and the same device s80-clauses.css uses for
 * the artwork question: the reading can be looked at rather than
 * argued about. Add `v2-closing-dark` to <body>. Whichever wins, one
 * of these two blocks is deleted at stage 2.
 *
 * Scoped to the CARD, not the body: a reversed final CTA is a
 * composition choice, and a reversed footer is a different product —
 * the shipped footer follows the theme today.
 *
 * --ring IS RE-DECLARED AS ITS FORMULA, not inherited. It is built
 * from --accent-focus on :root, and a custom property's var() is
 * substituted where it is DECLARED — so remapping --accent-focus here
 * without this line would leave every focus ring inside the card
 * resolving against the LIGHT accent on a near-black ground. That is
 * the trap 00-tokens.css names, and ft-ui.css's own `.band--dark`
 * scope carries the same line for the same reason.
 * ------------------------------------------------------------------ */
:root:not([data-theme="dark"]) body.v2-closing-dark .closing__card {
    --v2-card: #0b0c0d;
    --v2-card-line: rgb(255 255 255 / .12);
    --text: #ffffff;
    --text-muted: rgb(255 255 255 / .66);
    --accent: #C7EA46;
    --accent-text: #C7EA46;
    --accent-focus: #DCF57E;
    --on-accent: #14210A;
    --field-border: rgb(255 255 255 / .2);
    --ring: 0 0 0 3px var(--accent-focus);

    --v2-tex-1: rgb(255 255 255 / .05);
    --v2-tex-2: rgb(255 255 255 / .085);
    --v2-tex-3: rgb(0 0 0 / .07);
    --v2-tex-rule: rgb(255 255 255 / .065);

    --v2-closing-glow:
        radial-gradient(70% 89% at 0% 100%,
            rgb(11 12 13 / .44) 0%, rgb(11 12 13 / 0) 70%),
        radial-gradient(70% 89% at 100% 100%,
            rgb(11 12 13 / .44) 0%, rgb(11 12 13 / 0) 70%),
        radial-gradient(68% 171% at 50% 190%,
            rgb(216 246 78 / .9) 0%,
            rgb(206 236 52 / .7) 24%,
            rgb(184 214 30 / .38) 46%,
            rgb(150 182 24 / .13) 70%,
            rgb(150 182 24 / 0) 88%),
        linear-gradient(0deg,
            rgb(124 154 22 / .34) 0%,
            rgb(62 84 14 / .17) 55%,
            rgb(62 84 14 / 0) 100%);

    background: var(--v2-card);
    border-color: var(--v2-card-line);
}


/* ==================================================================== *
 * BROKER INTEGRATION INSTRUCTIONS  (resources/views/crm/instructions.blade.php)
 *
 * The EMBEDDED edition's site root: the page a broker's own developer reads to wire their client
 * portal to this deployment. It is the only customer-facing page in the product whose whole point
 * is code somebody retypes, and nothing in the system styled a code BLOCK — `code, kbd` near the
 * top of this file is an inline chip, and a bare <pre> inherits the browser default, which does
 * not wrap and runs off the side of a 390px screen.
 *
 * SCOPED `body.ly-marketing`, NOT `body.v2`. The layout is the guarantee; `v2` is a per-page opt-in
 * that a later edit could drop, and these rules must not disappear with it. Every value is an
 * existing token, so the block introduces no new colour pair and nothing for the contrast guards
 * to re-derive.
 * ==================================================================== */

/* The four encoding steps. A numbered <ol> with its own marker rather than `list-style: decimal`,
   because the step numbers are read aloud with the step and a CSS marker is not selectable. */
:where(body.ly-marketing) .bi-steps {
    display: grid;
    gap: var(--s-sm);
    margin: 0 0 var(--s-lg);
    padding: 0;
    max-width: 68ch;
    list-style: none;
    color: var(--text-2);
    line-height: 1.55;
}

:where(body.ly-marketing) .bi-steps li {
    display: grid;
    grid-template-columns: 26px 1fr;
    gap: var(--s-xs);
    align-items: start;
}

:where(body.ly-marketing) .bi-steps__k {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}

/* The one sentence on the page an implementer loses a day to. A left rule rather than a card: it is
   an aside to the steps above it, and boxing it would make it a second thing to read rather than an
   emphasis on the first. */
:where(body.ly-marketing) .bi-note {
    max-width: 68ch;
    margin: 0 0 var(--s-lg);
    padding: var(--s-sm) 0 var(--s-sm) var(--s-md);
    border-left: 2px solid var(--accent);
    color: var(--text-2);
    line-height: 1.55;
}

:where(body.ly-marketing) .bi-cap {
    max-width: 68ch;
    margin: 0 0 var(--s-xs);
    font-size: 13px;
    color: var(--text-muted);
}

/* The code block itself.
   SCROLLS RATHER THAN WRAPS, and that is the opposite of the choice `.dl-sum__hash` makes for a
   checksum one screen over. A digest is one token somebody selects whole, so wrapping it costs
   nothing; this is source that will be pasted into an editor, and a soft-wrapped line is read as a
   line break that is not there. `tabindex="0"` in the markup is what makes a scrollable region
   reachable without a mouse (WCAG 2.1.1) — the rule and the attribute are a pair. */
:where(body.ly-marketing) .bi-code {
    margin: 0 0 var(--s-lg);
    padding: var(--s-md);
    overflow-x: auto;
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    background: var(--surface-2);
    color: var(--text-2);
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 13px;
    line-height: 1.6;
    tab-size: 4;
}

/* The inline chip styling would double the background and the padding inside a block that already
   has both, so it is undone for the one <code> that fills a <pre>. */
:where(body.ly-marketing) .bi-code code {
    padding: 0;
    background: none;
    border-radius: 0;
    font-size: inherit;
    color: inherit;
}

:where(body.ly-marketing) .bi-code:focus-visible {
    outline: none;
    box-shadow: var(--ring);
}
