/* Arcline — Datum Arc design tokens.
   Scope: the tokens are declared on :root as well as on .arcline, so the
   document canvas can use them. Add class "arcline" to your app root, to any
   portal layer, and to any element that pins its own theme with data-theme
   (tokens must be re-declared there for light-dark() to resolve locally).

   Why :root as well: .arcline sits on an inner element, so html and body were
   outside every token's scope. `body { background: var(--canvas, #eaebe8) }`
   then resolved to the literal fallback — the light value — and a dark page
   flashed white wherever the canvas shows through, which on iOS is every
   rubber-band scroll. Declaring at :root makes that reference resolve, and
   color-scheme there also gives the browser's own canvas the right default.

   Theme: the palette follows the operating system by default. Set
   data-theme="light" | "dark" on <html> (or on any .arcline element) to pin it;
   that attribute only flips color-scheme, and every color token below resolves
   through light-dark() from there. No JavaScript is required for system dark
   mode. */

:root,
.arcline {
  /* Unpinned: follow the OS. data-theme overrides this below. */
  color-scheme: light dark;

  /* ── The knobs ──────────────────────────────────────────────────────────
     Retheming Arcline is two declarations. The seed is the signal colour in
     light mode, given exactly as picked; every other accent — the dark cut,
     the label ink, the tint, the focus ring, the specular edge, the chart
     signal — is derived from it in CSS, so there is one place to change and
     nothing to keep in sync by hand.

     Derivation is by absolute lightness with chroma proportional to the seed.
     Absolute, because a tint has to land near the canvas and an ink has to
     clear it whatever lightness the seed happens to sit at; proportional,
     because a deliberately muted brand should yield a muted family. The
     numbers came off the hand-tuned Arc Blue set and reproduce it to ΔE < 1.

     CSS can compute this family but cannot check it: a seed too pale to carry
     white text renders happily and fails WCAG in silence. Before shipping a
     new seed, run `node scripts/check-brand.mjs "#yourhex"`.

     A seed may itself be a light-dark() pair if an app wants to pin both cuts
     by hand; the derivation reads whichever one the current mode resolves.

     The knobs are *read* with a fallback rather than declared. Declaring them
     here would mean every nested .arcline — a pinned ThemePreview, a portal
     layer, a themed island — reset the seed to Arcline's own, and an app that
     reseeded would watch its islands stay blue. Read this way, a scope
     inherits whatever its ancestors set and falls back only at the root. */
  --_brand-seed: var(--brand-seed, #365cff);

  /* The neutral cast. The surface ramp is not one tint — it rotates as it
     descends, green at the top and blue at the bottom, and that rotation is
     the mineral character the brand guide describes. These scale and turn the
     whole ramp without flattening it: chroma 0 gives pure grey, 1 gives
     Arcline's own surfaces. The shift is in degrees, unitless — `8deg` is a
     type error inside a relative colour. */
  --_neutral-hue-shift: var(--neutral-hue-shift, 0);
  --_neutral-chroma: var(--neutral-chroma, 1);

  /* Color — light value first, dark value second.

     The light ramp is deliberately held off pure white. A #FFFFFF surface on a
     #F7F8F6 ground puts almost the whole page above 94% luminance, which glares
     and leaves no room for elevation to read. Everything steps down a few
     percent instead: the ground carries the reduction, the raised surface stays
     clearly above it, and the mineral cast (green highest, red lowest) is
     preserved exactly. Text darkens slightly to match, so every ratio improves
     rather than degrades. */
  --canvas: oklch(from light-dark(#eaebe8, #0e1214) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --surface: oklch(from light-dark(#f8f9f7, #171d20) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --panel: oklch(from light-dark(#dfe2dd, #222a2e) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --text: oklch(from light-dark(#111517, #edf2f0) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --text-soft: oklch(from light-dark(#20272b, #d5dede) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --muted: oklch(from light-dark(#545f66, #9daaab) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --rule: oklch(from light-dark(#d6dad5, #262f33) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --rule-strong: oklch(from light-dark(#bcc4c6, #46545a) l calc(c * var(--_neutral-chroma)) calc(h + var(--_neutral-hue-shift)));
  --brand: light-dark(var(--_brand-seed), oklch(from var(--_brand-seed) 0.693 calc(c * 0.66) calc(h + 3)));
  /* The tint tracks the ground down: left at #E9EDFF it would sit *lighter*
     than the canvas and stop reading as a tint at all. */
  --brand-soft: light-dark(oklch(from var(--_brand-seed) 0.928 calc(c * 0.14) calc(h + 8)), oklch(from var(--_brand-seed) 0.304 calc(c * 0.31) h));
  /* The brand's ink dual — the same pattern the status colours already use.
     The seed is a fill colour: as a label on a tinted fill it lands at 4.2–4.4:1,
     under AA. This darker cut clears 5.6:1 on every tint in the system. */
  --brand-text: light-dark(oklch(from var(--_brand-seed) 0.48 calc(c * 0.85) h), oklch(from var(--_brand-seed) 0.781 calc(c * 0.45) calc(h + 3)));
  /* The status inks are measured against the badge *tint*, not white: on their
     own soft fills the previous cuts sat at 4.33 and 4.44, just under AA. */
  --ember: light-dark(#c96b52, #e58a70);
  --ember-text: light-dark(#953d29, #e58a70);
  --moss: light-dark(#587363, #7ea58c);
  --yellow: light-dark(#c69b35, #d7b25f);
  --yellow-text: light-dark(#77570a, #d7b25f);
  --focus: var(--brand);

  /* Ink for text and glyphs sitting ON a filled surface.
     Its polarity is computed from the fill's own lightness rather than fixed
     per theme, which is what lets one seed carry its own legible label: a dark
     brand gets white, a luminous one gets near-black, and neither needs a
     second knob set by hand. `l` crossing 0.56 is the flip — measured as the
     point where white stops clearing 4.5:1 — and the two clamps read as: pull
     lightness to 1 or to 0.10, and let chroma through only on the dark side so
     white stays neutral while near-black keeps a trace of the fill's hue.

     Reading from the fill rather than the seed means one expression serves
     both themes, and it also caught a shipped failure: white on the ember fill
     measured 3.68:1 under the old fixed light-mode white, so the destructive
     button and the count badge were below AA. They flip to near-black here.

     A thin band of very saturated fills around l 0.56-0.59 clears 4.5:1 with
     neither polarity. That is a property of the colour, not of this rule;
     check-brand.mjs names it rather than pretending a ratio was met. */
  --on-brand: oklch(from var(--brand) clamp(0.1, (0.56 - l) * 1000, 1) calc(c * 0.14 * clamp(0, (l - 0.56) * 1000, 1)) h);
  --on-critical: oklch(from var(--ember) clamp(0.1, (0.56 - l) * 1000, 1) calc(c * 0.14 * clamp(0, (l - 0.56) * 1000, 1)) h);
  --on-muted: oklch(from var(--muted) clamp(0.1, (0.56 - l) * 1000, 1) calc(c * 0.14 * clamp(0, (l - 0.56) * 1000, 1)) h);
  /* The name every consumer already imports; the brand fill is its usual job. */
  --on-accent: var(--on-brand);

  /* Space.
     A 4px base scale. These are real tokens, not documentation: the guide and
     the inspector both resolve them from here, and layout code should use them
     instead of literals so density stays adjustable in one place. Space stays
     in px, like radii — it is layout structure, not reading size, and should
     not grow when the type scale does. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;

  /* Control metrics.
     The heights and focus widths every control is built from. These exist as
     tokens for the same reason the colours do: the inspector printed a
     hardcoded "button.radius: 8px" long after the real radius moved to 10px,
     because there was nothing to resolve against. */
  --control-height: 38px;
  --control-height-sm: 32px;
  --control-height-lg: 46px;
  --field-height: 38px;
  --row-height: 44px;
  /* The floor for anything finger-operated, at compact widths. */
  --touch-target: 44px;
  --focus-ring: 2px;
  --focus-halo: 3px;

  /* Shape.
     Curvature communicates altitude: nested elements always take a smaller
     radius than their container, and the step between tiers stays visible. */
  --radius-none: 0px;
  --radius-xs: 8px;
  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-xl: 26px;
  --radius-full: 999px;

  /* Elevation — geometry is shared; only the shadow color changes per theme */
  --shadow-xs: 0 1px 2px light-dark(rgba(16, 20, 22, .06), rgba(0, 0, 0, .30));
  --shadow-sm:
    0 1px 2px light-dark(rgba(16, 20, 22, .05), rgba(0, 0, 0, .30)),
    0 2px 8px light-dark(rgba(16, 20, 22, .06), rgba(0, 0, 0, .30));
  --shadow-md:
    0 2px 4px light-dark(rgba(16, 20, 22, .05), rgba(0, 0, 0, .30)),
    0 8px 24px light-dark(rgba(16, 20, 22, .09), rgba(0, 0, 0, .40));
  --shadow-lg:
    0 4px 12px light-dark(rgba(16, 20, 22, .08), rgba(0, 0, 0, .40)),
    0 20px 48px light-dark(rgba(16, 20, 22, .14), rgba(0, 0, 0, .50));
  --shadow-xl:
    0 8px 24px light-dark(rgba(16, 20, 22, .12), rgba(0, 0, 0, .50)),
    0 32px 80px light-dark(rgba(16, 20, 22, .24), rgba(0, 0, 0, .60));
  --edge: inset 0 1px 0 light-dark(rgba(255, 255, 255, 0), rgba(255, 255, 255, .07));
  /* light-dark() takes colors, not percentages, so the halo uses one mix for
     both themes — --brand is already theme-aware, which carries the weight. */
  --ring: 0 0 0 var(--focus-halo) color-mix(in srgb, var(--brand) 26%, transparent);

  /* Material — glass.
     Three blur depths, one saturation. Translucency is expressed as a mix of
     the theme's own surface so glass stays on-palette in both themes. */
  --glass-blur-sm: 12px;
  --glass-blur-md: 20px;
  --glass-blur-lg: 30px;
  --glass-saturate: 180%;
  --glass-surface: color-mix(in srgb, var(--surface) 76%, transparent);
  --glass-surface-strong: color-mix(in srgb, var(--surface) 88%, transparent);
  --glass-panel: color-mix(in srgb, var(--panel) 58%, transparent);
  --glass-canvas: color-mix(in srgb, var(--canvas) 82%, transparent);
  --glass-scrim: light-dark(rgba(12, 18, 20, .42), rgba(3, 6, 8, .58));

  /* Specular edge — a 1px lit rim for glass surfaces.
     The brand plus neutrals only; the system never carries a second hue. The
     dark highlight is a pale cut of the seed, so the rim stays on-brand. */
  --edge-specular: linear-gradient(
    140deg,
    color-mix(in srgb, var(--brand) 52%, transparent) 0%,
    color-mix(in srgb, light-dark(#ffffff, oklch(from var(--_brand-seed) 0.907 calc(c * 0.18) h)) 62%, transparent) 38%,
    color-mix(in srgb, var(--rule-strong) 52%, transparent) 68%,
    color-mix(in srgb, var(--brand) 32%, transparent) 100%
  );

  /* Motion */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-spring: cubic-bezier(.32, 1.35, .5, 1);
  --ease-press: cubic-bezier(.32, .72, 0, 1);
  --dur-fast: 150ms;
  --dur-med: 260ms;
  --dur-slow: 400ms;

  /* Press — how far a control compresses under the finger */
  --press-scale: .972;
  --press-scale-sm: .94;
}

/* Applied styling, deliberately kept off :root. Exposing the tokens
   document-wide is safe — nothing renders differently until something reads
   one — but imposing Arcline's ink and typography on a whole host document is
   not the token file's call. Consumers opt in by classing their app root. */
.arcline {
  color: var(--text);
  font-family: "Inter Tight Variable", -apple-system, "Segoe UI", Arial, sans-serif;
  font-feature-settings: "cv05", "cv08";
}

/* Pinned themes. These only switch color-scheme — every token above resolves
   from it, so there is exactly one place where a palette value is written.

   The ancestor pin is wrapped in :where() so it carries no specificity of its
   own. Without that, `[data-theme="dark"] .arcline` and
   `.arcline[data-theme="light"]` tie at (0,2,0) and source order decides, so a
   light-pinned island inside a dark-pinned page renders dark. The nearest pin
   must win in both directions. */
:where([data-theme="light"]) .arcline {
  color-scheme: light;
}

:where([data-theme="dark"]) .arcline {
  color-scheme: dark;
}

.arcline[data-theme="light"] {
  color-scheme: light;
}

.arcline[data-theme="dark"] {
  color-scheme: dark;
}

/* The same pin applied to the document element. Without these, data-theme on
   <html> flips every .arcline island but leaves :root itself following the OS,
   so the tokens the canvas resolves would contradict the pinned page. */
:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}
