/* ============================================================================
   SWORT design tokens
   ---------------------------------------------------------------------------
   Single source of truth for colour, type, spacing, and motion. Loaded first on
   every page, before every other stylesheet.

   THE PALETTE IS FULLY NEUTRAL. Black, graphite, white, silver, grey. There is
   no accent hue. Interaction is carried by white and silver on dark surfaces
   and by near-black on light surfaces, with underlines and borders doing the
   work an accent colour used to do.

   Two prohibitions, both absolute and both enforced by
   scripts/verify-color.mjs, which runs in `npm test` and in CI:

     NO BLUE     not blue, navy, cyan, teal, steel blue, or blue-grey
     NO YELLOW   not yellow, gold, amber, cream, beige, ivory, parchment,
                 ochre, champagne, sand, or any warm-tinted white

   The gate reads VALUES, not names, so renaming a token does not get past it.
   It also rejects yellow-family words in token NAMES, so the vocabulary cannot
   creep back in even where the value is currently neutral.

   Why there is no amber any more: an earlier revision of this file used amber
   for links, focus, selection, form accents, and the simulator's inconsistency
   state. Reviewed on screen it read as yellow and cheapened the whole surface.
   Only two colours survive, and only as status, never as decoration:

     --status-danger   genuine warnings, confirmed failures, destructive
                       actions, fault states
     --status-ok       verified, available, completed, healthy

   Both have separate text-safe variants per surface because the base values
   fail contrast as small text. Never use colour as the only carrier of state;
   pair it with text, an icon, or a border change.

   How the previous cool-grey scale was removed, kept here because it explains
   the numbers: every blue-leaning grey was replaced by the pure grey of the
   SAME relative luminance rather than an eyeballed substitute, so measured
   contrast ratios stayed valid. Largest drift was 0.0024.
   ========================================================================= */

:root {
  color-scheme: dark;

  /* ── Base palette ─────────────────────────────────────────────────────── */
  --swort-black:          #050505;
  --swort-black-raised:   #0a0a0b;
  --swort-graphite:       #111214;
  --swort-graphite-light: #1a1c1f;

  --swort-white:       #ffffff;
  --swort-white-soft:  #f8f8f8;
  --swort-white-muted: #efefef;

  --swort-silver:    #bfc2c5;
  --swort-gray:      #8d9297;
  --swort-gray-dark: #50555a;

  --swort-danger:  #b84b47;
  --swort-success: #6e866f;

  --swort-border-dark:  rgba(255, 255, 255, 0.13);
  --swort-border-light: rgba(0, 0, 0, 0.14);

  /* Surface-specific status text. Measured, not chosen by eye. The base values
     above are for MARKS and FILLS; as small text they fail:
       --swort-danger  is 2.92:1 on dark and 4.42:1 on light
       --swort-success is 3.74:1 on dark and 3.44:1 on light
     These four all clear 4.5:1 against every surface of their family. */
  --status-danger-on-dark:  #ca7875;   /* 4.56:1 worst case */
  --status-danger-on-light: #b64a46;   /* 4.50:1 */
  --status-ok-on-dark:      #7d947e;   /* 4.52:1 */
  --status-ok-on-light:     #5e725e;   /* 4.52:1 */

  /* ── Semantic: dark surfaces ──────────────────────────────────────────── */
  --bg-base:   var(--swort-black);
  --bg-raised: var(--swort-black-raised);
  --bg-panel:  var(--swort-graphite);
  --bg-inset:  var(--swort-graphite-light);

  --text-primary:   var(--swort-white-soft);   /* 13.95:1 worst dark surface */
  --text-secondary: var(--swort-silver);       /*  8.28:1 */
  --text-muted:     var(--swort-gray);         /*  4.72:1, AA for body */
  /* gray-dark is 1.97:1 on dark. Borders and dividers ONLY, never text. */
  --text-faint:     var(--swort-gray);

  --border-subtle: var(--swort-border-dark);
  --border-strong: rgba(255, 255, 255, 0.24);
  --rule-hairline: rgba(255, 255, 255, 0.08);

  /* ── Semantic: light surfaces ─────────────────────────────────────────────
     Genuinely neutral white. No cream, beige, ivory, or parchment, and no
     warm tint of any strength: verify-color.mjs fails on a red-minus-blue gap
     of 4 or more at these lightnesses. */
  --paper-bg:     var(--swort-white-soft);
  --paper-raised: var(--swort-white);
  --paper-inset:  var(--swort-white-muted);
  --paper-text:   var(--swort-graphite);       /* 16.30:1 */
  --paper-muted:  var(--swort-gray-dark);      /*  6.55:1 */
  /* swort-gray is 2.73:1 on white. Never use it as text on a light surface. */
  --paper-faint:  #696969;                     /*  4.60:1 */
  --paper-border: var(--swort-border-light);
  --paper-rule:   rgba(0, 0, 0, 0.10);

  /* ── Interactive. Neutral. Underlines and borders carry affordance. ────── */
  --link:                var(--swort-white);
  --link-hover:          var(--swort-silver);
  --link-on-paper:       var(--swort-graphite-light);
  --link-on-paper-hover: var(--swort-black);
  --focus-ring:          var(--swort-white);          /* 20.38:1 on black */
  --focus-ring-paper:    var(--swort-graphite);       /* 17.65:1 on white-soft */
  --focus-ring-width:    3px;
  --focus-ring-offset:   2px;

  /* ── Button state tokens ──────────────────────────────────────────────────
     Explicit background, text, and border for every variant in every state.
     The reason they are enumerated rather than derived: the recurring defect on
     this site has been a state that sets only SOME of the three and inherits the
     rest, which is how a transparent background ended up under white text.
     scripts/verify-button-states.mjs resolves these and fails the build if any
     combination becomes unreadable. */
  --button-primary-bg:            var(--swort-white);
  --button-primary-text:          var(--swort-black);
  --button-primary-border:        var(--swort-white);
  --button-primary-hover-bg:      #e9e9e9;
  --button-primary-hover-text:    var(--swort-black);
  --button-primary-hover-border:  #e9e9e9;
  --button-primary-active-bg:     #cfcfcf;
  --button-primary-active-text:   var(--swort-black);
  --button-primary-active-border: #cfcfcf;

  --button-dark-bg:            var(--swort-black);
  --button-dark-text:          var(--swort-white);
  --button-dark-border:        rgba(255, 255, 255, 0.35);
  --button-dark-hover-bg:      #191919;
  --button-dark-hover-text:    var(--swort-white);
  --button-dark-hover-border:  var(--swort-white);
  --button-dark-active-bg:     #262626;
  --button-dark-active-text:   var(--swort-white);
  --button-dark-active-border: var(--swort-white);

  /* Outline variants. Border must clear 3:1 against its surface (WCAG 1.4.11)
     or the button has no visible shape at rest. */
  --button-outline-dark-text:   var(--swort-white-soft);
  --button-outline-dark-border: var(--swort-gray);       /* 6.49:1 on black */
  --button-outline-light-text:   var(--swort-graphite);
  --button-outline-light-border: var(--swort-gray-dark); /* 6.55:1 on white */

  --button-disabled-bg:     var(--swort-gray-dark);
  --button-disabled-text:   var(--swort-white-muted);
  --button-disabled-border: var(--swort-gray-dark);

  /* ── Buttons. No accent variant exists any more. ──────────────────────── */
  --btn-primary-bg:   var(--swort-white);
  --btn-primary-text: var(--swort-black);
  --btn-ghost-border: var(--border-strong);
  --btn-ghost-text:   var(--text-primary);
  --btn-ink-bg:       var(--swort-graphite);
  --btn-ink-text:     var(--swort-white);
  /* Retained names, remapped to the neutral primary so any stale reference
     lands on an approved colour instead of reintroducing amber. */
  --btn-accent-bg:    var(--swort-white);
  --btn-accent-text:  var(--swort-black);

  /* ── State ────────────────────────────────────────────────────────────────
     "Attention" is deliberately silver, not amber. An intermediate warning
     reads as a LUMINANCE step between muted grey and full white, and the
     component must also change its label or border so colour is never the
     only signal. */
  --state-attention:     var(--swort-silver);
  --state-attention-ink: var(--swort-graphite);
  --state-fault:         var(--status-danger-on-dark);
  --state-fault-mark:    var(--swort-danger);
  --state-fault-ink:     var(--status-danger-on-light);
  --state-ok:            var(--status-ok-on-dark);
  --state-ok-ink:        var(--status-ok-on-light);
  --state-neutral:       var(--swort-gray);

  /* ── Forms ────────────────────────────────────────────────────────────── */
  --field-bg:           transparent;
  --field-border:       var(--border-subtle);
  --field-border-focus: var(--swort-white);
  --field-text:         var(--text-primary);
  --field-placeholder:  var(--swort-gray);
  --field-error:        var(--status-danger-on-dark);
  --field-bg-paper:     var(--swort-white);
  --field-border-paper: rgba(0, 0, 0, 0.22);
  --field-error-paper:  var(--status-danger-on-light);
  --control-min-height: 44px;   /* WCAG 2.2 target size */

  /* ── Canvas overlays. Pure grey; any tint here colours the whole scene. ── */
  --canvas-grid:  rgba(214, 214, 214, 0.06);
  --canvas-trace: rgba(230, 230, 230, 0.42);
  --canvas-node:  rgba(241, 241, 241, 0.92);
  /* Warn is the BRIGHTEST neutral, not a hue shift. Normal traces sit dimmer,
     so warn / normal / fault separate by luminance and by hue only at fault. */
  --canvas-node-warn:  rgba(255, 255, 255, 0.96);
  --canvas-node-fault: var(--swort-danger);
  --canvas-label: rgba(173, 173, 173, 0.66);

  /* ── Selection and disabled ───────────────────────────────────────────── */
  --selection-bg:     rgba(191, 194, 197, 0.32);
  --selection-text:   var(--swort-white);
  --disabled-opacity: 0.5;

  /* ── Type roles ───────────────────────────────────────────────────────────
     Every text element maps to a role. Do not add a one-off font-size for a
     single section; if a size is genuinely needed it becomes a role here. */
  --type-caption:    clamp(0.72rem, 0.69rem + 0.10vw, 0.80rem);
  --type-label:      clamp(0.78rem, 0.74rem + 0.14vw, 0.88rem);
  --type-body:       clamp(1rem, 0.96rem + 0.16vw, 1.10rem);
  --type-body-large: clamp(1.12rem, 1.04rem + 0.28vw, 1.30rem);
  --type-subhead:    clamp(1.35rem, 1.18rem + 0.70vw, 1.85rem);
  --type-section:    clamp(2rem, 1.50rem + 1.70vw, 3.50rem);
  --type-display:    clamp(3rem, 1.70rem + 4.80vw, 7.50rem);
  --type-nav:        clamp(0.74rem, 0.71rem + 0.10vw, 0.82rem);
  --type-cta:        clamp(0.80rem, 0.77rem + 0.12vw, 0.90rem);
  --type-data:       clamp(0.86rem, 0.82rem + 0.18vw, 0.98rem);

  --leading-tight:  1.05;
  --leading-snug:   1.30;
  --leading-body:   1.75;
  --tracking-tight: 0.02em;
  --tracking-wide:  0.14em;
  --tracking-mega:  0.26em;

  --measure-prose:  65ch;
  --measure-narrow: 46ch;

  /* ── Fluid spacing ────────────────────────────────────────────────────── */
  --space-2xs: clamp(0.25rem, 0.24rem + 0.05vw, 0.3rem);
  --space-xs:  clamp(0.5rem, 0.47rem + 0.15vw, 0.65rem);
  --space-sm:  clamp(0.75rem, 0.7rem + 0.25vw, 1rem);
  --space-md:  clamp(1.15rem, 1rem + 0.6vw, 1.75rem);
  --space-lg:  clamp(1.75rem, 1.4rem + 1.4vw, 3rem);
  --space-xl:  clamp(2.5rem, 1.9rem + 2.6vw, 5rem);
  --space-2xl: clamp(3.5rem, 2.4rem + 4.4vw, 8rem);

  /* ── Containers, chosen by section PURPOSE not applied uniformly ──────── */
  --container-reading: 720px;
  --container-content: 1180px;
  --container-wide:    1440px;
  --container-visual:  1680px;

  --section-pad-block: clamp(3rem, 2rem + 5vw, 7.5rem);
  --gutter:            clamp(1rem, 4vw, 5rem);
  --content-max:       var(--container-content);
  --content-max-wide:  var(--container-wide);

  /* ── Motion ───────────────────────────────────────────────────────────── */
  --ease-out:   cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inst:  cubic-bezier(0.33, 0, 0.15, 1);
  --dur-micro:  0.16s;
  --dur-ctrl:   0.2s;
  --dur-sheet:  0.24s;
  --dur-reveal: 0.62s;
  --dur-trace:  1.1s;
  --stagger:    0.07s;
  --reveal-shift: 18px;

  /* ── Legacy aliases ───────────────────────────────────────────────────────
     Older page CSS references these names. All now resolve to the neutral
     palette, so a reference this refactor missed degrades to an approved
     colour rather than to browser-default blue or a stale amber. */
  --paper:       var(--paper-bg);
  --paper-ink:   var(--paper-text);
  --paper-tint:  var(--paper-raised);
  --paper-sink:  var(--paper-inset);
  --charcoal:    var(--swort-graphite-light);
  --near-black:  var(--swort-black);
  --white:       var(--swort-white);
  --black:       #000000;
  --gray-mid:    var(--text-muted);

  --swort-black-1000:   #000000;
  --swort-black-950:    var(--swort-black);
  --swort-black-900:    var(--swort-black-raised);
  --swort-graphite-850: var(--swort-graphite);
  --swort-graphite-800: var(--swort-graphite-light);
  --swort-graphite-700: #25282b;
  --swort-white-100: var(--swort-white);
  --swort-white-150: var(--swort-white-soft);
  --swort-white-200: var(--swort-white-soft);
  --swort-white-250: var(--swort-white-muted);
  --swort-silver-300: var(--swort-silver);
  --swort-silver-500: var(--swort-gray);
  --swort-silver-700: var(--swort-gray-dark);
  --swort-steel-300: var(--swort-silver);
  --swort-steel-500: var(--swort-gray);
  --swort-steel-700: var(--swort-gray-dark);
  --swort-ink-950: var(--swort-black);
  --swort-ink-900: var(--swort-black-raised);
  --swort-ink-850: var(--swort-graphite);
  --swort-ink-800: var(--swort-graphite-light);
  --swort-paper-100: var(--swort-white);
  --swort-paper-200: var(--swort-white-soft);
  --swort-paper-300: var(--swort-white-muted);
  --swort-red-500: var(--swort-danger);
  --swort-red-400: var(--status-danger-on-dark);
  --swort-red-ink: var(--status-danger-on-light);
  --swort-green-500: var(--swort-success);
  --swort-green-ink: var(--status-ok-on-light);
  --swort-fault-red: var(--swort-danger);
  --swort-stable-green: var(--swort-success);
  /* The retired accent aliases (--swort-flight-cyan, --swort-cyan-ink,
     --swort-signal-amber, --swort-amber-*) are DELETED rather than remapped.
     Verified first that nothing references them: 0 occurrences of each across
     every tracked HTML, CSS, and JS file. Keeping a token named "amber" that
     holds a grey is an invitation to put amber back into it, so the gate
     rejects the vocabulary in token names as well as in values. */
}

/* ── Selection ──────────────────────────────────────────────────────────── */
::selection { background: var(--selection-bg); color: var(--selection-text); }

/* ── Browser-default colour removal ───────────────────────────────────────
   Left alone a user agent supplies its own blue for each of these. Pinned to
   approved neutrals so no browser can reintroduce a hue. */
:root {
  /* Checkbox, radio, range, progress fills. Graphite rather than white,
     because these controls live on white form surfaces where a white fill
     would be invisible. */
  accent-color: var(--swort-graphite);
  caret-color: var(--swort-graphite);
}
/* ── Generic link colour, at ZERO specificity ─────────────────────────────
   `:where()` contributes nothing to specificity, so these rules sit at 0,0,0 and
   0,0,1 and lose to ANY component rule. That is the point: a component that
   declares its own colour always wins, with no exclusion list to maintain.

   The previous version was `:where(a):not(.p-btn):not(.p-btn-full):not(.c-btn)…`
   which reads like an exclusion but scores 0,4,0, because `:not()` contributes
   the specificity of its argument. It therefore BEAT every component's own
   0,1,0 rule. `.btn-primary` and `.btn-secondary` were not in the list, so the
   homepage hero CTAs resolved to var(--link), which is white, on a white
   background: 1.00:1, invisible at rest. `.found-cta` and `.route-cell-cta` went
   the same way.

   Worth recording how it was missed: a custom cascade resolver written to audit
   button states scored `:where()` as if it added specificity, so it ranked the
   component rule as the winner and reported 501 elements clean. The browser
   disagreed. getComputedStyle is the ground truth; a hand-rolled specificity
   model is not. */
:where(a) { color: var(--link); }
:where(a):hover { color: var(--link-hover); }
:where(.s-paper, .surface-paper, .p-form-surface, .form-surface, .form-wrap) :where(a) {
  color: var(--link-on-paper);
}
:where(.s-paper, .surface-paper, .p-form-surface, .form-surface, .form-wrap) :where(a):hover {
  color: var(--link-on-paper-hover);
}
/* Never let :visited collapse a link to `inherit`, which on a dark panel inside
   a light section resolved to unreadable. */
:where(a):visited { color: inherit; }

/* Chromium paints autofilled fields with its own background and text colour.
   An inset shadow is the only reliable override for the background. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-text-fill-color: var(--paper-text);
  -webkit-box-shadow: 0 0 0 100px var(--field-bg-paper) inset;
  box-shadow: 0 0 0 100px var(--field-bg-paper) inset;
  caret-color: var(--paper-text);
}

:invalid, :user-invalid { outline-color: var(--field-error); }

/* ── Focus. Visible on both surfaces, never removed. ────────────────────── */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
.s-paper :focus-visible,
.surface-paper :focus-visible,
.p-form-surface :focus-visible,
.form-surface :focus-visible {
  outline-color: var(--focus-ring-paper);
}

/* ── Typographic hygiene ────────────────────────────────────────────────── */
h1, h2, h3, .p-title, .s-head, .c-hero-title {
  text-wrap: balance;
  overflow-wrap: break-word;
}
p, li, dd { text-wrap: pretty; }

.p-identity-meta, .g-mono, .found-v, address, .t-affil-full,
a[href^="mailto:"], code, kbd, samp {
  overflow-wrap: anywhere;
}

/* ── Reduced motion, global ─────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Forced colors: never rely on our palette alone ─────────────────────── */
@media (forced-colors: active) {
  :focus-visible { outline: 3px solid CanvasText; }
  .p-btn, .c-btn, button { border: 1px solid ButtonText; }
}
