/* MeshVault design tokens — the shipped mirror of the canonical palette.
 *
 * SOURCE OF TRUTH: /home/meshvault/Documents/MeshVault/system/design-tokens/meshvault.graphite.css,
 * which is itself read from BOUND FIGMA VARIABLES in file 2olO3EZ32oJjauaAqYjLUk,
 * page "01 · Tokens", node 1:2. Every value in the "Canonical" block below is copied
 * from that file byte for byte. Do not edit a hex here to match a rendered page —
 * change Figma, re-read the vault file, then mirror it here.
 *
 * Figma's locked note, verbatim:
 *   "LOCKED rev.2 (2026-07-02): pure Graphite monotone. Brand shade #33333A — not
 *    black, a shade. No accent color. Logo = Node Cube. Semantic colors only:
 *    green online, amber approvals, red danger."
 *
 * So: green is an ONLINE STATUS. Amber is an APPROVAL STATE. Red is DANGER. None of
 * the three is a link colour, an eyebrow colour, or a decoration. Emphasis on this
 * site comes from weight and space.
 */

:root {
  /* ---------- Canonical: 13 bound Figma colours ---------- */

  /* Surfaces */
  --c-canvas:        #fafaf8;
  --c-card:          #ffffff;
  --c-hover-subtle:  #f2f2f0;
  --c-border:        #e8e8e4;

  /* Text */
  --c-ink:           #1d1d1f;
  --c-secondary:     #6e6e73;

  /* Brand — a shade, deliberately not black */
  --c-brand:         #33333a;
  --c-brand-deep:    #2e2e33;
  --c-brand-tint:    #efeff2;

  /* Semantic only. Never decorative. */
  --c-running:       #34c759;
  --c-attention:     #ff9f0a;
  --c-danger:        #b3261e;

  /* Present in Figma but gated: the locked note says "no accent color".
   * Unused on this surface. Declared so nobody re-invents it as a literal. */
  --c-whisper-accent: #c77d2e;

  /* ---------- Canonical: type families ---------- */
  --f-ui:   "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---------- Canonical: type ramp ----------
   * Figma pins families and the 4pt grid, not a size ramp, so this is DERIVED —
   * and the floor is the load-bearing part. --t-2xs is 11px. No functional text
   * goes below it, and the fix for text that does not fit is less text or more
   * room, never a smaller step. */
  --t-2xs:  0.6875rem; /* 11px — hard floor for functional text */
  --t-xs:   0.75rem;   /* 12px */
  --t-sm:   0.875rem;  /* 14px */
  --t-base: 1rem;      /* 16px */
  --t-lg:   1.1875rem; /* 19px */
  --t-xl:   1.5rem;    /* 24px */
  --t-2xl:  2rem;      /* 32px */
  --t-3xl:  clamp(1.8125rem, 3.7vw, 3.125rem);

  /* ---------- Canonical: radius ---------- */
  --r-card:  12px;
  --r-pill:  999px;
  --r-input: 10px;

  /* ---------- Canonical: 4pt spacing grid ---------- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-12: 48px;
  --sp-16: 64px;

  /* ---------- Derived: elevation ----------
   * Ink at low alpha, with a real offset and a soft blur. A zero-offset coloured
   * halo is decoration rather than depth, so there is no token for one. */
  --shadow-lift:   0 8px 22px rgba(29, 29, 31, .10);
  --shadow-panel:  0 26px 60px rgba(29, 29, 31, .16);
  --shadow-device: 0 26px 60px rgba(29, 29, 31, .28);

  /* ---------- Derived: motion ----------
   * Not in Figma. Operate-surface budget: state changes read as instant, nothing
   * on this surface earns choreography. */
  --dur-fast: 150ms;
  --dur:      220ms;
  --ease:     cubic-bezier(.22, 1, .36, 1);
}

/* ---------- Compatibility bridge ----------
 * assets/site.css declares its own :root with eleven short names, four of which
 * had drifted from the palette and one of which (--blue) holds the graphite brand
 * shade under a colour name that is simply false. Five sub-pages load that file.
 *
 * Rather than edit site.css, this block re-points the legacy names at the canonical
 * values. Load tokens.css AFTER site.css and these win on source order.
 *
 * Correction, measured 2026-07-29: the reason recorded here previously was that
 * "index.html loads it too". It does not. `grep -l assets/site.css *.html */*.html`
 * returns 404.html, skills/index.html, contact/index.html, install/index.html and
 * demos/index.html; index.html carries its own CSS in two inline <style> blocks and
 * loads no external sheet. The real reason to stay out of site.css is still good,
 * just different: four of those five pages belong to other owners working in
 * parallel, so an additive correction here beats an in-place edit there.
 *
 * ponytail: alias shim, delete it when site.css itself moves onto --c-* names.
 */
:root {
  --ink:  var(--c-ink);
  --gr:   var(--c-brand);
  --gr2:  var(--c-brand-deep);
  --mut:  var(--c-secondary);
  --bd:   var(--c-border);       /* was #E6E7EA */
  --soft: var(--c-hover-subtle); /* was #F2F2F4 */
  --cv:   var(--c-canvas);
  --grn:  var(--c-running);
  --grnT: var(--c-running);      /* was a byte-identical duplicate of --grn */
  --blue: var(--c-brand);        /* name lies; value was already the brand shade */
  --amb:  var(--c-attention);
  --bg:   var(--c-canvas);       /* site.css never defined this; pages fell back
                                  * to #f6f5f2 from a retired palette */

  /* site.css:407 declares two more surface literals, off the palette by a hair
   * and used for body background and the .sec.alt band the install page runs. */
  --paper:   var(--c-canvas);       /* was #FBFBFA */
  --paper-2: var(--c-hover-subtle); /* was #F6F6F4 */
}

/* ---------- Contrast pairings, measured ----------
 * Computed against WCAG AA body text (4.5:1) on 2026-07-29:
 *
 *   ink       on card 16.8 · canvas 16.1 · tint 14.7 · hover 15.0   all pass
 *   brand     on card 12.5 · canvas 12.0 · tint 10.9 · hover 11.2   all pass
 *   secondary on card  5.1 · canvas  4.9 · hover  4.5               pass
 *   secondary on TINT  4.4                                          FAILS
 *
 *   running   on card  2.2      amber/attention on card 2.1
 *
 * Two rules fall out of that, and both are load-bearing:
 *
 *   1. A --c-brand-tint surface carries ink or brand text. Never --c-secondary.
 *      It is the single failing neutral pairing on this palette, and it fails by
 *      0.08 — close enough to look fine and still be wrong.
 *   2. --c-running and --c-attention are INDICATORS, not text colours. At about
 *      2:1 they cannot carry a word. Put the hue in a dot, a border or a fill and
 *      keep the label in ink.
 */

/* ---------- Sub-page corrections to assets/site.css ----------
 * The eyebrow is under the floor at every width, not only on phones. site.css:373
 * drops it to 10px inside @media(max-width:560px), which the phone-scoped block
 * below used to be the whole answer to. But site.css:283 sets 10.5px with no media
 * query at all, so desktop was under the 11px floor too and nothing caught it. Same
 * bordered badge is also padded 6px vertically there, under the 8px minimum.
 *
 * One unconditional rule now covers both widths. Two things keep it from leaking:
 * legal.css also declares .eyebrow and loads after this file, so the legal family
 * keeps its own treatment; demos/hardware.html scopes its badge as .card .eyebrow,
 * which outranks a bare class. Checked both, 2026-07-29.
 */
.eyebrow {
  font-size: var(--t-2xs);
  padding: var(--sp-2) var(--sp-3);
}

/* site.css:131 pads the accordion rows 19px 6px. The row is a hover target with a
 * fill, so 6px puts the text almost against the edge of the thing that highlights.
 * Vertical goes onto the grid in the same move. */
details {
  padding: var(--sp-5) var(--sp-3);
}

/* site.css:95 gives .pillar a translucent white and a 12px backdrop blur. What sits
 * behind it is a flat vertical gradient, and a blurred flat gradient is the same
 * gradient, so the blur buys a compositing layer and no pixels. The fill becomes
 * the real card surface instead of 80% white over the band, which also matches
 * .step at site.css:105 instead of quietly differing from it. */
.pillar {
  background: var(--c-card);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The Node Cube's centre dot sat in the markup as fill="#fff" on four pages. A
 * presentation attribute is a CSS property, so a class reaches it — and this keeps
 * the mark's one light value on the token layer with everything else. */
.cube-core { fill: var(--c-card); }

/* ---------- Reduced motion: put the state feedback back ----------
 * assets/site.css:385 does this:
 *
 *   *,*::before,*::after{animation-duration:.001ms!important;
 *                        transition-duration:.001ms!important}
 *
 * A blanket kill does not just stop movement, it deletes every hover, focus,
 * selected and loading transition on the page. Someone who asked for less motion
 * still needs to see that a button took their click.
 *
 * So: restore the half that does not move — colour, fill, edge, elevation, opacity —
 * at a duration short enough to read as immediate. Movement (transform, translate,
 * scale, width) stays dead, which is the part the preference is actually about.
 *
 * This lives here rather than in site.css because index.html loads that file and
 * belongs to another owner. The real fix is to scope the rule at its source;
 * until then this covers the sub-pages only, and :where() keeps specificity at zero
 * so any page rule still wins on everything except the duration.
 */
@media (prefers-reduced-motion: reduce) {
  :where(a, button, summary, input, textarea, label,
         .pill, .card, .qchip, .pbtn, .mapp, .srow, .incoming, .req,
         [data-copy], [data-email]) {
    transition-property: color, background-color, border-color, box-shadow,
                         opacity, outline-color, text-decoration-color !important;
    transition-duration: var(--dur-fast) !important;
  }
}
