/* ===========================================================================
   byhumans design tokens — the single source of truth.

   Loaded before any other stylesheet. Nothing outside this file declares a
   colour, a spacing value, a radius, a duration or an easing curve. A literal
   hex or a stray "margin: 13px" anywhere else is a review blocker.

   Reference: docs/design/01-color-system.md
              docs/design/02-typography-space-motion.md
   =========================================================================== */

@layer tokens, base, components, utilities;

@layer tokens {

  :root {
    color-scheme: light dark;

    /* -----------------------------------------------------------------------
       Colour is declared in HEX, not OKLCH, and that is a deliberate change
       from the original plan.

       The reason OKLCH was mandated is that skin tones interpolated in sRGB go
       through dead grey in the middle. That problem is solved by choosing the
       interpolation space at the point of use — every gradient below says
       "in oklch" — not by how the endpoints are spelled.

       Meanwhile every contrast ratio in the docs was measured from these exact
       hex values. Declaring OKLCH would mean maintaining two representations of
       the same colour and re-verifying they still agree on every commit. That
       is a chore that silently rots, and when it rots the thing that breaks is
       accessibility.

       So: hex endpoints, verified once, plus explicit OKLCH interpolation.
       ----------------------------------------------------------------------- */

    /* -----------------------------------------------------------------------
       SKIN is the default surface, and that is the whole point.

       The palette used to open on Carbon, with a light "Paper" mode bolted on for
       reading. That was backwards for this product. A place for humans should not
       greet people with the interface language of a server console, and every
       machine-generated feed on the internet is already dark and neon.

       So the page is #F1E7DB: literally the lightest tone of the Monk Skin Tone
       Scale, the same scale the Human Spectrum is derived from. The background is
       a human colour rather than an off-white chosen because it looked warm.

       Deliberately NOT #FFF, and not close to it. Pure white is a printer's
       default, it glares, and it says nothing.

       Numbers below are measured, not estimated. tools/check-contrast.ps1 reads
       this file and fails the build if any of them stops being true.
       ----------------------------------------------------------------------- */

    /* The two reference sets. Plain hex, measured, never consumed directly by a
       component: everything downstream reads the active tokens below. */

    /* Skin - the default. Raised surfaces get lighter, so 0 is the page. */
    --bh-skin-0: #F1E7DB;  /* the page: Monk tone 1 */
    --bh-skin-1: #F7F0E7;  /* cards */
    --bh-skin-2: #FBF6EF;  /* raised: sheets, menus. Still not white. */
    --bh-skin-3: #E3D5C3;  /* recessed: wells, tracks, inactive rails */

    /* Carbon - the dark set. */
    --bh-void:     #08080A;
    --bh-carbon-0: #101014;
    --bh-carbon-1: #17171C;
    --bh-carbon-2: #1F1F26;
    --bh-carbon-3: #2A2A33;
    --bh-ink:      #1B1714;

    /* -----------------------------------------------------------------------
       Active tokens, one declaration each, via light-dark().

       This replaced four near-duplicate palette blocks: :root, a data-theme
       override, a data-surface override, and a prefers-color-scheme media query.
       Keeping them in sync by hand is what shipped the previous light mode
       broken - it remapped the surfaces and forgot Ion, Crimson, Amber and the
       entire Human Spectrum, so those kept their Carbon values and dropped as low
       as 1.09:1 with nothing failing anywhere.

       Now a token cannot exist in one theme and not the other: there is one
       declaration, and it names both values. Which one resolves is decided by the
       inherited color-scheme, so the theme switches below are one line each and a
       subtree can flip without restating a single colour.
       ----------------------------------------------------------------------- */

    --bh-surface-0: light-dark(var(--bh-skin-0), var(--bh-carbon-0));
    --bh-surface-1: light-dark(var(--bh-skin-1), var(--bh-carbon-1));
    --bh-surface-2: light-dark(var(--bh-skin-2), var(--bh-carbon-2));
    --bh-surface-3: light-dark(var(--bh-skin-3), var(--bh-carbon-3));
    --bh-vapor:     light-dark(rgb(241 231 219 / 0.72), rgb(16 16 20 / 0.72));

    /* Text and lines. On light, warm near-black rather than a neutral grey: a cold
       grey on a warm surface reads as a rendering mistake. */
    --bh-chrome:       light-dark(#1B1714, #E8E8EC);  /* 14.58 / 15.53 on surface-0 */
    --bh-titanium:     light-dark(#6A6157, #8A8A99);  /*  4.97 /  5.58 - secondary */
    --bh-titanium-dim: light-dark(#B6A896, #5A5A66);  /*  borders and decorative fill
                                                         ONLY. Never text. Never a
                                                         state indicator: those are
                                                         graphical objects and need
                                                         3:1. */
    --bh-state-dim:    light-dark(#857A6D, #68687A);  /*  3.44 /  3.48 - the
                                                         recessive but legible value
                                                         for "nothing proven yet". */
    --bh-hairline:     light-dark(rgb(27 23 20 / 0.12), rgb(232 232 236 / 0.08));

    /* Semantic accents. The light theme uses ink-weight variants: the luminous
       values belong on Carbon, and on a warm surface they turn into highlighter. */
    --bh-ember:   light-dark(#B23A0B, #FF6A2B);  /* 4.91 /  6.64 - primary action */
    --bh-aurora:  light-dark(#046B4A, #00E5A0);  /* 5.36 / 11.49 - VERIFIED HUMAN,
                                                    and nothing else ever */
    --bh-ion:     light-dark(#1B4FCC, #5B8CFF);  /* 5.66 /  6.00 - links, editorial */
    --bh-amber:   light-dark(#7A4F00, #FFC53D);  /* 5.84 / 12.03 - caution */
    --bh-crimson: light-dark(#B3123A, #FF2D55);  /* 5.61 /  5.21 - error, disputed */

    --bh-ember-glow: light-dark(rgb(178 58 11 / 0.22), rgb(255 106 43 / 0.35));

    /* The scrim behind a top-layer surface. Warm ink on light rather than pure
       black, which over a skin-toned page reads as a hole punched in the paper;
       deeper on Carbon, where the sheet has less luminance to separate it from
       the page behind. */
    --bh-scrim: light-dark(rgb(27 23 20 / 0.44), rgb(8 8 10 / 0.6));

    /* The label that sits ON an accent, and it flips direction with the theme.
       On light, Ember is dark so the label is the palest surface: 5.58:1. On
       Carbon, Ember is luminous so the label is the void: 6.64:1. Never white by
       default in either, because white on Ember measures 2.86:1 and fails. */
    --bh-on-accent: light-dark(var(--bh-skin-2), var(--bh-void));

    /* -----------------------------------------------------------------------
       Human Spectrum. Derived from the Monk Skin Tone Scale.
       Permitted uses only: the provenance ring, the verification progress
       indicator, the logo, and the Spectrum Scrubber. Never a card background,
       never behind text, never decoration.
       ----------------------------------------------------------------------- */

    /* -----------------------------------------------------------------------
       There are two ramps, and the reason is not decoration.

       Every stop of the ring is a graphical object carrying meaning, so each one
       has to clear 3:1 against the surface behind it. A single ramp cannot: the
       pale end of the Monk scale is invisible on a light page (1.09:1) and the
       deep end is invisible on Carbon (1.24:1). Whichever single ramp you pick,
       one theme silently loses its most important indicator.

       So the scale is anchored at whichever end the surface can carry, and the
       hue is held. Named by the surface they sit on, not by how light they are,
       because "light tones" is exactly the ambiguity that gets this wrong.
       ----------------------------------------------------------------------- */

    /* For light surfaces: the deep half of the scale. Floor 3.11:1 on skin-0. */
    --bh-tone-on-light-1: #A17850;
    --bh-tone-on-light-2: #94694C;
    --bh-tone-on-light-3: #8A6248;
    --bh-tone-on-light-4: #77523C;
    --bh-tone-on-light-5: #614333;
    --bh-tone-on-light-6: #49332A;
    --bh-tone-on-light-7: #2E2622;

    /* For Carbon: lightness raised, hue held. Floor 3.54:1 on carbon-0. */
    --bh-tone-on-dark-1: #F6EDE4;
    --bh-tone-on-dark-2: #EEDFC6;
    --bh-tone-on-dark-3: #DFC49E;
    --bh-tone-on-dark-4: #C9A87C;
    --bh-tone-on-dark-5: #B08C64;
    --bh-tone-on-dark-6: #9C7454;
    --bh-tone-on-dark-7: #8A6248;

    /* The active ramp. Everything downstream reads these and nothing else. */
    --bh-tone-1: light-dark(var(--bh-tone-on-light-1), var(--bh-tone-on-dark-1));
    --bh-tone-2: light-dark(var(--bh-tone-on-light-2), var(--bh-tone-on-dark-2));
    --bh-tone-3: light-dark(var(--bh-tone-on-light-3), var(--bh-tone-on-dark-3));
    --bh-tone-4: light-dark(var(--bh-tone-on-light-4), var(--bh-tone-on-dark-4));
    --bh-tone-5: light-dark(var(--bh-tone-on-light-5), var(--bh-tone-on-dark-5));
    --bh-tone-6: light-dark(var(--bh-tone-on-light-6), var(--bh-tone-on-dark-6));
    --bh-tone-7: light-dark(var(--bh-tone-on-light-7), var(--bh-tone-on-dark-7));

    --bh-spectrum: conic-gradient(from 180deg in oklch,
      var(--bh-tone-1), var(--bh-tone-2), var(--bh-tone-3), var(--bh-tone-4),
      var(--bh-tone-5), var(--bh-tone-6), var(--bh-tone-7), var(--bh-tone-1));

    --bh-spectrum-linear: linear-gradient(90deg in oklch,
      var(--bh-tone-1), var(--bh-tone-2), var(--bh-tone-3), var(--bh-tone-4),
      var(--bh-tone-5), var(--bh-tone-6), var(--bh-tone-7));

    /* The whole scale end to end, pale through deep. Only for a surface that can
       carry both ends, which means a mid-tone band or an image, never a themed
       page background. Used by the Spectrum Scrubber. */
    --bh-spectrum-full: linear-gradient(90deg in oklch,
      var(--bh-tone-on-dark-1), var(--bh-tone-on-dark-3),
      var(--bh-tone-on-light-1), var(--bh-tone-on-light-4),
      var(--bh-tone-on-light-7));

    /* -----------------------------------------------------------------------
       Typography. Three voices, each with a meaning.
       Evidence is the important one: every provenance datum renders in mono,
       so that over time the user learns that mono means verifiable fact.

       Self-hosted woff2 with latin + latin-ext subsetting is still to do; the
       stacks below degrade to system faces until those files exist.
       ----------------------------------------------------------------------- */
    --ff-ui: "Inter Variable", Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --ff-editorial: "Newsreader Variable", Newsreader, Georgia, "Times New Roman", serif;
    --ff-evidence: "JetBrains Mono", ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;

    --fs-micro:   0.6875rem;  /* 11px — mono evidence labels only */
    --fs-caption: 0.75rem;    /* 12px — metadata */
    --fs-body-s:  0.875rem;   /* 14px — dense UI */
    --fs-body:    1rem;       /* 16px — floor for anything read */
    --fs-body-l:  1.125rem;   /* 18px — editorial body */
    --fs-h3:      clamp(1.25rem, 1rem + 1vw,   1.5rem);
    --fs-h2:      clamp(1.5rem,  1.1rem + 2vw, 2.25rem);
    --fs-h1:      clamp(2rem,    1.4rem + 3vw, 3.25rem);
    --fs-display: clamp(2.75rem, 1.5rem + 6vw, 5.5rem);

    --lh-tight: 1.1;
    --lh-heading: 1.15;
    --lh-ui: 1.5;
    --lh-reading: 1.7;

    --measure: 68ch;  /* 62-72 characters. Wider than this and reading breaks. */

    /* Spacing — 4px base. These values and no others.
       --sp-05 is the one sub-base step, and it exists for a single job: the inset
       of a pill nested inside a pill, where 4px reads as a gap rather than as a
       seam. Without it that inset becomes a 2px literal in a component file,
       which is how a scale starts leaking. */
    --sp-05: 2px;
    --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
    --sp-5: 24px;  --sp-6: 32px;  --sp-7: 48px;  --sp-8: 64px;
    --sp-9: 96px;  --sp-10: 128px;

    /* Radii */
    --r-xs: 6px;  --r-sm: 10px; --r-md: 14px;
    --r-lg: 20px; --r-xl: 28px; --r-full: 999px;

    /* Motion */
    --ease-human:  cubic-bezier(0.22, 1, 0.36, 1);
    --ease-enter:  cubic-bezier(0.16, 1, 0.3, 1);
    --ease-exit:   cubic-bezier(0.4, 0, 1, 1);
    --ease-spring: linear(0, 0.42 12%, 0.86 24%, 1.02 34%, 1.01 46%, 1);

    --dur-instant: 100ms;
    --dur-fast:    180ms;
    --dur-base:    260ms;
    --dur-slow:    420ms;
    --dur-ceremony: 900ms;  /* Spectrum Sweep only. Once per account lifetime. */

    /* Elevation on a light surface is a real shadow, tinted warm: a neutral black
       shadow over a warm surface greys it out and reads as dirt, not depth. Carbon
       absorbs shadow instead, so there the same slot goes near-opaque black and the
       depth cue is the hairline of light around the edge. */
    --elev-1: 0 1px 0 0 var(--bh-hairline);
    --elev-2: 0 8px 24px -14px light-dark(rgb(74 51 32 / 0.28), rgb(0 0 0 / 0.6)),
              0 0 0 1px var(--bh-hairline);
    --elev-3: 0 24px 64px -28px light-dark(rgb(74 51 32 / 0.34), rgb(0 0 0 / 0.75)),
              0 0 0 1px var(--bh-hairline);

    --z-base: 0;
    --z-sticky: 100;
    --z-deck: 200;
    --z-sheet: 300;
    --z-toast: 400;
  }

  /* -------------------------------------------------------------------------
     Theme switches. One line each, because light-dark() above already names both
     values for every token: all these have to do is say which side resolves.

     :root with no attribute means "follow the operating system", so a reader who
     set their machine to dark gets Carbon without the markup mentioning it.

     data-surface is deliberately NOT scoped to :root. color-scheme inherits, so
     setting it on a section flips that subtree and nothing else - a Carbon hero
     band on a light page, or a light reading panel inside a dark shell. Anchoring
     these selectors to :root was the first attempt and it silently did nothing on
     any element that was not the document root.

     "only" is load-bearing. A plain "color-scheme: dark" still lets the browser
     pick when the OS disagrees; "only dark" is the instruction.
     ------------------------------------------------------------------------- */
  :root[data-theme="dark"],
  [data-surface="dark"] {
    color-scheme: only dark;
  }

  :root[data-theme="light"],
  [data-surface="light"] {
    color-scheme: only light;
  }

  /* Reading surface. It used to be a colour change, because the app was dark and
     an article needed a light page. Now the page is already a human tone, so
     reading mode is one step up the surface ramp plus, in the views, the
     typography and the measure.

     Written against the reference sets rather than as --bh-surface-0:
     var(--bh-surface-1). That shorthand looks tidier and is wrong: custom
     properties resolve against the same element, so surface-1 would already have
     been remapped here and both surfaces would collapse to the same value. */
  [data-surface="paper"] {
    --bh-surface-0: light-dark(var(--bh-skin-1), var(--bh-carbon-1));
    --bh-surface-1: light-dark(var(--bh-skin-2), var(--bh-carbon-2));
  }

  /* Nothing above 480ms, nothing uninterruptible, everything respects this. */
  @media (prefers-reduced-motion: reduce) {
    :root {
      --dur-instant: 0.01ms;
      --dur-fast: 0.01ms;
      --dur-base: 0.01ms;
      --dur-slow: 0.01ms;
      --dur-ceremony: 0.01ms;
    }
  }
}
