/* ══════════════════════════════════════════════════════════
   aptEDMS — the token contract

   The single source of colour. Components read these and never
   declare a hex value of their own.

   ── Why this file exists ──────────────────────────────────

   Before it, six tokens were referenced across the two
   stylesheets and declared in neither: --surface, --surface-2,
   --primary, --muted, --border-subtle and --font-sans. Every use
   fell through to a hardcoded fallback, and the fallbacks
   disagreed with each other — var(--surface, #151b28) in one
   place and var(--surface, #fff) in another. The bulk-edit
   action bar, the TOTP enrolment secret and the recovery-code
   grid rendered as white blocks in a black application.

   Nothing failed. Nothing was logged. The pages looked finished.
   That is the shape of defect this project keeps finding, and
   the answer is the same as it was for the data sources and the
   inline handlers: make the contract explicit, then assert it.

   `npm run test:contrast` reads THIS FILE and refuses a palette
   whose text tones fall below 4.5:1 against any surface they can
   land on — in both themes.

   ── The file that guarded nothing ─────────────────────────

   Which is what made the next defect so easy to miss. This file
   was written, audited, and linked by no page. `app.html` loaded
   only design-system.css, which carried its own hardcoded
   dark-only palette; index.html carried a third. test:contrast
   read this file, measured it, and passed — green, every run,
   over a palette that shipped to nobody.

   The same shape again: a check that passes while asserting
   nothing about what a person actually sees. So the check now
   also asserts that this file is *linked* by every page that
   renders the product, and that the default theme is the one we
   say it is. A contract nothing loads is not a contract.

   ── Light is the default ──────────────────────────────────

   Bare `:root` carries the light palette, so a page that ships
   before its theme script runs renders vellum rather than ink.
   Dark is opt-in, under [data-theme="dark"], and the choice is
   stored per-origin — so choosing dark on the marketing site
   carries into the app, which is the same browser and the same
   person.

   ── The rule the palette is built on ──────────────────────

   One interactive colour: blueprint. Every link, focus ring,
   selection and primary button. Every OTHER saturated colour in
   the product is a document status. If something is coloured, it
   is telling you what state a document is in — which is what
   makes a register of four hundred rows scannable at all.
   ══════════════════════════════════════════════════════════ */

/* ── LIGHT — "vellum" ──────────────────────────────────────
   Not white. The off-white of drafting film, so a white card
   lifts off it and the eye has somewhere to rest on a page that
   is mostly table.                                             */
:root,
[data-theme="light"] {
  color-scheme: light;

  --base:#F4F2EE;
  --surface:#FFFFFF;
  --raised:#FFFFFF;
  --sunk:#EAE7E1;
  /* The ground of an input or a text area. White in light, because
     a field is where you write and drafting film is where you have
     already written. */
  --field:#FFFFFF;

  --ink-1:#15171B;
  --ink-2:#565C66;
  /* 4.55:1. The first attempt at this tone was #848A93, which measured
     3.11:1 on vellum — worse than the value being replaced, in a
     proposal that criticised it. Light mode is where contrast bugs
     multiply: a grey that reads fine on near-black is routinely two
     points short on off-white. */
  --ink-3:#63686E;

  --line:rgba(18,20,24,.11);
  --line-2:rgba(18,20,24,.20);

  --accent:#0F6E9E;
  --accent-press:#0B5578;
  --accent-wash:rgba(15,110,158,.09);
  --accent-ring:rgba(15,110,158,.40);
  /* The label on a filled accent button. It is not --ink-1 and it is
     not always white: the accent is dark in light mode and light in
     dark mode, so the text on top has to invert with it. Asserted
     against --accent by test:contrast, because a primary button whose
     label is unreadable is the most expensive kind of unreadable. */
  --on-accent:#FFFFFF;

  --hover:rgba(18,20,24,.045);
  --active:rgba(15,110,158,.11);

  --st-ifc:#0F6B32;   --st-ifc-bg:rgba(21,128,61,.10);
  --st-ifa:#0369A1;   --st-ifa-bg:rgba(3,105,161,.10);
  --st-ifr:#8A5405;   --st-ifr-bg:rgba(161,98,7,.11);
  /* Issued for HAZOP — the one gate specific to a P&ID, not the
     general document flow, so it gets its own hue rather than
     borrowing IFR's. Burnt orange: a hazard colour, not a status one. */
  --st-ifh:#9A3412;   --st-ifh-bg:rgba(154,52,18,.10);
  --st-drf:#5B6470;   --st-drf-bg:rgba(91,100,112,.10);
  --st-ab:#6D28D9;    --st-ab-bg:rgba(109,40,217,.09);
  --st-void:#B91C1C;  --st-void-bg:rgba(185,28,28,.09);

  --glass-bg:rgba(255,255,255,.62);
  --glass-edge:inset 0 1px 0 rgba(255,255,255,.85);
  --glass-drop:0 8px 32px rgba(20,24,32,.11);

  --sheet-grid:rgba(18,24,38,.045);
  --shadow-1:0 1px 2px rgba(18,24,38,.07);
  --shadow-2:0 4px 16px rgba(18,24,38,.09);
  --shadow-3:0 18px 48px rgba(18,24,38,.15);
}

/* ── DARK — "ink" ──────────────────────────────────────────
   Warm-neutral graphite, not navy. A navy at 5% lightness reads
   as a screen with the brightness down; graphite reads as a room
   with the lights off, and it gives translucent panels something
   to pick up.                                                  */
[data-theme="dark"] {
  color-scheme: dark;

  --base:#0E1013;
  --surface:#16191E;
  --raised:#1D2127;
  --sunk:#0A0C0F;
  --field:#0A0C0F;

  --ink-1:#ECEEF1;
  --ink-2:#9BA3AE;
  /* 4.54:1 against the worst of base, surface and sunk. The value it
     replaced sat at 3.43:1 and was used for every column header in the
     register — small text, where a short ratio does the most damage. */
  --ink-3:#7A828E;

  --line:rgba(255,255,255,.09);
  --line-2:rgba(255,255,255,.16);

  --accent:#5AB0E0;
  --accent-press:#7CC3EA;
  --accent-wash:rgba(90,176,224,.14);
  --accent-ring:rgba(90,176,224,.45);
  /* Ink, not white — the dark theme's accent is a pale blue, and white
     on it measures 2.2:1. */
  --on-accent:#0E1013;

  --hover:rgba(255,255,255,.055);
  --active:rgba(90,176,224,.16);

  --st-ifc:#4ADE80;   --st-ifc-bg:rgba(74,222,128,.13);
  --st-ifa:#56C7F0;   --st-ifa-bg:rgba(86,199,240,.13);
  --st-ifr:#FBBF24;   --st-ifr-bg:rgba(251,191,36,.13);
  --st-ifh:#FB923C;   --st-ifh-bg:rgba(251,146,60,.15);
  --st-drf:#96A0AC;   --st-drf-bg:rgba(150,160,172,.13);
  --st-ab:#B69CF5;    --st-ab-bg:rgba(182,156,245,.13);
  --st-void:#F87171;  --st-void-bg:rgba(248,113,113,.13);

  /* Glass. Chrome only — sidebar, top bar, modals, toasts, popovers,
     the floating markup toolbar. Never a table, a form or the drawing
     canvas: translucency means "this layer is above your work", and a
     register is the work. */
  --glass-bg:rgba(22,25,30,.66);
  --glass-edge:inset 0 1px 0 rgba(255,255,255,.09);
  --glass-drop:0 8px 32px rgba(0,0,0,.34);

  --sheet-grid:rgba(255,255,255,.032);
  --shadow-1:0 1px 2px rgba(0,0,0,.35);
  --shadow-2:0 4px 16px rgba(0,0,0,.30);
  --shadow-3:0 18px 48px rgba(0,0,0,.45);
}

/* ── Theme-independent ──────────────────────────────────────
   Type, spacing and radii do not change between themes, so they
   are declared once. A token that appears in only one theme
   block is a component that renders in one theme and vanishes in
   the other — which is what test:contrast asserts against.     */
:root {
  --f-display:'Archivo','Helvetica Neue',Arial,sans-serif;
  --f-body:'Inter',system-ui,-apple-system,sans-serif;
  --f-mono:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,monospace;

  --sp-1:4px;  --sp-2:8px;  --sp-3:12px; --sp-4:16px;
  --sp-5:20px; --sp-6:24px; --sp-8:32px; --sp-10:40px;
  --sp-12:48px; --sp-16:64px;

  --r-sm:6px; --r-md:10px; --r-lg:16px; --r-xl:22px; --r-full:999px;

  --sidebar-width:260px;
  --sidebar-collapsed:64px;
  --topbar-height:56px;

  --ease:cubic-bezier(.22,.8,.28,1);
}
