/* ============================================================
   TRICSY — DESIGN TOKENS
   Warm monochrome. Two modes, one ramp.

   White is #FFF9F2, never #FFFFFF.
   Black is #070605, never #000000.
   Every neutral sits between 28 and 34 degrees hue.

   Light mode ships as the default. Dark mode is applied with
   <html data-theme="dark"> and also follows the OS setting
   when no attribute is present.
   ============================================================ */

:root {
  /* --- surfaces ------------------------------------------------
     Sections are the extreme, the ground is one step toward the
     middle. A card is cut into the plane, it does not float. */
  --surface:        #FFF9F2;
  --surface-sunk:   #F9F2E8;
  --paper:          #F6EFE4;

  /* --- ink --------------------------------------------------- */
  --ink:            #14120F;
  --ink-secondary:  #4A4239;
  --ink-muted:      #766D63;   /* 4.89:1 on --surface — clears WCAG AA */
  --ink-inverse:    #FFF9F2;

  /* --- lines ------------------------------------------------- */
  --line:           #E4DCD0;
  --line-strong:    #D8CFC1;
  --hatch:          #EDE5D9;

  /* --- action ------------------------------------------------ */
  --accent:         #14120F;
  --accent-hover:   #332E27;
  --on-accent:      #FFF9F2;
  --accent-tint:    #EFE8DC;

  /* --- data -------------------------------------------------- */
  --chart-1:        #201C18;
  --chart-2:        #4A423A;
  --chart-3:        #857A6D;
  --chart-4:        #B0A697;
  --chart-5:        #D6CCBD;
  --chart-grid:     #E4DCD0;
  --chart-track:    #F0E9DD;

  /* --- gradients ---------------------------------------------
     Only ever under a data curve. Never a card, button, section
     or hero. Both fade to fully transparent so the ground shows
     through and they work in either mode unchanged. */
  --grad-warm-top:    #201C1845;
  --grad-warm-bottom: #201C1800;
  --grad-cool-top:    #007FCF33;
  --grad-cool-bottom: #00BFFF00;
  --grad-wash-top:    #14120F1A;
  --grad-wash-bottom: #14120F00;

  /* --- state -------------------------------------------------
     The only non-monochrome colours in the system. A badge, a
     failed field, a threshold. Never a background. */
  --positive:       #00A96E;
  --positive-tint:  #DEF0E4;
  --negative:       #D9455C;
  --negative-tint:  #F8E3DF;
  --warning:        #B87400;

  /* --- type -------------------------------------------------- */
  --font-display: "Geist", system-ui, sans-serif;
  --font-body:    "Geist", system-ui, sans-serif;
  --font-mono:    "Geist Mono", ui-monospace, monospace;

  /* Tracking is expressed in em, never px. Headings use fluid clamp()
     sizes, so a fixed px value drifts: -2px is -3.8% at 52px but
     -6.7% at 30px and the letters collide on small screens. */
  /* Nine steps, in rem so the reader's own font-size setting still
     works. Two parallel tracks share the bottom: a sans track for
     reading (body, body-sm) and a mono track for machine metadata
     (spec, mono-label). Nothing else exists — if a size is not on
     this list it does not belong in the system. */
  --size-display:    6rem;      /* 96 */  --tracking-display: -0.036em;
  --size-h1:         4rem;      /* 64 */  --tracking-h1:      -0.0375em;
  --size-h2:         2.5rem;    /* 40 */  --tracking-h2:      -0.035em;
  --size-h3:         1.75rem;   /* 28 */  --tracking-h3:      -0.029em;
  --size-h4:         1.25rem;   /* 20 */  --tracking-h4:      -0.02em;
  --size-body:       1rem;      /* 16 */
  --size-body-sm:    0.875rem;  /* 14 */
  --size-spec:       0.75rem;   /* 12 */  --tracking-spec:     0.02em;
  --size-mono-label: 0.6875rem; /* 11 */  --tracking-mono-label: 0.145em;

  --leading-display: 0.95;
  --leading-heading: 1.1;
  --leading-body:    1.55;
  --leading-caption: 1.4;

  /* --- space ------------------------------------------------- */
  --space-1:  4px;
  --space-2:  8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* --- shape -------------------------------------------------
     Zero. The single exception in the whole system is the
     percentage pill inside a funnel chart. */
  --radius: 0px;
  --border-hairline: 1px;
  --border-rail:     2px;

  /* --- grid -------------------------------------------------- */
  --grid-columns:     12;
  --grid-cell:        200px;
  --grid-gutter:      24px;
  --grid-content-max: 1400px;
  --grid-margin:      100px;

  /* --- blueprint nodes --------------------------------------- */
  --node-xs:       5px;
  --node-sm:       7px;
  --node-md:       9px;
  --node-lg:      13px;
  --node-hover:   16px;
  --node-hit:     88px;   /* reveal radius, not the visible node */

  /* --- motion ------------------------------------------------ */
  --reveal-in:  120ms;
  --reveal-out: 200ms;
  --ease:       cubic-bezier(0.16, 1, 0.3, 1);
}

/* Dark mode. Same tokens, mirrored values.
   Note the inversion: sections become the darkest layer and the
   ground sits one step lighter. Do not "fix" this — it is the
   defining trait of the system. */
:root[data-theme="dark"] {
  --surface:        #070605;
  --surface-sunk:   #0E0C0B;
  --paper:          #100E0D;

  --ink:            #FFF9F2;
  --ink-secondary:  #C3B9AE;
  --ink-muted:      #8A8078;
  --ink-inverse:    #070605;

  --line:           #2A2622;
  --line-strong:    #322D28;
  --hatch:          #181513;

  --accent:         #FFF9F2;
  --accent-hover:   #E6DED4;
  --on-accent:      #070605;
  --accent-tint:    #16130F;

  --chart-1:        #F5EDE3;
  --chart-2:        #C9BCAC;
  --chart-3:        #978A7C;
  --chart-4:        #6B6155;
  --chart-5:        #3A342D;
  --chart-grid:     #2A2622;
  --chart-track:    #100E0D;

  --grad-warm-top:    #F5EDE345;
  --grad-warm-bottom: #F5EDE300;
  --grad-cool-top:    #007FCF45;
  --grad-cool-bottom: #00BFFF00;
  --grad-wash-top:    #FFF9F22E;
  --grad-wash-bottom: #FFF9F200;

  --positive:       #3FBF93;
  --positive-tint:  #17231C;
  --negative:       #FF7A7A;
  --negative-tint:  #2B1A1A;
  --warning:        #E0A54A;
}

/* Follow the operating system when the user has not chosen. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface:        #070605;
    --surface-sunk:   #0E0C0B;
    --paper:          #100E0D;
    --ink:            #FFF9F2;
    --ink-secondary:  #C3B9AE;
    --ink-muted:      #8A8078;
    --ink-inverse:    #070605;
    --line:           #2A2622;
    --line-strong:    #322D28;
    --hatch:          #181513;
    --accent:         #FFF9F2;
    --accent-hover:   #E6DED4;
    --on-accent:      #070605;
    --accent-tint:    #16130F;
    --chart-1:        #F5EDE3;
    --chart-2:        #C9BCAC;
    --chart-3:        #978A7C;
    --chart-4:        #6B6155;
    --chart-5:        #3A342D;
    --chart-grid:     #2A2622;
    --chart-track:    #100E0D;
    --grad-warm-top:    #F5EDE345;
    --grad-warm-bottom: #F5EDE300;
    --grad-cool-top:    #007FCF45;
    --grad-wash-top:    #FFF9F22E;
    --grad-wash-bottom: #FFF9F200;
    --positive:       #3FBF93;
    --positive-tint:  #17231C;
    --negative:       #FF7A7A;
    --negative-tint:  #2B1A1A;
    --warning:        #E0A54A;
  }
}

/* ============================================================
   BASE
   ============================================================ */

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  font-kerning: normal;
  text-wrap: pretty;          /* no single-word last lines */
}

/* Ivory on warm black reads a shade lighter than warm black on
   ivory, and light type on a dark ground needs more air. One extra
   notch of leading in dark mode, nowhere else. */
:root[data-theme="dark"] body { line-height: calc(var(--leading-body) + 0.06); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body { line-height: calc(var(--leading-body) + 0.06); }
}

/* Figures that sit in a column — table cells, metrics, axis labels —
   must align on the decimal. Geist Mono is already fixed-width; this
   is for the sans face. */
.tabular, table td, .metric-value { font-variant-numeric: tabular-nums; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--leading-heading);
  margin: 0;
  text-wrap: balance;         /* even line lengths on multi-line headings */
}
h1 { font-size: var(--size-h1); letter-spacing: var(--tracking-h1); }
h2 { font-size: var(--size-h2); letter-spacing: var(--tracking-h2); font-weight: 600; }
h3 { font-size: var(--size-h3); letter-spacing: var(--tracking-h3); font-weight: 600; }
h4 { font-size: var(--size-h4); letter-spacing: var(--tracking-h4); font-weight: 600; }

/* The mono label is a signature of the system. Uppercase, wide
   tracking, muted. Use it for section eyebrows, table headers, tags
   and metadata.

   HARD LIMIT: three words, or about forty characters. Uppercase and
   wide tracking work by removing word shapes, which is fine for a
   label the eye recognises whole and fatal for anything it has to
   read. Past that limit use .label-spec below. */
.label-mono {
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* The same voice for anything longer than a label: a spec line, a
   chart footnote, a caption carrying a full sentence. Still mono, so
   it still reads as machine metadata — but sentence case, normal
   tracking and one step larger, so it can actually be read. */
.label-spec {
  font-family: var(--font-mono);
  font-size: var(--size-spec);
  letter-spacing: var(--tracking-spec);
  line-height: 1.5;
  color: var(--ink-secondary);
}

/* ============================================================
   COMPONENTS
   ============================================================ */

.card {
  background: var(--surface);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-6);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 12px 20px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: var(--size-body-sm);
  font-weight: 500;
  cursor: pointer;
}
.btn:hover { background: var(--accent-hover); }

.btn--secondary {
  background: var(--surface);
  color: var(--ink);
  border: var(--border-hairline) solid var(--line-strong);
}
.btn--quiet     { background: var(--surface-sunk); color: var(--ink); }
.btn--ghost     { background: transparent;         color: var(--ink); }
/* Text on a destructive fill reads against the ground, not a fixed
   value — in dark mode --negative is a light salmon. */
.btn--danger    { background: var(--negative);     color: var(--paper); }
.btn:disabled   { background: var(--surface-sunk); color: var(--ink-muted); cursor: not-allowed; }

.btn--sm { padding:  8px 12px; font-size: var(--size-body-sm); }
.btn--lg { padding: 15px 26px; font-size: var(--size-body); }

.field {
  width: 100%;
  padding: 11px 12px;
  background: var(--surface);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--size-body-sm);
}
.field::placeholder { color: var(--ink-muted); }
.field:focus        { outline: none; border-color: var(--accent); }
.field[aria-invalid="true"] { border-color: var(--negative); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px 8px;
  background: var(--positive-tint);
  color: var(--positive);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  font-weight: 500;
}
.badge--negative { background: var(--negative-tint); color: var(--negative); }
.badge--warning  { background: var(--surface); color: var(--warning); border: 1px solid var(--warning); }
.badge--neutral  { background: var(--accent-tint); color: var(--ink); }

.tag {
  display: inline-flex;
  padding: 5px 9px;
  border: var(--border-hairline) solid var(--line-strong);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-secondary);
}

/* ============================================================
   BLUEPRINT GRID
   The construction grid behind hero sections. Nodes sit on the
   intersections: most are absent, the rest are a mix of solid
   dots and open rings at four sizes.
   ============================================================ */

.blueprint {
  position: relative;
  background-image:
    linear-gradient(to right,  var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: var(--grid-cell) var(--grid-cell);
}

/* Invisible hit target, centred on the intersection. The node is then
   centred inside the hit, so it lands exactly on the crossing. Do not
   position the node with negative margins alone — it would be offset
   by half the hit as well as half itself.

   z-index 0 keeps the whole field underneath the content. The grid is
   construction, not decoration laid over the work — a node must never
   sit on top of the logo or a headline. */
.blueprint__hit {
  position: absolute;
  z-index: 0;
  width: var(--node-hit);
  height: var(--node-hit);
  transform: translate(-50%, -50%);
}

/* Anything placed inside a .blueprint sits above the field. Give it
   pointer-events:none so the nodes underneath still react. */
.blueprint > .blueprint__over { position: relative; z-index: 1; pointer-events: none; }

.blueprint__node {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--node-md);
  height: var(--node-md);
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--line-strong);
}
.blueprint__node--solid { background: var(--line-strong); border: 0; }
.blueprint__node--xs { width: var(--node-xs); height: var(--node-xs); }
.blueprint__node--sm { width: var(--node-sm); height: var(--node-sm); }
.blueprint__node--lg { width: var(--node-lg); height: var(--node-lg); }

/* A ghost node is absent at rest and appears under the cursor. */
.blueprint__node--ghost { opacity: 0; transition: opacity var(--reveal-out) var(--ease); }
.blueprint__hit:hover .blueprint__node--ghost { opacity: 1; transition-duration: var(--reveal-in); }

.blueprint__hit:hover .blueprint__node {
  width: var(--node-hover);
  height: var(--node-hover);
  background: var(--ink);
  border-color: var(--ink);
  box-shadow: 0 0 0 3px var(--paper);
  transition: width var(--reveal-in) var(--ease), height var(--reveal-in) var(--ease),
              background-color var(--reveal-in) var(--ease), border-color var(--reveal-in) var(--ease);
}
/* Never animate the grid lines themselves. */

/* 45 degree hatch. Marks an empty cell. */
.hatch {
  background-image: repeating-linear-gradient(
    45deg,
    var(--hatch) 0, var(--hatch) 1px,
    transparent 1px, transparent 10px
  );
}

/* ============================================================
   MOTION

   The timing tokens above are the whole vocabulary. Two rules use
   them outside the blueprint field.

   FEEDBACK. Every control that changes colour on hover, focus or
   press eases into it instead of snapping. 120ms is long enough to
   read as an answer and short enough that a fast hand never waits.
   A press is a 1px sink, never a scale: things in this system are
   cut into the plane, so they push in rather than pop out.

   THE MODE SWITCH. Light and dark are one ramp mirrored, and the
   switch is what makes that legible — you watch paper and surface
   trade places, and the inverted depth stops being a claim in a
   document. The transition is armed only while the switch happens:
   set data-switching on the root, clear it after ~320ms. Nothing
   carries a permanent transition on every element, so this costs
   nothing the rest of the time.

       document.documentElement.dataset.switching = '';
       setTimeout(() => delete document.documentElement.dataset.switching, 320);
   ============================================================ */

.btn, .field, .tab, .badge {
  transition: background-color var(--reveal-in) var(--ease),
              border-color     var(--reveal-in) var(--ease),
              color            var(--reveal-in) var(--ease);
}
.btn { transition: background-color var(--reveal-in) var(--ease), transform 80ms var(--ease); }
.btn:active { transform: translateY(1px); }

tbody tr { transition: background-color var(--reveal-in) var(--ease); }
tbody tr:hover { background: var(--surface-sunk); }

:root[data-switching] *,
:root[data-switching] *::before,
:root[data-switching] *::after {
  transition: background-color 260ms var(--ease),
              border-color     260ms var(--ease),
              color            260ms var(--ease),
              fill             260ms var(--ease),
              stroke           260ms var(--ease) !important;
}

@media (max-width: 768px) {
  :root { --grid-cell: var(--grid-cell-mobile, 100px); --grid-margin: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
