/* ============================================================================
   Polaris — Design Tokens · the single source of truth
   ----------------------------------------------------------------------------
   PHASE 0 (2026-08-27). This file previously described a DARK "space" theme
   (#050816 ground, cyan/aurora/gold) that the app never adopted: of its 61
   tokens exactly THREE were referenced anywhere, while 584 var() uses pointed
   at a competing :root declared inline in index.html. A documented design
   system nothing obeys is worse than none — it makes a restyle look like a
   token edit when it is really a 14,000-line hunt.

   So the values below are the palette the app ACTUALLY renders (a light
   slate ground, cyan accent), lifted verbatim so this change moves zero
   pixels. What changes is that they now live in one place and are named for
   what they are.

   Three lies are corrected here:
     --brand-red held #00E5FF, which is cyan. Every "red" in the codebase was
       cyan, so anyone editing "the red" would have edited the accent.
     Nine tokens were referenced but declared nowhere (--brand, --muted,
       --card, --border, --surface, --red, --brand-deep, --brief-col,
       --danger) and rendered as nothing at all.
     The eight node identity colours were raw hex in a stylesheet, so the
       nodes page had no token behind any colour that identifies it.

   Old names are kept as aliases, not values: nothing downstream breaks, and
   the next restyle is an edit to the ROLES block alone.
   Docs: design-system/polaris/MASTER.md
   ========================================================================== */
/* >>> PALETTE:BEGIN — generated by scripts/build-palette.mjs · do not edit by hand */
/* Palette: Espresso (espresso) — #291C0E #6E473B #A78D78 #BEB5A9 #E1D4C2 #F4E6D2
   accent #6E473B. Six colours in, every token out; see lib/palette.js
   for how a ramp becomes a theme. Rebuild: node scripts/build-palette.mjs */
:root {
  --c-accent:               #6E473B;
  --c-accent-deep:          #54362D;
  --c-on-accent:            #FFFFFF;
  --c-ink:                  #291C0E;
  --c-ink-2:                #6E473B;
  --c-ink-3:                #645349;
  --c-ground:               #F4E6D2;
  --c-surface:              #F9F1E5;
  --c-field:                #FBF6EE;
  --c-on-field:             #291C0E;
  --c-line:                 #BEB5A9;
  --c-line-soft:            #E1D4C2;
  --c-ok:                   #23964D;
  --c-warn:                 #DD9823;
  --c-err:                  #DF5454;
  --c-idle:                 #A78D78;
  --pv-provided:            #126E9F;
  --pv-provided-fg:         #2A47A3;
  --pv-provided-bg:         #DFEBFA;
  --pv-hypothesis-fg:       #6A2C9D;
  --pv-hypothesis-bg:       #F3EAFD;
  --pv-unknown:             #A78D78;
  --pv-unknown-fg:          #6E473B;
  --pv-unknown-line:        #BEB5A9;
  --pv-unknown-bg:          #F6EBDB;
  --role-researcher:        #4E87E3;
  --role-strategist:        #A865E7;
  --role-creative:          #DD9823;
  --role-digital:           #1BA7BF;
  --role-crm:               #DD5799;
  --role-affiliate:         #20A97C;
  --role-efficiency:        #7173E3;
  --role-execution:         #CC3636;
  --st-ok-fg:               #10604A;
  --st-ok:                  #117B5A;
  --st-ok-line:             #79DDB5;
  --st-ok-bg:               #EEFBF5;
  --st-ok-deep:             #1C5F36;
  --st-ok-line-2:           #AEECCF;
  --st-ok-bg-2:             #DFF9E8;
  --st-warn-fg:             #85431B;
  --st-warn:                #995C14;
  --st-warn-line:           #EAC95F;
  --st-warn-bg:             #F8F0CD;
  --st-warn-deep:           #844613;
  --st-warn-line-2:         #F1DF96;
  --st-warn-bg-2:           #FDFAED;
  --st-warn-base:           #DD9823;
  --st-warn-alt:            #D3A71F;
  --st-err-fg:              #8E2626;
  --st-err:                 #C03232;
  --st-err-line:            #F3AEAE;
  --st-err-bg:              #FBE5E5;
  --st-err-deep:            #A12828;
  --st-err-line-2:          #F9CFCF;
  --st-err-bg-2:            #FDF3F3;
  --st-note:                #186D66;
  --st-hypo:                #804BDC;
  --st-hypo-deep:           #6A33BE;
  --st-hypo-line:           #E9D9FB;
  --st-hypo-bg:             #FAF6FE;
  --st-hypo-bg-2:           #F6F4FE;
  --st-hypo-line-2:         #C8BCF6;
  --st-info:                #2E56C7;
  --st-info-line:           #C6DCF7;
  --st-info-bg:             #F1F6FD;
  --st-info-tint:           #8ACEEF;
  --st-pink:                #DD5799;
  --st-paper:               #F7EEE0;
  --st-paper-2:             #FAF4EB;
  --gold:                   #A3551A;
  --bg-2:                   #EDE0CC;
  --pv-verified:            #117B5A;
  --pv-verified-fg:         #1C5F36;
  --pv-verified-line:       #AEECCF;
  --pv-verified-bg:         #DFF9E8;
  --pv-observed:            #186D66;
  --pv-provided-line:       #C6DCF7;
  --pv-inferred:            #995C14;
  --pv-inferred-fg:         #85431B;
  --pv-inferred-line:       #F1DF96;
  --pv-inferred-bg:         #F8F0CD;
  --pv-hypothesis:          #804BDC;
  --pv-hypothesis-line:     #E9D9FB;
  --pv-needs:               #CC3636;
  --c-accent-soft:          rgba(110,71,59,0.12);
  --role-researcher-soft:   rgba(78,135,227,0.08);
  --role-strategist-soft:   rgba(168,101,231,0.08);
  --role-creative-soft:     rgba(221,152,35,0.08);
  --role-digital-soft:      rgba(27,167,191,0.08);
  --role-crm-soft:          rgba(221,87,153,0.08);
  --role-affiliate-soft:    rgba(32,169,124,0.08);
  --role-efficiency-soft:   rgba(113,115,227,0.08);
  --role-execution-soft:    rgba(204,54,54,0.08);
  --space:                  #F4E6D2;
  --graphite:               #EDE0CC;
  --panel:                  #F9F1E5;
  --panel-2:                #F7EEE0;
  --white:                  #291C0E;
  --slate:                  #645349;
  --slate-2:                #A78D78;
  --polar:                  #54362D;
  --aurora:                 #117B5A;
  --glass:                  rgba(41,28,14,0.035);
  --line-2:                 #E1D4C2;
  --color-border:           #BEB5A9;
  --glow-cyan:              rgba(110,71,59,0.35);
  --glow-aurora:            rgba(17,123,90,0.28);
  --grad-cyan:              linear-gradient(135deg,#6E473B 0%,#54362D 100%);
  --grad-head:              linear-gradient(180deg,#291C0E 0%,#6E473B 100%);
  --grad-accent:            linear-gradient(120deg,#6E473B 0%,#54362D 100%);
  --accent-rgb:             110,71,59;
  --ok-rgb:                 17,123,90;
  --ink-accent:             #6E473B;
  --ink-rgb:                41,28,14;
  --bg-rgb:                 244,230,210;
  --on-st-ok:               #FFFFFF;
  --on-st-warn:             #FFFFFF;
  --on-st-err:              #F4E6D2;
  --on-st-info:             #F4E6D2;
  --on-st-hypo:             #FFFFFF;
  --on-pv-verified:         #FFFFFF;
  --on-pv-provided:         #F4E6D2;
  --on-pv-inferred:         #FFFFFF;
  --on-pv-hypothesis:       #FFFFFF;
  --on-st-note:             #F4E6D2;
  --on-st-pink:             #291C0E;
  --on-pv-unknown:          #291C0E;
  --on-pv-observed:         #F4E6D2;
  --on-pv-needs:            #FFFFFF;
  --on-st-warn-base:        #291C0E;
  --on-st-warn-alt:         #291C0E;
  --on-c-ok:                #000000;
  --on-c-warn:              #291C0E;
  --on-c-err:               #000000;
  --on-c-idle:              #291C0E;
  --shell-rail:             #291C0E;
  --shell-rail-2:           #43372B;
  --shell-rail-ink:         #F4E6D2;
  --shell-rail-ink-2:       #AD9F8D;
  --shell-rail-line:        rgba(244,230,210,0.1);
  --shell-rail-hover:       rgba(244,230,210,0.06);
}

/* The same ramp read from the other end. Elevation still means lighter. */
body.theme-dark, [data-theme="dark"] {
  --c-accent:               #BC8E80;
  --c-accent-deep:          #C8A297;
  --c-on-accent:            #000000;
  --c-ink:                  #F4E6D2;
  --c-ink-2:                #E1D4C2;
  --c-ink-3:                #BEB5A9;
  --c-ground:               #1B1209;
  --c-surface:              #291C0E;
  --c-field:                #FBF6EE;
  --c-on-field:             #291C0E;
  --c-line:                 #4F3427;
  --c-line-soft:            #3E291C;
  --c-ok:                   #61DC8E;
  --c-warn:                 #E6B156;
  --c-err:                  #E25A5A;
  --c-idle:                 #A78D78;
  --pv-provided:            #51B6EB;
  --pv-provided-fg:         #8EA1E2;
  --pv-provided-bg:         #17272B;
  --pv-hypothesis-fg:       #BF95E2;
  --pv-hypothesis-bg:       #423935;
  --pv-unknown:             #BEB5A9;
  --pv-unknown-fg:          #E1D4C2;
  --pv-unknown-line:        #4C3225;
  --pv-unknown-bg:          #332215;
  --role-researcher:        #568DE6;
  --role-strategist:        #A865E7;
  --role-creative:          #E6B156;
  --role-digital:           #54D3E8;
  --role-crm:               #E05D9D;
  --role-affiliate:         #5BE1B5;
  --role-efficiency:        #7173E3;
  --role-execution:         #D96363;
  --st-ok-fg:               #55E7BE;
  --st-ok:                  #55E7BA;
  --st-ok-line:             #105137;
  --st-ok-bg:               #172A1A;
  --st-ok-deep:             #68D492;
  --st-ok-line-2:           #143F2A;
  --st-ok-bg-2:             #191F13;
  --st-warn-fg:             #E29467;
  --st-warn:                #E8A354;
  --st-warn-line:           #764208;
  --st-warn-bg:             #3D2408;
  --st-warn-deep:           #E69456;
  --st-warn-line-2:         #5C3408;
  --st-warn-bg-2:           #2E1C09;
  --st-warn-base:           #E6B156;
  --st-warn-alt:            #E8C455;
  --st-err-fg:              #E08A8A;
  --st-err:                 #DA6868;
  --st-err-line:            #781C17;
  --st-err-bg:              #3E160E;
  --st-err-deep:            #E38888;
  --st-err-line-2:          #5D1913;
  --st-err-bg-2:            #2E140C;
  --st-note:                #5FDDD3;
  --st-hypo:                #9B72E6;
  --st-hypo-deep:           #B091E3;
  --st-hypo-line:           #4A2576;
  --st-hypo-bg:             #2C1932;
  --st-hypo-bg-2:           #251620;
  --st-hypo-line-2:         #3C2057;
  --st-info:                #6E8DDF;
  --st-info-line:           #1C2F6C;
  --st-info-bg:             #1B1D2E;
  --st-info-tint:           #8ACEEF;
  --st-pink:                #E05D9D;
  --st-paper:               #352416;
  --st-paper-2:             #302013;
  --gold:                   #E69456;
  --bg-2:                   #22170B;
  --pv-verified:            #55E7BA;
  --pv-verified-fg:         #68D492;
  --pv-verified-line:       #143F2A;
  --pv-verified-bg:         #191F13;
  --pv-observed:            #5FDDD3;
  --pv-provided-line:       #1C2F6C;
  --pv-inferred:            #E8A354;
  --pv-inferred-fg:         #E29467;
  --pv-inferred-line:       #5C3408;
  --pv-inferred-bg:         #3D2408;
  --pv-hypothesis:          #B597EF;
  --pv-hypothesis-line:     #4A2576;
  --pv-needs:               #D96363;
  --c-accent-soft:          rgba(188,142,128,0.12);
  --role-researcher-soft:   rgba(86,141,230,0.08);
  --role-strategist-soft:   rgba(168,101,231,0.08);
  --role-creative-soft:     rgba(230,177,86,0.08);
  --role-digital-soft:      rgba(84,211,232,0.08);
  --role-crm-soft:          rgba(224,93,157,0.08);
  --role-affiliate-soft:    rgba(91,225,181,0.08);
  --role-efficiency-soft:   rgba(113,115,227,0.08);
  --role-execution-soft:    rgba(217,99,99,0.08);
  --space:                  #1B1209;
  --graphite:               #22170B;
  --panel:                  #291C0E;
  --panel-2:                #352416;
  --white:                  #F4E6D2;
  --slate:                  #BEB5A9;
  --slate-2:                #A78D78;
  --polar:                  #C8A297;
  --aurora:                 #55E7BA;
  --glass:                  rgba(244,230,210,0.035);
  --line-2:                 #3E291C;
  --color-border:           #4F3427;
  --glow-cyan:              rgba(188,142,128,0.35);
  --glow-aurora:            rgba(85,231,186,0.28);
  --grad-cyan:              linear-gradient(135deg,#BC8E80 0%,#C8A297 100%);
  --grad-head:              linear-gradient(180deg,#F4E6D2 0%,#E1D4C2 100%);
  --grad-accent:            linear-gradient(120deg,#BC8E80 0%,#C8A297 100%);
  --accent-rgb:             188,142,128;
  --ok-rgb:                 85,231,186;
  --ink-accent:             #BC8E80;
  --ink-rgb:                244,230,210;
  --bg-rgb:                 27,18,9;
  --on-st-ok:               #1B1209;
  --on-st-warn:             #1B1209;
  --on-st-err:              #1B1209;
  --on-st-info:             #1B1209;
  --on-st-hypo:             #1B1209;
  --on-pv-verified:         #1B1209;
  --on-pv-provided:         #1B1209;
  --on-pv-inferred:         #1B1209;
  --on-pv-hypothesis:       #1B1209;
  --on-st-note:             #1B1209;
  --on-st-pink:             #1B1209;
  --on-pv-unknown:          #1B1209;
  --on-pv-observed:         #1B1209;
  --on-pv-needs:            #1B1209;
  --on-st-warn-base:        #1B1209;
  --on-st-warn-alt:         #1B1209;
  --on-c-ok:                #1B1209;
  --on-c-warn:              #1B1209;
  --on-c-err:               #1B1209;
  --on-c-idle:              #1B1209;
  --shell-rail:             #292018;
  --shell-rail-2:           #433B34;
  --shell-rail-ink:         #F4E6D2;
  --shell-rail-ink-2:       #ADA191;
  --shell-rail-line:        rgba(244,230,210,0.1);
  --shell-rail-hover:       rgba(244,230,210,0.06);
}
/* <<< PALETTE:END */


/* ── The scale ──────────────────────────────────────────────────────────────
   Everything geometric is a multiple of the root, and the root grows with the
   viewport CONTINUOUSLY — no step, no breakpoint, nothing to jump at. Dragging
   a window edge moves every box together instead of re-flowing it sixteen
   times, which is what a stack of breakpoints does and what made this feel
   unfinished.

   15px at a phone, ~16.6px at a laptop, 18px from a large desktop up. A ~20%
   range: enough that a wide screen breathes, small enough that nothing has to
   be redrawn to survive it. Written in px rather than rem so a user's own
   browser font setting is not multiplied twice. */
html { font-size: clamp(15px, 14px + 0.25vw, 18px); }

/* ── Touch ──────────────────────────────────────────────────────────────────
   Keyed on the POINTER, not the width: a narrow desktop window is not a thumb,
   and a tablet in landscape is.

   Here rather than in a page, because a rule written in one page protects one
   page. This was in index.html for an afternoon and measured 0 controls under
   the floor there while landing.html — the first thing anyone touches on a
   phone — had 38 of 45.

   min-height and min-width, never height and width: a wrapped label still
   decides how large its control gets, and a floor that clips text is not an
   improvement. */
@media (pointer: coarse) {
  button, .btn, a.btn, select, summary, [role="button"] { min-height: 44px; }
  /* A control whose label is an icon or one or two characters has no text to
     give it width, so it needs the floor in both directions. */
  button:empty, .icon-btn, [aria-label]:is(button, a.btn) { min-width: 44px; }
}


:root{

              /* cyan — the interactive accent          */

               /* slate-700 — secondary text             */

              /* slate-50  — page ground                */

                /* slate-200 — borders                    */

  /* ── Provenance — "how much can you trust this value?" ─────────────────
     A correction to what this file claimed on the day it was written. It said
     five colours here were "Polaris's actual identity … read everywhere a
     claim, a node, or a score is shown". They were read NOWHERE. The app's
     verification panel used four raw hexes of its own, the client report used
     a third set inline, and these five sat here describing neither. A token
     naming an intention nothing implements is not a design system; it is a
     wish, and it is worse than a raw hex because it reads as settled.

     These are ALIASES of the status family, not second definitions: today
     "verified" IS the ok green. They are named apart because they answer a
     different question — a node can finish successfully (status: ok) on
     evidence that is only inferred (provenance: amber) — so the two have to
     be able to move independently later without one dragging the other.

     Six levels, because the two surfaces between them render six: the panel
     shows where a value came from, the report also distinguishes second-hand
     observation from first-party verification, and both need "unknown". */

                   /* the client told us              */

                    /* not stated either way           */

  /* ── Node identity — one colour per role in the 8-node workflow ─────────
     Was eight raw hex values in a stylesheet; the nodes page could not be
     themed without editing rules by hand. */

  /* ── Status surfaces ────────────────────────────────────────────────────
     The appendices render state — ok / at-risk / blocked / note — and each
     state needs four values, not one: deep text, a base, a border tint and a
     background wash. The app already used all four for every state; they were
     just 39 loose hex values spread through the stylesheet, so restyling a
     state meant finding all four by eye.

     Every value here is EXACTLY what the app rendered before. Naming them
     changes nothing on screen and makes a state themeable in one line.

     Kept apart from --ok/--warn/--err, which are the chrome colours for
     interactive controls: a green button and a green "done" chip are different
     jobs and were already different greens. Collapsing them would be a
     redesign, not a refactor. */

                      /* near-white washes */

  /* Box geometry is expressed in rem so ONE number scales the whole system.
     A box should scale, not reflow: at any width its padding, its radius and
     its gaps keep the same proportion to each other, instead of a 16px padding
     looking generous at 390px and mean at 1920px.

     rem and not a transform: transform:scale() blurs text at fractional
     scales, breaks hit-testing and scroll, and would take a 12px label down to
     4px on a phone. This scales the GEOMETRY and leaves type alone.

     FONT SIZES STAY IN px on purpose. The readability suite holds a 12px
     floor, and a floor that moves with the viewport is not a floor. */
  --space-xs:0.25rem; --space-sm:0.5rem; --space-md:1rem; --space-lg:1.5rem;
  --space-xl:2rem; --space-2xl:3rem; --space-3xl:4rem;
  --radius-sm:0.625rem; --radius:0.875rem; --radius-lg:1.125rem; --radius-full:999px;
  --shadow:0 1px 2px rgba(15,23,42,.05), 0 4px 12px rgba(15,23,42,.06);
  --shadow-lg:0 8px 30px -6px rgba(15,23,42,.14);
  /* Motion: one duration and one curve, so nothing in the UI moves at a
     speed nothing else moves at. Respect the OS setting — see the
     prefers-reduced-motion block at the end. */
  --ease:cubic-bezier(.2,.7,.3,1);
  --dur-fast:120ms; --dur:180ms; --dur-slow:280ms;

  /* Vazirmatn sits inside the Latin stack, not only in --font-fa: a page is
     bilingual sentence by sentence, and a Persian word inside an English line
     has to render in the same face as the Persian around it rather than in
     whichever fallback the machine happens to carry. The browser takes each
     glyph from the first family that has it, so one stack serves both scripts.

     Before this, one page in eighteen loaded a webfont at all. --font-body and
     --font-fa were declared here and used by NOTHING — the second time this
     file has named a system nobody implemented. The app and the dashboard were
     rendering Persian in whatever the operating system supplied, which is why
     the same word looked different on two pages of one product. */
  --font-body:"Inter","Vazirmatn",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --font-display:var(--font-body);
  --font-fa:"Vazirmatn","IRANSansX","Dana",-apple-system,sans-serif;
  --font-mono:ui-monospace,SFMono-Regular,Menlo,monospace;

  /* ── Aliases ───────────────────────────────────────────────────────────
     Names already used across index.html and the exporters. They are
     aliases, never second definitions, so there is exactly one place a
     colour is decided. */
  --brand-red:var(--c-accent);          /* misnamed: it is cyan, never red   */
  --brand-red-deep:var(--c-accent-deep);
  --brand-red-soft:var(--c-accent-soft);
  --on-accent:var(--c-on-accent);
  --ink:var(--c-ink); --ink-2:var(--c-ink-2); --ink-3:var(--c-ink-3);
  --bg:var(--c-ground); --bg-elev:var(--c-surface);
  --line:var(--c-line); --line-soft:var(--c-line-soft);
  --ok:var(--c-ok); --warn:var(--c-warn); --err:var(--c-err); --idle:var(--c-idle);

  /* Referenced in the app but declared nowhere before this file — they
     rendered as nothing. Bound to the role each usage clearly intended. */
  --brand:var(--c-accent); --brand-deep:var(--c-accent-deep);
  --surface:var(--c-surface); --card:var(--c-surface);
  --border:var(--c-line); --muted:var(--c-ink-3);
  --red:var(--c-err); --danger:var(--c-err);
  /* NOT a colour. `main` reads it as a grid track — `var(--brief-col, 380px)
     12px 1fr` — and the resize handle writes a px value onto <html>. Bound to
     `--pv-provided` it resolved to a hex, which made the whole
     grid-template-columns declaration invalid at computed-value time: the
     declaration was dropped and the two-column layout never applied at all
     until the handle was dragged. Declared here so the "referenced but never
     declared" rule still holds, with the width the layout actually wants. */
  /* The brief pane scales with the viewport rather than sitting at 380px from
     a laptop to a 4K display. A fixed column IS the resize problem in one
     line: every other track absorbs the change and this one alone stays put,
     so the proportion between the panes shifts every time the window moves.
     The drag-handle still overrides it — a person's own choice outranks the
     scale — and clamp keeps that choice between a readable minimum and a width
     past which a form column stops reading as one. */
  --brief-col:clamp(340px, 26vw, 460px);
  /* Three survivors of the old dark palette, still referenced with inline
     fallbacks. Bound to their CURRENT effective values so nothing shifts. */

  --cyan:var(--c-accent);

}


/* ── Type scale ─────────────────────────────────────────────────────────────
   Sizes were being chosen per component: 229 declarations sat below 12px, down
   to 10px, on pages a client reads. A step is a decision made once.

   12px is the floor, and it is a floor rather than a target — it is for a
   tracked micro-label, not for anything anyone has to read a sentence of. */
:root {
  --fs-micro:12px;   /* uppercase labels, table headers, chips              */
  --fs-xs:13px;      /* captions, help text                                 */
  --fs-sm:14px;      /* dense UI, table cells                               */
  --fs-base:15px;    /* app body                                            */
  --fs-md:16px;      /* reading body                                        */
  --fs-lg:18px; --fs-xl:22px; --fs-2xl:28px; --fs-3xl:36px; --fs-4xl:46px;
  --lh-tight:1.25; --lh:1.55; --lh-loose:1.75;
  --tracking-label:.06em;
  /* Persian needs no tracking and more leading than Latin: the script joins,
     so letter-spacing breaks the joins outright, and the ascenders and
     descenders need the room. */
  --lh-fa:1.85;
}

:where([lang="fa"], [dir="rtl"], .fa, .title-fa, .sub-fa, .fa-lbl) {
  font-family: var(--font-fa);
  line-height: var(--lh-fa);
  letter-spacing: normal;
}
/* A tracked uppercase label is a Latin device. Applied to Persian it separates
   letters that are supposed to join, and there is no uppercase to render. */
:where([lang="fa"], [dir="rtl"], .fa, .title-fa, .sub-fa, .fa-lbl) {
  text-transform: none;
}

/* ── Shared inline-icon system (see icons.js sprite) ── */
.ic{width:1em;height:1em;display:inline-block;vertical-align:-.125em;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none;}
.ic-fill{fill:currentColor;stroke:none;}

/* A control that sets no colour of its own gets the browser's, which is black
   in both themes. It was invisible on every dark card — "Manage billing" and
   "Sign out" among them — and nobody noticed while the product had one theme.
   Zero specificity, so any rule that does set a colour still wins. */
:where(button, [type="button"], [type="submit"], [type="reset"]) { color: inherit; }

/* ══════════════════════════════════════════════════════════════════════════
   Interaction layer — every action answers back
   --------------------------------------------------------------------------
   Audited before writing this: 101 buttons in the app against 2 :active rules,
   12 :focus-visible, and 35 transitions of which ZERO used a shared duration.
   So pressing a control mostly did nothing visible, keyboard users got almost
   no ring, and everything that did move moved at its own invented speed.

   Every selector below is wrapped in :where(), which has ZERO specificity.
   That is the whole reason this can be added to an 1.1 MB stylesheet safely:
   any existing rule — however weak — still wins, so this is a floor under the
   UI, never an override of it. Remove a component's bespoke hover and it
   inherits a sane one instead of nothing.

   Loaded by all 18 pages, so the vocabulary is the same everywhere: the same
   press, the same ring, the same disabled treatment on the dashboard, the
   pricing page and the samples.
   ═══════════════════════════════════════════════════════════════════════════ */

/* One duration and one curve. Named properties only — `transition: all` makes
   the browser watch every property and is the usual source of hover jank. */
:where(button, [role="button"], a[href], summary, .btn, .tab, input, select, textarea){
  transition:
    background-color var(--dur) var(--ease),
    border-color     var(--dur) var(--ease),
    color            var(--dur) var(--ease),
    box-shadow       var(--dur) var(--ease),
    opacity          var(--dur) var(--ease),
    transform        var(--dur-fast) var(--ease);
}

/* Keyboard focus is not decoration. It is the only way some people can see
   where they are, so it gets the accent at 2px with an offset that keeps it
   legible on both the light ground and the dark theme. :focus-visible, not
   :focus — a mouse click should not leave a ring behind. */
:where(button, [role="button"], a[href], summary, input, select, textarea, [tabindex]):focus-visible{
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* A press should be felt. One pixel of travel reads as a button going down
   without moving anything around it. */
:where(button, [role="button"], .btn, .tab):not(:disabled):not([aria-disabled="true"]):active{
  transform: translateY(1px);
}

/* Unavailable, and honest about it: dimmed, and the cursor says so before the
   click rather than after. */
:where(button, [role="button"], .btn, input, select, textarea):disabled,
:where([aria-disabled="true"]){
  opacity: .55;
  cursor: not-allowed;
}

/* Working. Anything that sets aria-busy while it waits gets the same
   treatment, so "the app is thinking" looks identical wherever it happens. */
:where([aria-busy="true"]){ cursor: progress; }
:where(button[aria-busy="true"], .btn[aria-busy="true"]){ opacity: .75; }

/* Pointer affordance for the things that are clickable but are not links. */
:where(button, [role="button"], .btn, .tab, summary):not(:disabled){ cursor: pointer; }

/* Touch targets. 44px is the floor below which a finger starts missing;
   min-height only, so nothing that is already larger is affected. */
@media (pointer: coarse){
  :where(button, [role="button"], .btn, .tab){ min-height: 44px; }
}

/* Motion is a preference, not a decision this app gets to make for someone. */
@media (prefers-reduced-motion: reduce){
  :root{ --dur-fast:0ms; --dur:0ms; --dur-slow:0ms; }
  *,*::before,*::after{ animation-duration:.01ms !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; scroll-behavior:auto !important; }
}
