/* ============================================================
   TRICSY — WEB KIT
   Part 1 of 2: tokens, base type and core components. This half is
   the brand manual verbatim — it is generated from the same values
   as tricsy.tokens.json and must not be edited by hand.

   Part 2 (layout, page furniture, forms, tables, dialogs) lives at
   the bottom of this file under SITE LAYER.

   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;

  /* Which way an image logo has to be flipped to land on this plane. Every
     logo file we ship is white-on-transparent, so the light plane inverts it
     and the dark plane leaves it alone — one rule for the whole set instead
     of a per-file dark-mode exception. */
  --logo-ink-invert: 1;

  /* --- 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. */
  /* Fluid between a 375px phone and a 1400px canvas. The rem floor is
     the mobile size, the rem ceiling is the desktop size printed in the
     manual — nothing outside that range exists. This is why tracking is
     em: a fixed -2px is -3.8% at 64px but -6.7% at 30px, and the letters
     collide the moment the heading shrinks. */
  --size-display:    clamp(2.75rem, 1.02rem + 7.38vw, 6rem);     /* 44 -> 96 */
  --tracking-display: -0.036em;
  --size-h1:         clamp(2.25rem, 1.32rem + 3.98vw, 4rem);     /* 36 -> 64 */
  --tracking-h1:      -0.0375em;
  --size-h2:         clamp(1.75rem, 1.35rem + 1.71vw, 2.5rem);   /* 28 -> 40 */
  --tracking-h2:      -0.035em;
  --size-h3:         clamp(1.375rem, 1.17rem + 0.85vw, 1.75rem); /* 22 -> 28 */
  --tracking-h3:      -0.029em;
  --size-h4:         clamp(1.125rem, 1.06rem + 0.28vw, 1.25rem); /* 18 -> 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-cell-mobile: 100px;
  --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;
  --logo-ink-invert: 0;

  --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;
    --logo-ink-invert: 0;
    --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;
  /* The header is sticky and 72px tall, so an anchor that scrolls to the
     very top lands underneath it. Declared once here rather than as a
     scroll-margin on each target, which would have to be repeated on
     every new section and every pillar card. */
  scroll-padding-top: calc(72px + var(--space-4));
}
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-ink); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px 8px;
  background: var(--positive-tint);
  color: var(--positive-ink);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  font-weight: 500;
}
.badge--negative { background: var(--negative-tint); color: var(--negative-ink); }
.badge--warning  { background: var(--surface); color: var(--warning-ink); 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; }
/* ...but a button on top of the field still has to be clickable. The
   field keeps everything else that falls through it. */
.blueprint__over :is(a, button, input, select, textarea, summary) { pointer-events: auto; }

.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) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   ============================================================
   SITE LAYER
   Part 2 of 2. Everything a page needs that a brand manual does
   not: the container, the grid, the header and footer, forms,
   tables, dialogs, prose.

   Nothing here invents a value. Every size, colour and duration
   comes from the tokens above. If a number appears in this half
   that is not built from a token, it is a bug.
   ============================================================
   ============================================================ */


/* ============================================================
   STATE COLOUR, AS TEXT
   The three state colours in the manual are picked to read as marks
   — a dot, a fill, a band on a chart. As *text* on the ivory they
   fall short: the green measures 2.66:1 against the page, where 4.5
   is the floor for type this small, and a badge sets 11px mono in it.

   So the ramp keeps its values and gains three darker siblings for
   type only. Same hue, same saturation, lower lightness. Anything
   that draws — a chart series, a fill, an icon beside readable text
   — still uses --positive, --negative and --warning.

   In dark mode the originals already clear 8:1, so the two are the
   same colour there and the distinction costs nothing.

   MEASURED (worst ground, including each badge's own tint):
     --positive-ink  4.54:1     --negative-ink  4.51:1
     --warning-ink   4.50:1
   ============================================================ */

:root {
  --positive-ink: #007A50;
  --negative-ink: #C62841;
  --warning-ink:  #9A6100;
}
:root[data-theme="dark"] {
  --positive-ink: var(--positive);
  --negative-ink: var(--negative);
  --warning-ink:  var(--warning);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --positive-ink: var(--positive);
    --negative-ink: var(--negative);
    --warning-ink:  var(--warning);
  }
}

/* ============================================================
   BASE, CONTINUED
   The manual styles type. A page also has links, images, lists,
   rules and a focus ring.
   ============================================================ */

/* Links carry a 1px underline offset far enough to clear the
   descenders. Underline is not decoration here — it is the only
   thing distinguishing a link from body copy, because colour is
   reserved for state. Never remove it in running text. */
a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--line-strong);
  transition: text-decoration-color var(--reveal-in) var(--ease);
}
a:hover { text-decoration-color: var(--ink); }

/* A link that is a block — a card, a logo, a nav item — carries no
   underline, because its whole box is the target. */
a.u-plain, a.u-plain:hover { text-decoration: none; }

/* THE FOCUS RING.
   One ring, everywhere, for keyboard users only. The offset is what
   makes it readable on a zero-radius system: the ring sits outside
   the element rather than tracing its edge, so it never reads as a
   border that grew. Do not set outline:none anywhere without putting
   an equivalent back. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--border-rail) solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius);
}

/* Selection follows the accent, muted so a selected paragraph stays
   readable rather than becoming a block. */
::selection { background: var(--accent-tint); color: var(--ink); }

img, svg, video { max-width: 100%; height: auto; }
img { display: block; }

hr, .divider {
  height: 0;
  margin: var(--space-8) 0;
  border: 0;
  border-top: var(--border-hairline) solid var(--line);
}

p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }

small { font-size: var(--size-body-sm); color: var(--ink-secondary); }

code, kbd, pre, samp {
  font-family: var(--font-mono);
  font-size: 0.9em;
}
code {
  padding: 2px 5px;
  background: var(--surface-sunk);
  border: var(--border-hairline) solid var(--line);
}
pre {
  overflow-x: auto;
  margin: 0 0 var(--space-5);
  padding: var(--space-5);
  background: var(--surface-sunk);
  border: var(--border-hairline) solid var(--line);
  font-size: var(--size-spec);
  line-height: 1.6;
}
pre code { padding: 0; background: none; border: 0; }

/* Skip link. Off-screen until focused, then the first thing on the
   page. A keyboard user should never have to tab through a whole
   navigation to reach the content. */
.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: 100;
  padding: 10px 16px;
  background: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  transform: translateY(-200%);
}
.skip-link:focus { transform: translateY(0); }


/* ============================================================
   LAYOUT
   12 columns, 24px gutter, 1400px of content, 100px of margin.
   The margin collapses to 24px below 768px — a phone has no room
   to spend on air at the edges.
   ============================================================ */

.container {
  width: 100%;
  max-width: calc(var(--grid-content-max) + var(--grid-margin) * 2);
  margin-inline: auto;
  padding-inline: var(--grid-margin);
}

/* For running text. Roughly 70 characters a line, which is where
   the eye stops having to hunt for the start of the next one. */
.container--narrow { max-width: 760px; }
.container--wide   { max-width: none; }

/* Vertical rhythm between the bands of a page. One value, so two
   sections stacked never produce an accidental double gap. */
.section { padding-block: var(--space-9); }
.section--tight { padding-block: var(--space-7); }
.section--flush { padding-block: 0; }

/* A section cut into the plane. Remember rule 3: the surface is the
   extreme and the page is one step in, so this reads as cut, not
   floating — in dark mode it is darker than the page, not lighter. */
.section--surface {
  background: var(--surface);
  border-block: var(--border-hairline) solid var(--line);
}
.section--sunk { background: var(--surface-sunk); }

/* The 12-column grid itself. Children take .col-N. */
.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
}
.col-1  { grid-column: span 1; }
.col-2  { grid-column: span 2; }
.col-3  { grid-column: span 3; }
.col-4  { grid-column: span 4; }
.col-5  { grid-column: span 5; }
.col-6  { grid-column: span 6; }
.col-7  { grid-column: span 7; }
.col-8  { grid-column: span 8; }
.col-9  { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }

/* Equal-width cards, the common case, without counting columns.
   auto-fit means they reflow on their own — no breakpoint needed. */
.cards {
  display: grid;
  gap: var(--grid-gutter);
  grid-template-columns: repeat(auto-fit, minmax(var(--card-min, 280px), 1fr));
}
.cards--2 { --card-min: 420px; }
.cards--4 { --card-min: 220px; }

/* A stack is vertical rhythm between siblings. The gap is the only
   thing that sets it, so no child ever needs a margin. */
.stack       { display: flex; flex-direction: column; gap: var(--space-5); }
.stack--tight{ gap: var(--space-3); }
.stack--loose{ gap: var(--space-7); }

/* A cluster is a row of things that wraps: buttons, tags, meta. */
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.cluster--between { justify-content: space-between; }

/* Two columns that become one. The ratio is the asymmetric default
   from the manual — 7 and 5 of 12, not a half and a half. */
/* minmax(0, …) rather than a bare fr, and this is not a detail. A grid
   item defaults to min-width:auto, so one wide child — a code block, a
   long table, an unbreakable URL — pushes its whole column past the
   container instead of scrolling inside it. Every fr in this file is
   wrapped for that reason. */
.split {
  display: grid;
  gap: var(--space-8) var(--grid-gutter);
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  align-items: start;
}
.split--even    { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.split--reverse { direction: rtl; }
.split--reverse > * { direction: ltr; }


/* ============================================================
   PAGE FURNITURE
   ============================================================ */

/* ============================================================
   ICONS
   Lucide, at the version pinned in icons/tricsy-icons.svg. One local
   sprite, no CDN, no JavaScript, one cached request for the whole set.

       <svg class="icon" aria-hidden="true">
         <use href="/icons/tricsy-icons.svg#check"></use>
       </svg>

   The symbol carries geometry only; colour, size and stroke width all
   come from here, so an icon inherits the ink of whatever it sits in
   and flips with the mode like everything else.

   Stroke is 1.5, not Lucide's default 2 — the manual sets the lighter
   weight and it is what makes the icons sit with the type rather than
   shout over it.

   An icon that carries meaning on its own needs a label. An icon
   beside text that already says the same thing takes aria-hidden.
   ============================================================ */

.icon {
  width: 16px;
  height: 16px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
}
.icon--sm { width: 13px; height: 13px; }
.icon--lg { width: 20px; height: 20px; }
.icon--xl { width: 24px; height: 24px; }

/* Inside a button the icon takes the button's own colour, and the gap
   is already set by .btn — nothing else to do. */
.btn .icon { stroke-width: 1.75; }

/* THE LOGO.
   A mask, not an <img>. An <img> paints its own colours and cannot
   inherit anything, so it would need a second file for dark mode. A
   mask uses the SVG as a stencil and fills it with currentColor, so
   one file serves both modes and any ink you set on the parent.

   Set the height; the aspect ratio supplies the width. Never set both
   — the lockup and the symbol have different proportions.

   The minimums from the manual: 160px lockup, 96px small lockup,
   24px symbol. Below those the dot closes up and the mark stops
   being legible. */
.logo {
  display: block;
  /* Without this the box stretches to the width of a flex column and the
     mask centres itself inside it — the mark drifts to the middle of the
     footer. The aspect ratio sets the width; nothing else may. */
  align-self: start;
  height: 24px;
  aspect-ratio: 259.5 / 46.3;
  background-color: currentColor;
  -webkit-mask: url("tricsy-lockup.svg") no-repeat center / contain;
          mask: url("tricsy-lockup.svg") no-repeat center / contain;
}
.logo--ring {
  aspect-ratio: 361.41 / 88.72;
  -webkit-mask-image: url("tricsy-lockup-ring.svg");
          mask-image: url("tricsy-lockup-ring.svg");
}
.logo--symbol {
  aspect-ratio: 1;
  -webkit-mask-image: url("tricsy-symbol.svg");
          mask-image: url("tricsy-symbol.svg");
}
.logo--bare {
  aspect-ratio: 96 / 108;
  -webkit-mask-image: url("tricsy-symbol-bare.svg");
          mask-image: url("tricsy-symbol-bare.svg");
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--paper);
  border-bottom: var(--border-hairline) solid var(--line);
}

/* The bar is 72px so the lockup can sit at its 24px symbol minimum
   with real air around it. */
.site-header__bar {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  min-height: 72px;
}

/* The lockup inherits --ink through currentColor, so it flips with
   the mode and never needs a second file. Height, never width — the
   lockup and the symbol have different aspect ratios. */
.site-header__logo { display: flex; align-items: center; color: var(--ink); }
.site-header__logo .logo { height: 22px; }

.nav {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin-left: auto;
}
.nav__link {
  color: var(--ink-secondary);
  text-decoration: none;
  font-size: var(--size-body-sm);
  padding-block: var(--space-2);
  transition: color var(--reveal-in) var(--ease);
}
.nav__link:hover { color: var(--ink); }

/* The current page is marked with the rail, not with colour —
   colour is a signal and a location is not a state. */
.nav__link[aria-current="page"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--ink);
}

/* Burger. Hidden on desktop, and it is a real button with a real
   label, not a div with three spans. */
.nav-toggle {
  display: none;
  margin-left: auto;
  padding: var(--space-2);
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
}
.nav-toggle svg { display: block; }

/* ============================================================
   SITE LAYER — NAV MENU
   The Product entry is a <details>, so the open state lives in the
   markup and the element answers the keyboard without help. The panel
   hangs off the summary and stays narrow enough to clear the right
   edge of the bar; on a phone there is no panel at all, it unfolds in
   place inside the navigation sheet.
   ============================================================ */
.nav-menu { position: relative; }

/* Both markers have to go: list-style covers Firefox and the modern
   engines, ::-webkit-details-marker is still what Safari reads. */
.nav-menu__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  list-style: none;
  cursor: pointer;
}
.nav-menu__summary::-webkit-details-marker { display: none; }
.nav-menu__summary::marker { content: ""; }
.nav-menu[open] > .nav-menu__summary { color: var(--ink); }

.nav-menu__chevron {
  width: 14px;
  height: 14px;
  transition: transform var(--reveal-in) var(--ease);
}
.nav-menu[open] .nav-menu__chevron { transform: rotate(180deg); }

.nav-menu__panel {
  position: absolute;
  top: calc(100% + var(--space-3));
  left: calc(var(--space-4) * -1);
  z-index: 10;
  width: 300px;
  padding: var(--space-4);
  background: var(--paper);
  border: var(--border-hairline) solid var(--line);
}

.nav-menu__label {
  margin: var(--space-4) 0 var(--space-2);
  padding-inline: var(--space-3);
}
.nav-menu__label:first-child { margin-top: 0; }

.nav-menu__link {
  display: block;
  padding: var(--space-2) var(--space-3);
  color: var(--ink-secondary);
  text-decoration: none;
  font-size: var(--size-body-sm);
  transition: color var(--reveal-in) var(--ease),
              background var(--reveal-in) var(--ease);
}
.nav-menu__link:hover,
.nav-menu__link:focus-visible {
  color: var(--ink);
  background: var(--surface-sunk);
}

/* The number is the same count the pillar cards carry, so the menu and
   the section agree on what 03 is. */
.nav-menu__index {
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  color: var(--ink-muted);
  margin-right: var(--space-2);
}
.nav-menu__note {
  display: block;
  margin-top: 2px;
  font-size: var(--size-mono-label);
  color: var(--ink-muted);
}

.site-footer {
  padding-block: var(--space-8) var(--space-7);
  background: var(--surface);
  border-top: var(--border-hairline) solid var(--line);
}
.site-footer__grid {
  display: grid;
  gap: var(--space-7) var(--grid-gutter);
  grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr));
}
.site-footer__col { display: flex; flex-direction: column; gap: var(--space-3); }
.site-footer__col a {
  color: var(--ink-secondary);
  text-decoration: none;
  font-size: var(--size-body-sm);
}
.site-footer__col a:hover { color: var(--ink); text-decoration: underline; }
.site-footer__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: space-between;
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--line);
}


/* ============================================================
   HERO
   The blueprint field goes here and nowhere else. Content inside a
   .blueprint must carry .blueprint__over or the nodes will sit on
   top of the headline.
   ============================================================ */

.hero { position: relative; overflow: hidden; padding-block: var(--space-9); }
.hero__inner { max-width: 900px; }
.hero__title { margin-bottom: var(--space-5); }
.hero__title--display {
  font-size: var(--size-display);
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-display);
}

/* The opening paragraph. One step up from body, secondary ink, and
   capped at 60 characters so it stays a lede and not a column. */
.lede {
  max-width: 60ch;
  font-size: clamp(1.0625rem, 1rem + 0.28vw, 1.25rem);
  line-height: 1.5;
  color: var(--ink-secondary);
}

/* The section eyebrow: mono, uppercase, three words maximum. Past
   that limit use .label-spec — see rule 7. */
.eyebrow {
  display: block;
  margin-bottom: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Section head: eyebrow, heading, one paragraph. The pattern that
   opens most bands on the page. */
.section-head { max-width: 720px; margin-bottom: var(--space-7); }
.section-head > * + * { margin-top: var(--space-4); }


/* ============================================================
   CARDS, METRICS, MEDIA
   ============================================================ */

.card--link {
  display: block;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--reveal-in) var(--ease);
}
.card--link:hover { border-color: var(--line-strong); }
.card--flush { padding: 0; }
.card__body { padding: var(--space-6); }

/* A card head that reads as machine metadata above human copy. */
.card__meta {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-6);
  background: var(--surface-sunk);
  border-bottom: var(--border-hairline) solid var(--line);
}

/* The KPI tile. Figures take tabular numerals so a column of them
   lines up on the decimal — this is the whole reason the token
   exists. The delta is the one place a number carries colour. */
.metric { display: flex; flex-direction: column; gap: var(--space-2); }
.metric__label {
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}
.metric__value {
  font-size: clamp(2rem, 1.5rem + 2vw, 2.75rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.metric__delta {
  font-family: var(--font-mono);
  font-size: var(--size-spec);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.metric__delta--up   { color: var(--positive-ink); }
.metric__delta--down { color: var(--negative-ink); }

figure { margin: 0 0 var(--space-6); }
figcaption {
  margin-top: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--size-spec);
  letter-spacing: var(--tracking-spec);
  color: var(--ink-secondary);
}


/* ============================================================
   TABLE
   ============================================================ */

/* The wrapper is not optional. A table cannot shrink below its
   content, so without this the whole page scrolls sideways on a
   phone instead of the table doing it. */
.table-wrap {
  overflow-x: auto;
  border: var(--border-hairline) solid var(--line);
  background: var(--surface);
}
.table-wrap > table { border: 0; }

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  font-size: var(--size-body-sm);
}
thead th {
  padding: var(--space-3) var(--space-4);
  background: var(--surface-sunk);
  border-bottom: var(--border-hairline) solid var(--line-strong);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  font-weight: 400;
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-muted);
  text-align: left;
  white-space: nowrap;
}
tbody td {
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-hairline) solid var(--line);
  color: var(--ink-secondary);
  vertical-align: top;
}
tbody tr:last-child td { border-bottom: 0; }

/* Figures right-align so the decimal points stack. */
.td-num, th.td-num { text-align: right; font-variant-numeric: tabular-nums; }
caption {
  padding-bottom: var(--space-3);
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: var(--size-spec);
  text-align: left;
}


/* ============================================================
   FORMS
   .field already covers input. These are the rest of the controls
   and the structure around them.
   ============================================================ */

.form-row { display: flex; flex-direction: column; gap: var(--space-2); }
.form-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.form-row--full { grid-column: 1 / -1; }

label, .label {
  font-size: var(--size-body-sm);
  font-weight: 500;
  color: var(--ink);
}

/* The required mark is the one ornament allowed on a label. */
.label__required { color: var(--negative-ink); }

textarea.field { min-height: 140px; resize: vertical; line-height: var(--leading-body); }

/* The chevron is drawn inline rather than loaded, so it inherits the
   ink and costs no request. */
select.field {
  appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23766D63' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* Checkbox and radio keep the native control — it is accessible,
   it is keyboard-operable, and accent-color repaints it for free.
   Do not rebuild these out of divs. */
/* Two shapes, both valid: a <label class="choice"> wrapping the input, or a
   <div class="choice"> holding an input and a <label for>. Use the second
   whenever the text contains a link — inside a label, a click on the link
   would toggle the box as well as follow the link. */
.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--size-body-sm);
  color: var(--ink-secondary);
  cursor: pointer;
}
.choice label { font-size: inherit; font-weight: 400; color: inherit; cursor: pointer; }
.choice input {
  flex: none;
  width: 16px;
  height: 16px;
  margin: 3px 0 0;
  accent-color: var(--accent);
}

.help  { font-size: var(--size-body-sm); color: var(--ink-muted); }
.error {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--size-body-sm);
  color: var(--negative-ink);
}

/* Focus on a field: the ring plus an ink border. The manual's
   .field:focus removed the outline; the ring above puts it back. */
.field:focus-visible { border-color: var(--accent); }

fieldset { margin: 0; padding: 0; border: 0; }
legend {
  padding: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--ink-muted);
}


/* ============================================================
   NOTICES, TABS, ACCORDION, DIALOG, PAGINATION
   ============================================================ */

/* An alert is a signal, so it carries colour — on a hairline and an
   icon, never as a filled background. Rule 5. */
.alert {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: var(--border-hairline) solid var(--line-strong);
  border-left-width: var(--border-rail);
  font-size: var(--size-body-sm);
  color: var(--ink-secondary);
}
.alert__icon { flex: none; margin-top: 2px; color: var(--ink-muted); }
.alert--positive { border-left-color: var(--positive); }
.alert--positive .alert__icon { color: var(--positive); }
.alert--negative { border-left-color: var(--negative); }
.alert--negative .alert__icon { color: var(--negative); }
.alert--warning  { border-left-color: var(--warning); }
.alert--warning .alert__icon { color: var(--warning); }

.tabs__list {
  display: flex;
  gap: var(--space-6);
  border-bottom: var(--border-hairline) solid var(--line);
}
.tab {
  padding: var(--space-3) 0;
  margin-bottom: -1px;
  background: none;
  border: 0;
  border-bottom: var(--border-rail) solid transparent;
  color: var(--ink-muted);
  font-family: var(--font-body);
  font-size: var(--size-body-sm);
  font-weight: 500;
  cursor: pointer;
}
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--ink); }
.tabs__panel { padding-top: var(--space-6); }
.tabs__panel[hidden] { display: none; }

/* The accordion is <details>. It opens without a line of JavaScript,
   it is keyboard-operable and screen readers announce it correctly.
   Do not replace it with a button and a class. */
.accordion { border-top: var(--border-hairline) solid var(--line); }
.accordion details { border-bottom: var(--border-hairline) solid var(--line); }
.accordion summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) 0;
  font-size: var(--size-h4);
  font-weight: 600;
  letter-spacing: var(--tracking-h4);
  cursor: pointer;
  list-style: none;
}
.accordion summary::-webkit-details-marker { display: none; }
/* A plus that becomes a minus. Two rules, no icon file. */
.accordion summary::after {
  content: "";
  flex: none;
  width: 14px;
  height: 14px;
  background:
    linear-gradient(var(--ink-muted), var(--ink-muted)) center / 14px 1px no-repeat,
    linear-gradient(var(--ink-muted), var(--ink-muted)) center / 1px 14px no-repeat;
  transition: transform var(--reveal-out) var(--ease);
}
.accordion details[open] summary::after {
  background: linear-gradient(var(--ink), var(--ink)) center / 14px 1px no-repeat;
  transform: rotate(180deg);
}
.accordion__body { padding-bottom: var(--space-5); color: var(--ink-secondary); }
.accordion__body > *:last-child { margin-bottom: 0; }

/* Native <dialog>. showModal() gives focus trapping, Escape and the
   backdrop for free — every one of which is a bug waiting to happen
   in a hand-rolled modal. */
dialog {
  width: min(560px, calc(100vw - var(--space-8)));
  padding: 0;
  background: var(--surface);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--ink);
}
dialog::backdrop { background: rgb(0 0 0 / 0.45); }
.dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-hairline) solid var(--line);
}
.dialog__body { padding: var(--space-6); }
.dialog__foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  background: var(--surface-sunk);
  border-top: var(--border-hairline) solid var(--line);
}
.dialog__close {
  padding: var(--space-2);
  background: none;
  border: 0;
  color: var(--ink-muted);
  cursor: pointer;
  line-height: 0;
}
.dialog__close:hover { color: var(--ink); }

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-spec);
  color: var(--ink-muted);
}
.breadcrumb a { color: var(--ink-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--ink); }
.breadcrumb li + li::before { content: "/"; margin-right: var(--space-2); color: var(--line-strong); }
.breadcrumb ol { display: contents; }
.breadcrumb li { display: flex; align-items: center; }

.pagination { display: flex; gap: var(--space-2); align-items: center; }
.pagination a, .pagination span {
  min-width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-2);
  border: var(--border-hairline) solid var(--line);
  color: var(--ink-secondary);
  text-decoration: none;
  font-size: var(--size-body-sm);
  font-variant-numeric: tabular-nums;
}
.pagination a:hover { border-color: var(--line-strong); color: var(--ink); }
.pagination [aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* A list with no bullets, for navigation and stacks of links. */
.list-plain { margin: 0; padding: 0; list-style: none; }


/* ============================================================
   PROSE
   Long-form copy: an article, a legal page, a changelog. Apply to
   the wrapper and stop styling the elements inside it.
   ============================================================ */

.prose { max-width: 68ch; color: var(--ink-secondary); }
.prose > * + * { margin-top: var(--space-5); }
.prose h2 { margin-top: var(--space-8); color: var(--ink); }
.prose h3 { margin-top: var(--space-7); color: var(--ink); }
.prose h2 + *, .prose h3 + * { margin-top: var(--space-4); }
.prose p { margin-bottom: 0; }
.prose ul, .prose ol { padding-left: var(--space-5); }
.prose li + li { margin-top: var(--space-2); }
.prose li::marker { color: var(--ink-muted); }
.prose strong { color: var(--ink); font-weight: 600; }
.prose blockquote {
  margin: 0;
  padding-left: var(--space-5);
  border-left: var(--border-rail) solid var(--line-strong);
  color: var(--ink);
}
.prose img { margin-block: var(--space-6); }
.prose a { color: var(--ink); }


/* ============================================================
   THE MODE SWITCH
   The control itself. The behaviour is in tricsy.js.
   ============================================================ */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  background: none;
  border: var(--border-hairline) solid var(--line);
  color: var(--ink-secondary);
  cursor: pointer;
  transition: border-color var(--reveal-in) var(--ease), color var(--reveal-in) var(--ease);
}
.theme-toggle:hover { border-color: var(--line-strong); color: var(--ink); }
.theme-toggle svg { display: block; }

/* One button, two icons: whichever mode is active hides its own.
   Both selectors are scoped to .theme-toggle so they outrank the
   `.theme-toggle svg { display: block }` above — a class on its own
   loses to a class plus an element, and the icon would never hide. */
.theme-toggle .theme-toggle__sun  { display: none; }
:root[data-theme="dark"] .theme-toggle .theme-toggle__sun  { display: block; }
:root[data-theme="dark"] .theme-toggle .theme-toggle__moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .theme-toggle__sun  { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .theme-toggle__moon { display: none; }
}


/* ============================================================
   UTILITIES
   A short list on purpose. If a page needs a utility that is not
   here, it usually needs a component instead.
   ============================================================ */

.u-center      { text-align: center; }
.u-center .lede, .u-center .section-head { margin-inline: auto; }
.u-muted       { color: var(--ink-muted); }
.u-secondary   { color: var(--ink-secondary); }
.u-mono        { font-family: var(--font-mono); }
.u-tabular     { font-variant-numeric: tabular-nums; }
.u-full        { width: 100%; }
.u-nowrap      { white-space: nowrap; }

/* Visible to a screen reader, invisible on screen. For the label a
   sighted user reads from an icon and a blind user cannot. */
.u-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ============================================================
   BREAKPOINTS
   Three, and they all move in the same direction: the grid folds,
   the margin shrinks, the navigation collapses. The type needs no
   breakpoint at all — it is fluid.
   ============================================================ */

@media (max-width: 1024px) {
  :root { --grid-margin: 48px; }
  .split, .split--even { grid-template-columns: minmax(0, 1fr); }
  .site-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .col-4, .col-5, .col-6, .col-7, .col-8 { grid-column: span 6; }
}

@media (max-width: 768px) {
  /* The core layer already sets this at the same breakpoint, but that
     block sits earlier in the file than the 1024px rule above — and
     between two equally specific rules the later one wins. Repeating
     it here is what makes 24px actually apply on a phone. */
  :root { --grid-margin: 24px; }

  .section { padding-block: var(--space-8); }
  .hero    { padding-block: var(--space-8); }
  .grid > [class*="col-"] { grid-column: span 12; }
  .site-footer__grid { grid-template-columns: minmax(0, 1fr); }

  /* The navigation becomes a panel under the bar. It is hidden with
     the hidden attribute, not with a class, so the markup itself
     says whether it is open. */
  .nav-toggle { display: block; }
  .nav {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: var(--space-3) var(--grid-margin) var(--space-5);
    background: var(--paper);
    border-bottom: var(--border-hairline) solid var(--line);
  }
  .nav[hidden] { display: none; }
  .nav__link {
    padding-block: var(--space-4);
    border-bottom: var(--border-hairline) solid var(--line);
    font-size: var(--size-body);
  }
  .nav__link[aria-current="page"] { box-shadow: none; font-weight: 600; }
  .nav .btn, .nav .theme-toggle { margin-top: var(--space-4); align-self: flex-start; }

  /* In the sheet there is nothing to hang a panel off, so the menu stops
     floating and unfolds where it stands. The summary keeps the same rule
     under it as a plain link, so the row reads as one list. */
  /* inline-flex shrinks to its content, so space-between has nothing to
     push apart — the row has to fill the sheet before the chevron can sit
     at the far edge. */
  .nav-menu__summary { display: flex; justify-content: space-between; }
  .nav-menu__panel {
    position: static;
    width: auto;
    padding: var(--space-3) 0 var(--space-4);
    background: none;
    border: 0;
    border-bottom: var(--border-hairline) solid var(--line);
  }
  .nav-menu__link { padding-inline: var(--space-4); font-size: var(--size-body); }
  .nav-menu__label { padding-inline: var(--space-4); }
}

@media (max-width: 480px) {
  .cards { grid-template-columns: minmax(0, 1fr); }
  .cluster > .btn { width: 100%; justify-content: center; }
}

/* Above the mobile breakpoint the panel is always a row, whatever the
   hidden attribute says — the toggle is gone, so nothing would ever
   put it back. */
@media (min-width: 769px) {
  .nav[hidden] { display: flex; }
}


/* ============================================================
   PRINT
   The blueprint field, the navigation and the mode switch are
   screen furniture. Paper gets the content.
   ============================================================ */

@media print {
  .site-header, .site-footer, .nav, .theme-toggle, .skip-link,
  .blueprint__hit, .nav-toggle { display: none !important; }
  :root { --paper: #fff; --surface: #fff; --ink: #000; --ink-secondary: #333; }
  body { background: #fff; }
  .section { padding-block: var(--space-5); }
  a { text-decoration: underline; }
  /* A printed link is a dead end unless the address comes with it. */
  .prose a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
}

/* ============================================================
   SITE LAYER — CAPABILITY PILLARS
   Five cards. Explicit column counts, not auto-fit, so five items
   collapse evenly instead of stranding a lone card: 5 across on a
   wide screen, then 3 + 2, then 2, then 1 — each step a little airier.
   ============================================================ */
.pillars {
  display: grid;
  gap: var(--grid-gutter);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 481px)  { .pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 768px)  { .pillars { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1400px) { .pillars { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

/* ============================================================
   SITE LAYER — LOGO STRIP
   A hairline matrix of partner marks. One component, three uses:
   the integrations we speak to, the cameras we drive, the buildings
   that run us. Monochrome by rule 4 — colour never enters the plane,
   so an image logo is desaturated to a single ink and only lifts to
   full weight on hover.

   The hairlines are drawn by each cell, not by the container: a 1px
   spread shadow on the item plus a 1px gap, so two neighbours meet in
   a single line and the outer edge closes itself. An earlier version
   painted a --line ground under the grid instead, which was fine only
   while the item count happened to fill the last row — nine marks in
   an eight-column grid left seven empty cells glowing in line colour.
   Drawing per cell means any count works at any width, with no
   nth-child arithmetic and nothing to retune when a brand is added.
   ============================================================ */
.logo-strip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--border-hairline);

  /* The column count is declared, not discovered. A fluid auto-fit picks the
     most columns that fit and leaves whatever does not divide stranded on a
     row of its own — eight marks and then a nineth, alone. Since a strip only
     looks right when the count and the columns agree, each strip states its
     own. These three are for nine marks: three-by-three fills exactly, and the
     widest plane takes five and carries one empty cell, which still reads as a
     strip where a square block would not. */
  --cols:    2;
  --cols-md: 3;
  --cols-lg: 5;
}
@media (min-width: 600px)  { .logo-strip { grid-template-columns: repeat(var(--cols-md), minmax(0, 1fr)); } }
@media (min-width: 1100px) { .logo-strip { grid-template-columns: repeat(var(--cols-lg), minmax(0, 1fr)); } }

/* Three marks: one row from tablet up, stacked on a phone rather than two
   and a straggler. */
.logo-strip--trio { --cols: 1; --cols-md: 3; --cols-lg: 3; }

/* Four marks: two and two, then one row. Four divides at every step, so
   nothing is ever left on a line of its own. */
.logo-strip--quad { --cols: 2; --cols-md: 2; --cols-lg: 4; }
.logo-strip__item {
  /* --cell-bg is the cell's own colour, named because a knocked-out logo has
     to paint with it: KONE's glyphs are cut out of its plates, so they follow
     the cell through hover and through both themes instead of freezing on one
     value. Every state below sets it. */
  --cell-bg: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 104px;
  padding: var(--space-5) var(--space-4);
  background: var(--cell-bg);
  color: var(--ink-muted);
  text-align: center;
  box-shadow: 0 0 0 var(--border-hairline) var(--line);
  transition: color var(--reveal-in) var(--ease),
              background var(--reveal-in) var(--ease);
}
.logo-strip__item:hover {
  color: var(--ink);
  --cell-bg: var(--surface-sunk);
}

/* Wordmark fallback, used until a real logo file lands. It has to
   read as a mark and not as body copy, so it takes the display face
   at optical size with tight tracking. Colour is currentColor, which
   is why it follows the item into full ink on hover and needs no
   dark-mode rule of its own. */
.logo-strip__mark {
  font-family: var(--font-display);
  font-size: var(--size-h4);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  white-space: nowrap;
}

/* Drop-in image logo. Swap the <span class="logo-strip__mark"> for
   <img class="logo-strip__mark logo-strip__mark--img" src="assets/logos/…">
   and the colour is stripped so it sits in the monochrome plane; it
   lifts to full ink on hover. NOTE: a dark logo file needs an invert
   in dark mode — handle that per file when the real assets arrive,
   because some marks already ship white. */
.logo-strip__mark--img {
  max-height: 34px;
  width: auto;
  max-width: 100%;
  filter: invert(var(--logo-ink-invert)) opacity(0.72);
  transition: filter var(--reveal-in) var(--ease);
}
.logo-strip__item:hover .logo-strip__mark--img { filter: invert(var(--logo-ink-invert)) opacity(1); }

/* A logo that ships as SVG goes in the markup rather than through <img>: an
   <img> renders the file as its own document, where currentColor resolves to
   black and never reaches the cell. Inline, the mark inherits the cell's ink
   and the whole dark-mode question disappears. */
.logo-strip__mark--svg {
  height: 26px;
  width: auto;
  max-width: 100%;
  opacity: 0.72;
  transition: opacity var(--reveal-in) var(--ease);
}
.logo-strip__item:hover .logo-strip__mark--svg { opacity: 1; }
.logo-strip__aside { margin: var(--space-5) 0 0; max-width: 58ch; }

/* Opening a section, the strip inherits the section-head's margin. Following
   anything else it has none of its own, and sat flush against the block above. */
* + .logo-strip { margin-top: var(--space-7); }

/* Captioned strip. Four marks that are not doing the same job cannot sit in
   a bare row — the row would claim they are equivalent. The caption is the
   smallest honest way to say what each one actually does. */
/* Grid rather than a column flex: the first row is a fixed 34px, so every
   caption sits on the same line no matter how tall its mark is. Constraining
   the marks to a common height instead would stretch a wide lockup and shrink
   a compact one — they have to stay optically balanced, not height-matched. */
.logo-strip--captioned .logo-strip__item {
  display: grid;
  grid-template-rows: 34px auto;
  align-items: center;
  justify-items: center;
  gap: var(--space-3);
  min-height: 132px;
}
.logo-strip--captioned .logo-strip__mark { max-height: 30px; }
.logo-strip__caption {
  font-family: var(--font-mono);
  font-size: var(--size-mono-label);
  letter-spacing: var(--tracking-mono-label);
  color: var(--ink-muted);
  text-align: center;
}
