/* ============================================================================
   Auracle web design system — "The Ledger"
   Token layer. Authority: /DESIGN.md (2026-08-13).

   This file is ADDITIVE and is deliberately not linked by any page yet. Pages
   adopt it slice by slice during the re-skin. Nothing here may depend on
   factory.css or index.css, and nothing here may be overridden by them.

   Rules this file encodes:
     - Light-first: cream ground, ink text, full-bleed black bands as the
       counter-material. On black, text is white at opacity steps, never a
       grey hex.
     - One accent (orange). It marks state or a verb. It is never decoration,
       never tinted, never gradient.
     - Two faces, no serif. Geist Sans for display and body, Geist Mono for
       eyebrows, nav, ledgers, tickers and all numbers.
     - No glass, no glow, no gradients. Hairlines and material contrast do the
       work.
     - Motion is enhancement. Content renders fully without it.

   Any /css edit MUST bump the ?v= token on every stylesheet link that ships it.
   ========================================================================= */

/* ----------------------------------------------------------------------------
   1. Type. Self-hosted, license-clean, woff2 only, no CDN.

   Source: `geist` npm package v1.7.2 (Vercel + basement.studio), SIL OFL 1.1.
   License text ships beside the files at /assets/fonts/geist/OFL.txt.
   Both files are the variable builds: one axis, wght 100..900.

   Pages that use these should preload them in <head> (same-origin fonts still
   require the crossorigin attribute on preload):
     <link rel="preload" href="/assets/fonts/geist/Geist-Variable.woff2"
           as="font" type="font/woff2" crossorigin>
-------------------------------------------------------------------------- */

@font-face {
    font-family: "Geist Sans";
    src: url("/assets/fonts/geist/Geist-Variable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Geist Mono";
    src: url("/assets/fonts/geist/GeistMono-Variable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

/* ----------------------------------------------------------------------------
   2. Tokens.
-------------------------------------------------------------------------- */

:root {
    /* Material ------------------------------------------------------------ */
    --cream: #f1eee7; /* page ground */
    --ink: #232323; /* text on cream; instrument bodies */
    --black: #000000; /* nav pill, dark bands, tray */
    --paper: #ffffff; /* rare raised cards on cream */
    --accent: #ff9100; /* THE accent. One verb per view. Never tinted */
    --tan: #d6a878; /* small graphic secondary (charts, marks) */
    --powder: #9fb6d6; /* small graphic secondary (charts, marks) */
    --grey-warm: #5b5a56; /* muted text on cream */
    --grey-line: #dedede; /* hairlines, borders */

    /* Text on black bands. Opacity steps only, never a grey hex. */
    --on-black: rgba(255, 255, 255, 0.9);
    --on-black-muted: rgba(255, 255, 255, 0.55);
    --on-black-faint: rgba(255, 255, 255, 0.4);
    --on-black-line: rgba(255, 255, 255, 0.14); /* hairlines on black */

    /* Semantic aliases. Prefer these in components. ----------------------- */
    --ground: var(--cream);
    --text: var(--ink);
    --text-muted: var(--grey-warm);
    --line: var(--grey-line);
    --rule: 1px solid var(--grey-line);
    --rule-on-black: 1px solid var(--on-black-line);

    /* Type ---------------------------------------------------------------- */
    --font-sans:
        "Geist Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
        sans-serif;
    --font-mono:
        "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* Display weight is 500. 400 for body, 600 is the ceiling. */
    --weight-body: 400;
    --weight-display: 500;
    --weight-strong: 600;

    /* Fluid scale, 360px..1440px viewport. Display leading stays tight. */
    --size-eyebrow: 0.6875rem; /* 11px, mono, letter-spaced, uppercase */
    --size-micro: 0.75rem; /* 12px, mono utility */
    --size-small: 0.8125rem; /* 13px */
    --size-body: clamp(0.9375rem, 0.9rem + 0.16vw, 1.0625rem);
    --size-lead: clamp(1.0625rem, 1rem + 0.28vw, 1.25rem);
    --size-h3: clamp(1.25rem, 1.13rem + 0.55vw, 1.625rem);
    --size-h2: clamp(1.75rem, 1.4rem + 1.55vw, 3rem);
    --size-h1: clamp(2.5rem, 1.7rem + 3.55vw, 5rem);

    --leading-display: 1.04;
    --leading-heading: 1.12;
    --leading-body: 1.55;

    --tracking-display: -0.02em;
    --tracking-mono: 0.08em; /* eyebrows, nav, tickers */
    --tracking-mono-tight: 0.02em; /* mono numbers in ledger rows */

    /* Motion. Durations are referenced by JS as well; keep names stable. --- */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --dur-fast: 140ms;
    --dur-base: 260ms;
    --dur-slow: 620ms;
}

/* ----------------------------------------------------------------------------
   3. Base. Only what the new system needs; no component styling lives here.
-------------------------------------------------------------------------- */

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

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background-color: var(--ground);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--size-body);
    font-weight: var(--weight-body);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-synthesis-weight: none;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    margin: 0;
    font-family: var(--font-sans);
    font-weight: var(--weight-display);
    line-height: var(--leading-heading);
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
}

h1 {
    font-size: var(--size-h1);
    line-height: var(--leading-display);
}
h2 {
    font-size: var(--size-h2);
    line-height: var(--leading-display);
}
h3 {
    font-size: var(--size-h3);
}

p,
ul,
ol,
figure,
blockquote {
    margin: 0;
}

p {
    text-wrap: pretty;
}

a {
    color: inherit;
    text-decoration: none;
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

img,
svg,
video,
canvas {
    display: block;
    max-width: 100%;
}

hr {
    border: 0;
    border-top: var(--rule);
    margin: 0;
}

::selection {
    background: var(--ink);
    color: var(--cream);
}

/* Focus is state, so the accent is correct here. Never remove the ring. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ----------------------------------------------------------------------------
   4. Mono utility. All data is mono and tabular.
-------------------------------------------------------------------------- */

.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-feature-settings:
        "tnum" 1,
        "zero" 1;
    letter-spacing: var(--tracking-mono-tight);
}

/* Uppercase, letter-spaced label. Eyebrows, nav links, column heads. */
.mono-label {
    font-family: var(--font-mono);
    font-size: var(--size-eyebrow);
    font-weight: var(--weight-body);
    line-height: 1;
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
}

/* Surfaces. `.on-black` flips the text tokens for a full-bleed dark band so
   descendants keep using --text / --text-muted / --line unchanged. */
.on-black {
    background-color: var(--black);
    --text: var(--on-black);
    --text-muted: var(--on-black-muted);
    --line: var(--on-black-line);
    --rule: var(--rule-on-black);
    color: var(--text);
}

/* ----------------------------------------------------------------------------
   5. Motion guard convention.

   Contract, in three parts:

   a) `.motion` marks an element whose animation is enhancement. Under
      reduce, its transitions and animations are cancelled and it is pinned
      to its resting state. It must already be legible and complete without
      any script running: never author a `.motion` element that starts
      hidden in CSS. JS applies the pre-animation state (opacity 0, offset
      transform) only at the moment it takes ownership of animating it back.

   b) `.motion-only` and `.static-only` are the either/or pair for cases that
      cannot simply be frozen, such as the marquee ticker and the word-by-word
      narrative. Ship both renderings; the media query picks one.

   c) JS must gate too. CSS alone cannot stop lenis or a GSAP scrub from
      running, so any motion script checks:
        window.matchMedia("(prefers-reduced-motion: reduce)").matches
      and skips setup entirely when true, leaving the static markup in place.
-------------------------------------------------------------------------- */

.static-only {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .motion,
    .motion * {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
        scroll-behavior: auto !important;
    }

    /* Pin to the resting state so a half-finished reveal can never strand
       content mid-fade. */
    .motion {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
    }

    .motion-only {
        display: none !important;
    }

    .static-only {
        display: revert;
    }
}
