/* ════════════════════════════════════════════════════════════════════════════
   public-unify.css — THE CROSS-SHELL LAYER
   Spec: docs/PUBLIC-SITE-DESIGN-SPEC.md §8 (Motion) and §12 (Cross-page parity)
   ════════════════════════════════════════════════════════════════════════════

   WHY THIS FILE EXISTS
   ────────────────────
   `cfin-tokens.css` DECLARES the shared vocabulary. Measured 2026-08-18 on the
   rig at 1440 and 390, light, all 14 public URLs: almost nothing CONSUMES it.

       --container: 1200px   consumed by ZERO pages.
       Measured content edges instead: 1080 (legal) · 1120 (/reviews)
       · 1238.4 (landing nav) · 1264 (/performance .wrap).
       Spec §4 says "Every page uses the same --container. A visitor should not
       see the content edge move." It moves by 184px.

   A token nobody reads is a comment. `cfin-tokens.css` is loaded FIRST by design
   (so a shell may override it); this file is loaded LAST by design, so the
   convergence rules below actually win over each shell's hard-coded value.

   ⚠ THIS FILE MUST BE THE LAST STYLESHEET IN THE <head> OF EVERY PUBLIC SHELL.
     Load it before a shell's own CSS and every rule here silently loses.

   THE SHELL COUNT WAS WRONG
   ─────────────────────────
   cfin-tokens.css's own header enumerates FIVE public shells. Measured, there
   are SEVEN surfaces, and the two it omits are exactly the two that never got
   the token layer:

       6. /reviews      templates/reviews.html — self-contained, loads only
                        theme.min.css + design-system.css. Measured: --container,
                        --gutter, --measure, --type-body, --btn-radius all UNSET;
                        its own .rvp-nav with 2 links (vs 13); NO <footer> AT ALL.
       7. /unsubscribe  NOT a template — a hard-coded HTML f-string in
                        app.py:9321-9345 with literal #1c1c1e/#2c2c2e/#34c759.
                        It renders DARK on a site that defaults to LIGHT. Cannot
                        be reached from CSS; fix is an app.py change. Reported,
                        not fixed here.

   PERFORMANCE CONTRACT (measured baseline: 14.4% dropped frames at 1440)
   ─────────────────────────────────────────────────────────────────────
   · This file adds ZERO animations and ZERO infinite loops. It defines tokens
     and static geometry only. Net loop count change: 0.
   · Every custom property below is STATIC — set once at parse time, never
     animated, never written from script. The 22.6ms/frame style-recalc incident
     came from ANIMATING a property on :root, which invalidates all 3,429
     elements. Declaring one is free; mutating one is not. Do not write to these
     from JS. If you need a per-frame value, scope it to the leaf that uses it.
   ════════════════════════════════════════════════════════════════════════════ */


/* ────────────────────────────────────────────────────────────────────────────
   1. THE MOTION SYSTEM — one easing set, one period lattice, one damping
   ────────────────────────────────────────────────────────────────────────────
   Motion arrived in three passes with three rationales and was never written
   down. Measured across the public CSS 2026-08-18:

       220 unique @keyframes names   (not 97 — that was landing.css alone;
                                      theme.css carries 108 more)
       18 distinct easing values in 1210 declarations

   The two easings this system is built on were, at the time of writing, the
   two LEAST used in the codebase:

       cubic-bezier(.37,0,.63,1)     7 uses    <- ambient/pendular
       cubic-bezier(.16,1,.3,1)      8 uses    <- settle (implies mass)
       cubic-bezier(.22,1,.36,1)    97 uses    <- the de-facto incumbent

   So the incumbent is RATIFIED here as --ease-entrance rather than pretended
   away: a 97-site rewrite inside contended files is not a unification, it is a
   merge conflict. Three named roles, each with one curve, is the system. */

:root {
    /* --- Easing: three roles, three curves, no fourth. --------------------- */

    /* Pendular and ambient: anything that loops forever. Symmetric, so the
       element spends equal time either side of rest and never appears to
       "start". easeInOutSine. */
    --ease-ambient:  cubic-bezier(.37, 0, .63, 1);

    /* Settles that imply mass: a card landing, a sheet coming to rest, a
       number counting to its final value. Fast out, long tail. */
    --ease-settle:   cubic-bezier(.16, 1, .3, 1);

    /* Entrances and UI state changes. The incumbent curve, 97 call sites. */
    --ease-entrance: cubic-bezier(.22, 1, .36, 1);

    /* ⚠ SCROLL-SCRUBBED KEYFRAMES MUST USE THIS AND ONLY THIS.
       On a scroll timeline the clock IS the scrollbar, so an eased curve makes
       the element lag the finger through the middle of the gesture and then
       snap at the end. Any `animation-timeline: scroll()/view()` rule that does
       not set `animation-timing-function: var(--ease-scrub)` is a bug. */
    --ease-scrub:    linear;

    /* --- Duration: UI transitions (finite, user-triggered). --------------- */
    --dur-micro:    120ms;  /* hover/focus tint, checkbox */
    --dur-ui:       220ms;  /* the default; buttons, links, small reveals */
    --dur-emphasis: 420ms;  /* modal, drawer, anything that changes the page */

    /* --- Ambient period lattice: COPRIME, so nothing ever syncs. ----------
       Measured 2026-08-18 across the public CSS, the periods on infinite
       animation declarations cluster hard on a harmonic lattice:

           2s  x22 uses      4s  x13 uses      1.8s x9      4.2s x6

       2s and 4s are harmonically related (4 = 2x2), so those 35 declarations
       re-phase together every 4 seconds — that is the visible collective pulse.
       The prime-second periods below share a common multiple of 11*13*19 =
       2717s (45 minutes), so a visitor never sees the site breathe in unison.

       Pick by ROLE, not by taste, and never round one to "about 12s". */
    --period-a:    11s;   /* primary ambient drift */
    --period-b:    13s;   /* secondary, counter-phase */
    --period-c:    19s;   /* tertiary / slowest background */
    --period-fast:  7s;   /* small accents only */
    --period-slow: 23s;   /* full-bleed backdrops, aurora */

    /* --- Amplitude ladder: PIXELS, inverse to element size. ---------------
       A percentage amplitude is wrong at both ends of the scale: 1% of a
       64px chip is 0.6px (invisible) and 1% of a 900px hero is 9px (seasick).
       Amplitude is therefore absolute and shrinks as the element grows.
       Choose the rung by the animated element's LARGEST dimension. */
    --amp-chip:   6px;    /* <  80px  — dots, pills, icons */
    --amp-card:   4px;    /* <  240px — cards, badges, small media */
    --amp-panel:  3px;    /* <  480px — panels, illustrations */
    --amp-hero:   2px;    /* <  900px — hero art, device mockups */
    --amp-canvas: 1.5px;  /* >= 900px — full-bleed backdrops */

    /* --- Damping: ONE constant for everything scroll-driven. -------------
       `--p` and `--turn` already share a single damping factor; this is that
       constant promoted to a name so the next scroll-driven thing uses it
       instead of inventing a second feel. Lower = heavier/laggier. */
    --damp: .12;
}


/* ────────────────────────────────────────────────────────────────────────────
   2. REDUCED MOTION — one backstop for the whole public site
   ────────────────────────────────────────────────────────────────────────────
   Every public stylesheet today carries its own `prefers-reduced-motion` block
   (landing.css alone has 65). That is per-file opt-IN: the next stylesheet any
   builder adds is unprotected until someone remembers. This is the opt-OUT
   backstop, and because this file loads last it also catches rules that an
   earlier file's own block missed.

   Deliberately NOT `animation: none` — killing an animation mid-way leaves the
   element at its keyframe-0 transform, which has hidden content before now.
   Collapsing the duration instead lands every animation on its FINAL frame. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
        scroll-behavior: auto !important;
    }
}


/* ────────────────────────────────────────────────────────────────────────────
   3. THE CONTENT EDGE — spec §4's headline rule, actually applied
   ────────────────────────────────────────────────────────────────────────────
   Spec §4: "Every page uses the same --gutter and the same --container. A
   visitor should not see the content edge move when navigating between pages.
   This single rule does more for perceived coherence than any colour change."

   Measured before (1440, light):
       /disclaimer /privacy /cookies /acceptable-use  .legal-container  1080px, pad-x 32
       /reviews                                       .rvp-wrap         1120px, pad-x 24
       landing                                        .nav-container    1238.4px
       /performance                                   .wrap             1264px

   The two reached from here converge onto var(--container). The landing and
   /performance edges live in landing.css and perf-page.css, which are held by
   other builders — they are REPORTED, not reached across for.

   Prose is unaffected: legal body copy is separately capped at 648px
   (~86ch) and /reviews at 636px, both well inside these containers. */
.legal-container,
.rvp-wrap {
    max-width: min(var(--container, 1200px), 100%);
    padding-left: var(--gutter, clamp(1rem, 4vw, 2rem));
    padding-right: var(--gutter, clamp(1rem, 4vw, 2rem));
    margin-left: auto;
    margin-right: auto;
}


/* ────────────────────────────────────────────────────────────────────────────
   4. HEADING LEADING — --leading-head, which nothing was reading
   ────────────────────────────────────────────────────────────────────────────
   `--leading-head: 1.25` is declared in cfin-tokens.css and consumed by no one.
   Measured h1 line-height / font-size at 1440, light:

       landing        92.4 / 88   = 1.05
       /performance   58.24 / 56  = 1.04
       legal x4       63 / 42     = 1.50   <- body leading on a 42px heading
       /reviews       69 / 46     = 1.50
       auth shell     48 / 32     = 1.50
       thankyou/pay   42 / 28     = 1.50

   1.5 is prose leading. On a 42px heading it opens a 21px gap between lines and
   the heading stops reading as one object. The display shells already sit near
   1.05; this brings the text shells to the declared 1.25 rather than dragging
   everything to one number, because a 28px page title and an 88px hero legitimately
   want different tightness — that is what a token with one value per role is for.

   Scoped to the shells measured at 1.5. Landing and /performance are already
   tighter than 1.25 by their own art direction and are left alone. */
.legal-container h1, .legal-container h2,
.rvp-wrap h1, .rvp-wrap h2,
.auth-container h1, .auth-container h2 {
    line-height: var(--leading-head, 1.25);
}


/* ────────────────────────────────────────────────────────────────────────────
   5. /reviews AND THE TYPE SCALE — deliberately NOT forced
   ────────────────────────────────────────────────────────────────────────────
   /reviews now links cfin-tokens.css, so the scale RESOLVES here for the first
   time. Its own sizes are not overridden, and that is a considered decision
   rather than an omission:

       .rvp-hero h1  clamp(30px, 5vw, 46px)   vs token clamp(30px, .., 48px)
                     -> identical at 390, 2px apart at 1440.

   Overriding it buys 2px. The cost is real: `.rvp-wrap p` (0,1,1) out-specifies
   BOTH `.rvp-body` (0,1,0) and `.rvp-disclaimer` (0,1,0), so a blanket paragraph
   rule silently drags the 15px review text and the intentional 13px disclaimer
   caption up to 17px. That is a regression bought for 2px of theoretical
   convergence.

   The page's real divergences were structural, not typographic — no token layer
   (fixed), a 1120px content edge (fixed in §3), a 2-link nav and NO footer
   (both reported; they are template-level and belong to that page's owner). */


/* ────────────────────────────────────────────────────────────────────────────
   6. BODY COPY BELOW THE 15px FLOOR
   ────────────────────────────────────────────────────────────────────────────
   cfin-tokens.css and spec §3 both state: "Body copy never below 15px; captions
   never below 12px (11px is banned on the public site)."

   Measured /forgot-password and /reset-password at BOTH 1440 and 390: body copy
   14px / line-height 21px. That is the only public page under the floor, and it
   is the page a locked-out customer reads under stress.

   Raised to the token (15 -> 17px fluid). Scoped to the prose element only, so
   auth captions, field hints and legal microcopy keep their own smaller sizes. */
.auth-container > p,
.auth-card > p,
.auth-card > .auth-sub {
    font-size: var(--type-body, clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem));
    line-height: var(--leading-body, 1.6);
}

/* ============================================================================
   TAP TARGETS FOR THE TWO GLOBAL COMPONENTS (2026-08-29)
   ----------------------------------------------------------------------------
   The beta banner and the cookie-consent modal render on EVERY public page, so
   their inline links fail WCAG 2.5.8 (AA, 24px) everywhere at once. Measured at
   390x844 on /performance and /register:

       span.beta-text-full > a   h=15   "support@cfin.be"
       p.cookie-policy-link > a  h=16   "Cookie Policy"

   ⚠ WHY HERE AND NOT IN public-subpages.css. That file already carries the
     equivalent rule, but it is only loaded by the legal pages and /faq —
     /performance and /register load public-unify.css instead, so the identical
     markup was failing on exactly the pages the fix did not reach. A component
     that renders on every page needs its rule in a stylesheet every page loads;
     scoping it to one page family is how this was missed the first time.
   ⚠ PADDING, NOT min-height — both are inline links inside a sentence. See the
     longer note in public-subpages.css for the reasoning.
   ============================================================================ */
.beta-text-full a,
.cookie-policy-link a,
.cookie-policy-link {
    padding-block: 5px;
    margin-block: -5px;
}
