/* ============================================================================
 * foundation.css — motion, typography, materials and accessibility.
 *
 * Split out of tokens.css so the public landing page can pull in the same
 * foundation WITHOUT the webfont @import at the top of tokens.css — the
 * landing layout already links its own font subset, and a second blocking
 * font request on the marketing page is exactly the kind of latency that
 * makes an interface stop feeling immediate.
 *
 * Colour tokens live in tokens.css. Every reference below carries a literal
 * fallback so this file is also correct standalone, on the landing page,
 * where only the --pz-* palette is defined.
 * ========================================================================== */
:root {
    /* ══ MOTION ═══════════════════════════════════════════════════
     * Springs, not durations. Each curve below is a numerically solved
     * spring expressed as a CSS linear() easing, using Apple's two
     * designer-facing parameters instead of mass/stiffness/damping:
     *
     *   damping ratio — 1.0 settles with no overshoot; < 1.0 bounces.
     *   response      — how fast it reaches the target, in seconds.
     *
     * A spring has no duration; the paired *-ms value is the measured
     * settle time of that curve, so the two MUST be used together.
     *
     * Rule of thumb: default to `snap` (critically damped). Reach for a
     * bouncy curve ONLY when the gesture itself carried momentum — a
     * flick, a throw, a drag release. Overshoot on a menu that merely
     * appeared reads as a glitch; overshoot on a card you threw reads
     * as physics.
     */
    --spring-snap:      linear(0, 0.0314, 0.1052, 0.1993, 0.2997, 0.398, 0.4894, 0.5716, 0.6437, 0.7059, 0.7587, 0.8031, 0.8401, 0.8707, 0.8958, 0.9163, 0.9329, 0.9464, 0.9573, 0.966, 0.973, 0.9786, 0.9831, 0.9866, 0.9895, 0.9917, 0.9935, 0.9949, 0.996, 0.9968, 0.9975, 0.9981, 0.9985, 0.9988, 1);
    --spring-snap-ms:   445ms;   /* damping 1.0, response 0.30 — default UI */

    --spring-smooth:    linear(0, 0.031, 0.1042, 0.1976, 0.2974, 0.3953, 0.4865, 0.5685, 0.6407, 0.703, 0.756, 0.8006, 0.8378, 0.8686, 0.894, 0.9147, 0.9316, 0.9453, 0.9563, 0.9652, 0.9723, 0.978, 0.9826, 0.9862, 0.9891, 0.9914, 0.9932, 0.9947, 0.9958, 0.9967, 0.9974, 0.998, 0.9984, 0.9988, 1);
    --spring-smooth-ms: 590ms;   /* damping 1.0, response 0.40 — move / reposition */

    --spring-sheet:     linear(0, 0.0272, 0.0952, 0.1869, 0.29, 0.3954, 0.497, 0.5908, 0.6746, 0.7475, 0.8093, 0.8604, 0.9019, 0.9347, 0.9601, 0.9791, 0.993, 1.0026, 1.0089, 1.0127, 1.0146, 1.0152, 1.0147, 1.0137, 1.0123, 1.0107, 1.0091, 1.0076, 1.0062, 1.0049, 1.0038, 1.0029, 1.0021, 1.0015, 1);
    --spring-sheet-ms:  405ms;   /* damping 0.8, response 0.30 — drawer / sheet */

    --spring-bounce:    linear(0, 0.0272, 0.0952, 0.1869, 0.29, 0.3954, 0.497, 0.5908, 0.6746, 0.7475, 0.8093, 0.8604, 0.9019, 0.9347, 0.9601, 0.9791, 0.993, 1.0026, 1.0089, 1.0127, 1.0146, 1.0152, 1.0147, 1.0137, 1.0123, 1.0107, 1.0091, 1.0076, 1.0062, 1.0049, 1.0038, 1.0029, 1.0021, 1.0015, 1);
    --spring-bounce-ms: 540ms;   /* damping 0.8, response 0.40 — flick / throw landing */

    /* Non-spatial changes (color, opacity, border) don't need physics —
     * they need to be imperceptibly fast. Never spring a hover tint. */
    --ease-out:   cubic-bezier(0.22, 0.61, 0.36, 1);
    --dur-instant: 90ms;    /* press feedback, hover tints */
    --dur-quick:   140ms;   /* small state changes */

    /* Mirror of --ease-out, for the return leg of a reversible
     * transition — the outbound and inbound paths must match. */
    --ease-out-reverse: cubic-bezier(0.64, 0, 0.78, 0.39);

    /* Press feedback. Scale — not transform — so it composes with any
     * transform a component already sets instead of clobbering it. */
    --press-scale:       0.97;
    --press-scale-sm:    0.94;   /* icon buttons: smaller target, needs more travel to read */

    /* ══ TYPOGRAPHY ═══════════════════════════════════════════════
     * Tracking is size-specific, never one value for everything. As type
     * grows the letterforms read too far apart, so large text wants
     * NEGATIVE tracking; small text wants slightly positive to stay
     * legible. Leading moves inversely to size: tight on headings,
     * open on body copy.
     */
    --track-display: -0.022em;   /* 32px+ */
    --track-title:   -0.014em;   /* 20–31px */
    --track-heading: -0.008em;   /* 16–19px */
    --track-body:     0;         /* 14–15px */
    --track-small:    0.006em;   /* 12–13px */
    --track-caps:     0.06em;    /* all-caps micro-labels */

    --leading-display: 1.06;
    --leading-title:   1.2;
    --leading-heading: 1.35;
    --leading-body:    1.55;
    --leading-dense:   1.4;      /* tables, lists, information-dense UI */

    /* ══ MATERIALS & DEPTH ════════════════════════════════════════
     * Translucency is a functional floating layer: it brings structure
     * without stealing focus, and it tells you content continues under
     * the chrome. Bigger surfaces read as thicker — stronger blur and a
     * deeper shadow than small chips.
     */
    --material-thin:    saturate(180%) blur(12px);   /* chips, small popovers */
    --material-regular: saturate(180%) blur(20px);   /* toolbars, menus */
    --material-thick:   saturate(180%) blur(32px);   /* sheets, full panels */

    --material-bg:        rgba(255, 255, 255, 0.72);
    --material-bg-strong: rgba(251, 248, 241, 0.86);
    --material-edge:      rgba(255, 255, 255, 0.55); /* bright top edge — light catching the material */

    /* Elevation grows with surface size, matching the blur scale. */
    --elev-1: 0 1px 2px rgba(32, 40, 31, .05), 0 2px 6px rgba(32, 40, 31, .05);
    --elev-2: 0 4px 10px rgba(32, 40, 31, .06), 0 14px 32px -14px rgba(32, 40, 31, .20);
    --elev-3: 0 10px 30px -8px rgba(32, 40, 31, .12), 0 30px 60px -20px rgba(32, 40, 31, .24);

    --scrim: rgba(32, 40, 31, 0.34);   /* dim-to-focus behind modal tasks */
}

/* ═══════════════════════════════════════════════════════════════
   BASE LAYER — fluid-interface defaults
   ───────────────────────────────────────────────────────────────
   Everything here is wrapped in :where(), which contributes ZERO
   specificity. These rules fill gaps; any existing component rule
   still wins without needing !important. Adding a foundation must
   never be a visual regression.
   ═══════════════════════════════════════════════════════════════ */

/* Kill the ~300ms tap delay. Latency is where directness dies — the
   moment lag appears the interface stops feeling connected to you. */
:where(html) { -webkit-text-size-adjust: 100%; }
:where(body) { touch-action: manipulation; }

/* We draw our own press state (below), so suppress the platform's
   delayed grey flash — it fires late and fights our feedback. */
:where(a, button, [role="button"], summary, label, input, select, textarea) {
    -webkit-tap-highlight-color: transparent;
}

/* ── Response: feedback on pointer-DOWN, never on release ────────
   A control must acknowledge the press the instant it happens.
   Waiting for click/touch-up to show anything reads as dead.
   `scale` is used rather than `transform` so components that already
   translate/rotate keep doing so while pressed.

   NOTE the second :where() around the :not() clauses. :not() takes the
   specificity of its most specific argument, so writing this as
   `:where(…):not(:disabled):not([aria-disabled])` would score (0,2,0)
   and silently outrank every component's own `transition` declaration
   across the app — dropping their box-shadow and transform transitions.
   Wrapping the negations keeps the whole selector at zero, which is the
   only thing making this base layer safe to add to an existing app. */
:where(button, [role="button"], .btn, .t-btn, a.rail-link, button.rail-link,
       .pz-btn, .settings-popover-item, .notif-item):where(:not(:disabled, [aria-disabled="true"])) {
    transition: scale var(--dur-instant) var(--ease-out),
                background-color var(--dur-instant) var(--ease-out),
                border-color var(--dur-instant) var(--ease-out),
                color var(--dur-instant) var(--ease-out);
}
/* The press state itself is allowed one unit of weight — it has to beat a
   component's base rule to be visible at all — but no more, so a
   component's own :active still wins. */
:where(button, [role="button"], .btn, .t-btn, a.rail-link, button.rail-link,
       .pz-btn, .settings-popover-item, .notif-item):where(:not(:disabled, [aria-disabled="true"])):active {
    scale: var(--press-scale);
}
/* Icon-only controls are small; they need more travel to register. */
:where(.icon-btn, .rail-toggle, .notif-x, .rail-bell):active { scale: var(--press-scale-sm); }

/* ── Touch targets ───────────────────────────────────────────────
   Keyed on pointer type, not viewport width: what makes a 38px control
   hard to hit is a fingertip, not a narrow window. A mouse on a small
   screen keeps the tighter, denser layout; a touchscreen on a large one
   still gets targets it can land on.
   min-height rather than height, so nothing that has grown to fit its
   content gets clamped back down.

   These are the one place in this file that use !important, and deliberately.
   Everything else here is a zero-specificity default that any component may
   override; a minimum touch target is not a preference to be out-voted. The
   rule was written with :where() first and lost to `.rail-link { min-height:
   38px }`, which is exactly the override that must not win. Scoped to coarse
   pointers, so no mouse-driven layout is affected. */
@media (pointer: coarse) {
    button, [role="button"], .btn, .t-btn, .rail-link, .icon-btn,
    .rail-toggle, .settings-popover-item, select {
        min-height: 44px !important;
    }
    /* Icon-only controls need width too — 44px tall and 22px wide is still
       a miss. */
    .icon-btn, .rail-toggle, .notif-x { min-width: 44px !important; }
    /* Form controls: the 16px floor is also what stops iOS zooming the page
       on focus, which silently breaks the layout you designed. */
    input:not([type="checkbox"]):not([type="radio"]), textarea {
        min-height: 44px;
        font-size: max(16px, 1em);
    }
}

/* ── Focus: always visible for keyboard, never a mouse artifact ── */
:where(a, button, [role="button"], input, select, textarea, [tabindex]):focus-visible {
    outline: 2px solid var(--brand, #2e3f2c);
    outline-offset: 2px;
    border-radius: inherit;
}

/* ── Typography: size-specific tracking and leading ──────────────
   A single letter-spacing value is wrong at some size by definition.
   These defaults key tracking to the role of the text; components that
   set their own tracking are unaffected (zero specificity). */
:where(h1) { letter-spacing: var(--track-display); line-height: var(--leading-display); }
:where(h2) { letter-spacing: var(--track-title);   line-height: var(--leading-title); }
:where(h3) { letter-spacing: var(--track-heading); line-height: var(--leading-heading); }
:where(h4, h5, h6) { letter-spacing: var(--track-heading); line-height: var(--leading-heading); }
:where(body) { letter-spacing: var(--track-body); }
:where(small, .text-small, .muted-sm) { letter-spacing: var(--track-small); }

/* Text scaled up by the user must not be clipped by a fixed-height box. */
:where(button, .btn, .pz-btn, input, select) { line-height: var(--leading-heading); }

/* ── Material helpers ────────────────────────────────────────────
   Opt-in classes. Bigger surface → thicker blur → deeper shadow. */
.material-thin {
    background: var(--material-bg);
    backdrop-filter: var(--material-thin);
    -webkit-backdrop-filter: var(--material-thin);
    box-shadow: var(--elev-1);
}
.material-regular {
    background: var(--material-bg);
    backdrop-filter: var(--material-regular);
    -webkit-backdrop-filter: var(--material-regular);
    box-shadow: var(--elev-2);
    border-top: 1px solid var(--material-edge);
}
.material-thick {
    background: var(--material-bg-strong);
    backdrop-filter: var(--material-thick);
    -webkit-backdrop-filter: var(--material-thick);
    box-shadow: var(--elev-3);
    border-top: 1px solid var(--material-edge);
}

/* Where floating chrome overlaps scrolling content, fade the content
   out under it instead of drawing a 1px rule. A hard divider claims
   the content stops there; the fade says it continues beneath. */
.scroll-edge-top {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 18px);
            mask-image: linear-gradient(to bottom, transparent 0, #000 18px);
}
.scroll-edge-bottom {
    -webkit-mask-image: linear-gradient(to top, transparent 0, #000 18px);
            mask-image: linear-gradient(to top, transparent 0, #000 18px);
}

/* Dim-to-focus scrim for genuinely modal tasks. A parallel, non-blocking
   panel should NOT use this — it would break the user's flow for no
   reason; translucency and offset are enough to signal a new layer. */
.scrim {
    position: fixed;
    inset: 0;
    background: var(--scrim);
    opacity: 0;
    transition: opacity var(--spring-snap-ms) var(--spring-snap);
}
.scrim.is-open { opacity: 1; }

/* ── Materialize / dematerialize ─────────────────────────────────
   Driven by Fluid.materialize(). Blur, scale and opacity move together
   so a translucent surface reads as a material forming rather than a
   picture fading in — and both legs run from the anchor point set on
   transform-origin, so the surface grows out of, and returns to, the
   control that owns it. */
@keyframes fluid-materialize {
    from { opacity: 0; scale: 0.92; filter: blur(6px); }
    to   { opacity: 1; scale: 1;    filter: blur(0); }
}
@keyframes fluid-dematerialize {
    from { opacity: 1; scale: 1;    filter: blur(0); }
    to   { opacity: 0; scale: 0.94; filter: blur(4px); }
}
.fluid-materialize {
    animation: fluid-materialize var(--spring-snap-ms) var(--spring-snap) both;
}
/* The exit mirrors the entry curve rather than reusing it, so the return
   leg retraces the outbound path instead of accelerating away. */
.fluid-dematerialize {
    animation: fluid-dematerialize var(--dur-quick) var(--ease-out-reverse) both;
}

/* ═══════════════════════════════════════════════════════════════
   ACCESSIBILITY — three independent signals, three distinct answers
   ═══════════════════════════════════════════════════════════════ */

/* Reduced motion is not "no feedback" — it's a gentler, non-vestibular
   equivalent. Spatial travel and overshoot go; opacity and color changes
   stay, because those are what carry the meaning. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --spring-snap:      var(--ease-out);
        --spring-smooth:    var(--ease-out);
        --spring-sheet:     var(--ease-out);
        --spring-bounce:    var(--ease-out);
        --spring-snap-ms:   140ms;
        --spring-smooth-ms: 140ms;
        --spring-sheet-ms:  140ms;
        --spring-bounce-ms: 140ms;
        --press-scale:      1;
        --press-scale-sm:   1;
    }
    /* Never let a decorative loop run; never let a large surface travel. */
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }

    /* One deliberate exception: progress indicators. A spinner is STATUS, not
       decoration — freezing it makes a working app look hung, and that costs
       more comprehension than the motion saves. These are small, centred and
       non-vestibular, which is the acceptable kind of motion; they keep
       running, just calmer.
       Pulsing status DOTS are not exempt, and deliberately so: their colour
       already carries the state, so the pulse is emphasis rather than
       information, and nothing is lost when it stops. */
    [class*="spinner"], [class*="spin"], [class*="skel"] {
        animation-duration: 1.4s !important;
        animation-iteration-count: infinite !important;
    }
}

/* Frostier, not clearer: raise the background toward opaque and drop the
   blur, so translucent chrome stops competing with the text on it. */
@media (prefers-reduced-transparency: reduce) {
    :root {
        --material-bg:        var(--bg-panel, #ffffff);
        --material-bg-strong: var(--bg-panel, #ffffff);
        --material-thin:      none;
        --material-regular:   none;
        --material-thick:     none;
    }
    .material-thin, .material-regular, .material-thick {
        background: var(--bg-panel, #ffffff);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
}

/* Solid surfaces with a defined, contrasting edge. */
@media (prefers-contrast: more) {
    :root {
        --border:      #8f8a78;
        --border-soft: #a8a294;
        --text-muted:  #4d5448;
        --text-dim:    #5d6458;
        --material-bg:        var(--bg-panel, #ffffff);
        --material-bg-strong: var(--bg-panel, #ffffff);
    }
    .material-thin, .material-regular, .material-thick {
        background: var(--bg-panel, #ffffff);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        border: 1px solid var(--text, #20281f);
    }
    :where(a, button, [role="button"], input, select, textarea):focus-visible {
        outline-width: 3px;
    }
}
