/*!
 * Holonova Design System — tokens, primitives, components, effects
 * Holonova BV, Lelystad NL. Company Brain, the systems built on it, and the
 * operating engagement that keeps both running.
 *
 * Direction: INSTRUMENT (adopted 2026-07-27). Dark-first. The canvas is a cold
 * near-black, the orange is a signal rather than a decoration, data is set in
 * mono, and structure is drawn with 1px rules instead of with boxes and
 * shadows. The previous system was extracted from the live WordPress theme and
 * therefore described holonova.nl instead of governing it. This one is derived
 * from the positioning and is the source of truth for every visual surface:
 * marketing site, client decks, legal PDFs, and client project UIs.
 *
 * Load order:  holonova-fonts.css  ->  this file  ->  your page styles.
 * No CDN. No network dependency. See DESIGN_SYSTEM.md for rules and rationale.
 *
 * Conformance target: WCAG 2.2 AA. Every text/background pair defined below is
 * computed, not estimated; the ratio is in the comment. Known exceptions are
 * listed in DESIGN_SYSTEM.md under "Known issues", not hidden here.
 */

@import "holonova-fonts.css";

/* ============================================================
   1. PRIMITIVES
   Raw values. Never reference these in product code; use the
   semantic tokens in section 2.
   ============================================================ */
:root {
  color-scheme: dark light;

  /* ---- Cold neutral ramp ----
     Deliberately cold, not warm. A warm near-white ground is the saturated
     default for agency sites and it is what the previous system inherited.
     Warmth in this brand is carried by the orange signal, not by the paper. */
  --ink-950:  #0B0C0E;  /* canvas */
  --ink-900:  #14161A;  /* raised surface */
  --ink-800:  #1B1E23;  /* surface, cards, wells */
  --ink-700:  #262A31;  /* hover surface */
  --ink-600:  #363B44;  /* strong border */
  --ink-400:  #6B7079;  /* faint text, decorative only */
  --ink-300:  #8A8F98;  /* muted text */
  --ink-200:  #B9BEC6;  /* secondary text */
  --ink-100:  #E8E9EB;  /* primary text on dark */
  --paper-50: #F4F5F6;  /* document alt band */
  --paper-0:  #FFFFFF;  /* document canvas */
  --slate-900:#16181C;  /* primary text on light */
  --slate-600:#4E535B;  /* muted text on light */

  /* ---- Orange, in three roles ----
     One identity, three values, each with a job the others cannot do. The
     July "one orange" decision was right that Holonova has one orange
     IDENTITY; it does not follow that one value can serve as logo artwork,
     as body-adjacent text, and as a fill behind white text at AA. */
  --orange-identity: #BC641B; /* the logo artwork value. Logo only. */
  --orange-signal:   #D2761F; /* text/annotation on dark: 5.92 canvas, 5.05 surface */
  --orange-action:   #9E521A; /* fill behind white text on light: 5.71 */
  --orange-dim:      #7C4014; /* pressed/edge on light */
  --orange-wash:     #2A1A0E; /* tinted well on dark */
  --orange-tint:     #FBF3EA; /* tinted well on light (was undocumented in print.css) */

  /* Channel list so alpha variants never re-hardcode the hex.
     The 2026-07-27 audit found the brand RGB written out three times inside
     the effects layer, days after the colour last moved. */
  --orange-signal-rgb: 210 118 31;
  --ink-100-rgb: 232 233 235;
  --ink-950-rgb: 11 12 14;

  /* ---- Status ----
     These exist so nobody has to invent one. The previous system had no error
     colour and simultaneously forbade adding hues, which is unbuildable. */
  --status-success: #4ADE80; /* 11.23 on canvas */
  --status-warning: #FBBF24; /* 11.72 on canvas */
  --status-danger:  #FF6B6B; /*  7.05 on canvas */
  --status-success-ink: #17692F; /* 6.78 on paper */
  --status-warning-ink: #7A4E02; /* 7.20 on paper */
  --status-danger-ink:  #B3261E; /* 6.54 on paper */

  /* ---- Type ----
     Two families on a real contrast axis (grotesk vs mono), not three
     geometric sans-serifs the brand's own logo README measured 2% apart. */
  --font-sans: Archivo, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- Spacing (4px base) ---- */
  --space-3xs: 2px;  --space-2xs: 4px;  --space-xs: 8px;
  --space-sm: 12px;  --space-md: 16px;  --space-lg: 24px;
  --space-xl: 32px;  --space-2xl: 48px; --space-3xl: 72px; --space-4xl: 112px;

  /* ---- Radius ----
     Instrument is a tight-radius world. Radius is semantic and does not scale
     with viewport or with slide size. */
  --radius-none: 0;
  --radius-sm: 2px;   /* inputs, buttons, tiles, chips */
  --radius-md: 3px;   /* cards, wells, panels */
  --radius-lg: 6px;   /* dialogs, popovers */
  --radius-full: 999px;

  /* ---- Borders ---- */
  --border-hairline: 1px solid var(--color-line);
  --border-strong:   1px solid var(--color-line-strong);

  /* ---- Elevation ----
     On a dark canvas, depth reads as a lighter surface plus a top edge
     highlight, not as a drop shadow. Shadows exist for true overlays only. */
  --elevation-0: none;
  --elevation-edge: inset 0 1px 0 rgb(var(--ink-100-rgb) / .07);
  --elevation-1: 0 1px 2px rgb(var(--ink-950-rgb) / .40);
  --elevation-2: 0 4px 12px rgb(var(--ink-950-rgb) / .48);
  --elevation-3: 0 12px 32px rgb(var(--ink-950-rgb) / .56);
  --elevation-signal: 0 0 0 1px rgb(var(--orange-signal-rgb) / .40),
                      0 4px 18px rgb(var(--orange-signal-rgb) / .20);

  /* ---- Gradient ---- */
  --gradient-signal: linear-gradient(135deg, var(--orange-signal) 0%, var(--orange-identity) 100%);
  --gradient-depth:  linear-gradient(180deg, var(--ink-900) 0%, var(--ink-950) 100%);
  --gradient-hairline: linear-gradient(90deg, var(--orange-signal) 0%,
                       rgb(var(--orange-signal-rgb) / 0) 62%);

  /* ---- Texture ----
     The grid cell equals the section rhythm, so the drawn grid is the real
     grid. Grain dithers the depth gradient; it is not paper texture. */
  --texture-grid-size: 48px;
  --texture-grid:
    repeating-linear-gradient(0deg,  rgb(var(--ink-100-rgb) / .045) 0 1px, transparent 1px var(--texture-grid-size)),
    repeating-linear-gradient(90deg, rgb(var(--ink-100-rgb) / .045) 0 1px, transparent 1px var(--texture-grid-size));
  --texture-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");

  /* ---- Motion ---- */
  --duration-instant: 80ms;
  --duration-fast:    140ms;
  --duration-base:    220ms;
  --duration-slow:    400ms;
  --duration-reveal:  600ms;
  --ease-standard:  cubic-bezier(.2, 0, 0, 1);
  --ease-out:       cubic-bezier(.16, 1, .3, 1);
  --ease-in-out:    cubic-bezier(.65, 0, .35, 1);
  --ease-overshoot: cubic-bezier(.34, 1.26, .64, 1); /* capped at 8px travel */
  --stagger-step: 60ms;
  --lift-sm: 1px;
  --lift-md: 2px;

  /* ---- Z-index scale ----
     Semantic, ordered, and complete. Never write a bare integer. The previous
     system documented --elevation-3 for "overlays, popovers, a sticky nav" and
     defined no z-index at all, so the first popover would have collided with
     the texture layer. */
  --z-below:   -1;
  --z-base:     0;
  --z-texture:  1;
  --z-content:  2;
  --z-sticky:   100;
  --z-dropdown: 200;
  --z-overlay:  300;
  --z-dialog:   400;
  --z-toast:    500;
  --z-tooltip:  600;

  /* ---- Layout ---- */
  --measure: 68ch;            /* prose line length cap */
  --content-max: 1200px;
  --content-narrow: 760px;
  --gutter: clamp(20px, 4vw, 48px);
}

/* ============================================================
   2. SEMANTIC TOKENS
   Reference these in product code, never the primitives.
   Default theme is dark (Instrument). The light set is for
   documents, contracts, print, and any client UI that needs it.
   ============================================================ */
:root,
[data-theme="dark"] {
  --color-canvas:        var(--ink-950);
  --color-surface:       var(--ink-800);
  --color-surface-raised:var(--ink-900);
  --color-surface-hover: var(--ink-700);
  --color-well:          var(--orange-wash);

  --color-text:          var(--ink-100);   /* 16.11 on canvas */
  --color-text-secondary:var(--ink-200);   /* 10.48 on canvas */
  --color-text-muted:    var(--ink-300);   /*  6.02 on canvas, 5.14 on surface */
  --color-text-faint:    var(--ink-400);   /*  3.93 — non-essential text only */
  --color-text-inverse:  var(--slate-900);
  --color-text-signal:   var(--orange-signal);

  --color-line:          rgb(var(--ink-100-rgb) / .13);
  --color-line-strong:   var(--ink-600);

  --color-action:        var(--orange-signal);
  --color-action-hover:  #E0862F;
  --color-action-text:   var(--ink-950);   /* dark text on orange fill: 5.92 */

  --color-link:          var(--orange-signal);
  --color-focus:         var(--orange-signal);

  --color-success: var(--status-success);
  --color-warning: var(--status-warning);
  --color-danger:  var(--status-danger);
}

[data-theme="light"] {
  --color-canvas:        var(--paper-0);
  --color-surface:       var(--paper-0);
  --color-surface-raised:var(--paper-50);
  --color-surface-hover: var(--paper-50);
  --color-well:          var(--orange-tint);

  --color-text:          var(--slate-900);  /* 17.77 on paper */
  --color-text-secondary:var(--slate-600);  /*  7.74 */
  --color-text-muted:    var(--slate-600);
  --color-text-faint:    #6E747D;           /*  4.71 */
  --color-text-inverse:  var(--paper-0);
  --color-text-signal:   var(--orange-action); /* 5.71 on paper */

  --color-line:          rgb(22 24 28 / .14);
  --color-line-strong:   #C9C7C1;

  --color-action:        var(--orange-action);
  --color-action-hover:  var(--orange-dim);
  --color-action-text:   var(--paper-0);    /* white on action: 5.71 */

  --color-link:          var(--orange-action);
  --color-focus:         var(--orange-action);

  --color-success: var(--status-success-ink);
  --color-warning: var(--status-warning-ink);
  --color-danger:  var(--status-danger-ink);
}

/* ============================================================
   3. RESET & BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-padding-top: var(--space-4xl); }
body {
  margin: 0;
  background: var(--color-canvas);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg, video, canvas { max-width: 100%; height: auto; display: block; }
:is(h1,h2,h3,h4,h5,h6) { margin: 0; text-wrap: balance; }
p, ul, ol, dl, figure, blockquote, pre, table { margin: 0; }
p { text-wrap: pretty; max-width: var(--measure); }

/* Unclassed prose. The reset above zeroes every block margin, which is right for
   composed layouts and wrong for anything that arrives as a blob of HTML: a CMS
   field, a rendered markdown file, a blog post. .prose restores rhythm without
   requiring the author to wrap every element in a primitive. Apply it to the
   container, not to each child. */
.prose { max-width: var(--measure); }
.prose > * + * { margin-block-start: var(--space-md); }
.prose > * + :is(h2, h3, h4) { margin-block-start: var(--space-2xl); }
.prose :is(h2, h3, h4) + * { margin-block-start: var(--space-xs); }
.prose :is(ul, ol) { padding-inline-start: var(--space-lg); }
.prose li + li { margin-block-start: var(--space-2xs); }
.prose a { text-decoration: underline; }
/* User or CMS content can contain an unbroken 60-character token. Without this a
   single URL or hash widens its container and puts a horizontal scrollbar on the
   page. */
.prose, .table td, .readout__row, .empty { overflow-wrap: anywhere; }
/* Any wide block inside prose scrolls itself rather than the page. */
.prose :is(table, pre) { display: block; max-width: 100%; overflow-x: auto; }

/* Links in prose are underlined. Colour alone is a WCAG 1.4.1 (Level A)
   failure, and the previous system removed underlines globally while the
   in-card link contrast against body text measured 1.79. */
a { color: var(--color-link); text-underline-offset: .18em; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }
/* Scoped so it cannot fade a button or a card. The previous global
   a:hover{opacity:.85} faded the primary CTA and its shadow at the moment of
   intent, on the one element that converts. */
a:not(.btn):not(.card):not(.nav__link):hover { color: var(--color-action-hover); }
:is(.btn, .card, .nav__link) { text-decoration: none; }

/* ============================================================
   4. FOCUS, SKIP LINK, SCREEN-READER TEXT
   The focus ring carries a WCAG 2.4.7 duty, so it is written at real
   specificity. The previous system wrapped it in :where(), which
   contributes zero specificity, while the docs claimed it "outranks
   every decorative layer". It did the opposite: anything beat it.
   ============================================================ */
:is(a, button, input, select, textarea, summary, [tabindex], .btn):focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}
.skip-link {
  position: absolute; left: var(--space-md); top: calc(-1 * var(--space-4xl));
  z-index: var(--z-tooltip);
  background: var(--color-action); color: var(--color-action-text);
  padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm);
  font: 600 14px/1 var(--font-sans); text-decoration: none;
  transition: top var(--duration-fast) var(--ease-standard);
}
.skip-link:focus { top: var(--space-md); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ============================================================
   5. TYPE RAMP
   Fluid. Every step clears a 1.25 ratio against the one below at the
   same viewport, and the display step is allowed to be genuinely large
   because this is a brand surface, not a dashboard.
   ============================================================ */
.t-display { font: 700 clamp(2.75rem, 1.6rem + 5.2vw, 5.5rem)/1.02 var(--font-sans); letter-spacing: -.035em; }
.t-h1      { font: 700 clamp(2.125rem, 1.5rem + 2.8vw, 3.5rem)/1.08 var(--font-sans); letter-spacing: -.03em; }
.t-h2      { font: 600 clamp(1.625rem, 1.3rem + 1.5vw, 2.5rem)/1.15 var(--font-sans); letter-spacing: -.022em; }
.t-h3      { font: 600 clamp(1.25rem, 1.1rem + .7vw, 1.75rem)/1.25 var(--font-sans); letter-spacing: -.015em; }
/* h4 is fluid so it stays above .t-lead at wide viewports. Fixed at 18px it
   was smaller than a 20px lead paragraph above 1200px, which inverts the
   hierarchy exactly where the page has the most room to show it. */
.t-h4      { font: 600 clamp(1.125rem, 1.02rem + .42vw, 1.375rem)/1.35 var(--font-sans); letter-spacing: -.01em; }
.t-lead    { font: 400 clamp(1.0625rem, 1rem + .35vw, 1.25rem)/1.6 var(--font-sans); color: var(--color-text-secondary); }
.t-body    { font: 400 1rem/1.6 var(--font-sans); }
.t-small   { font: 400 .875rem/1.55 var(--font-sans); }
.t-data    { font: 400 .8125rem/1.5 var(--font-mono); letter-spacing: .01em; font-variant-numeric: tabular-nums; }
/* The kicker. ONE per page, on the section that needs framing the reader does
   not already have. It is not a per-section scaffold: an eyebrow above every
   heading is the single most saturated AI tell in 2026, and the previous
   system mandated exactly that in its instructions to coding agents.
   Also note the colour: --color-text-signal, which is AA at 13px. */
.t-kicker  { font: 500 .6875rem/1 var(--font-mono); letter-spacing: .16em;
             text-transform: uppercase; color: var(--color-text-signal); }
.t-mono    { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ============================================================
   6. LAYOUT PRIMITIVES
   The previous system shipped .container and .section and nothing else,
   so its own reference implementation had to invent seven local
   primitives before it could render one row.
   ============================================================ */
.container { width: 100%; max-width: var(--content-max); margin-inline: auto; padding-inline: var(--gutter); }
.container--narrow { max-width: var(--content-narrow); }
.section { padding-block: clamp(var(--space-3xl), 8vw, var(--space-4xl)); }
.section--tight { padding-block: var(--space-2xl); }
.section--raised { background: var(--color-surface-raised); }
.section--depth { background-color: var(--color-canvas); background-image: var(--gradient-depth); }
.section--grid  { background-image: var(--texture-grid), var(--gradient-depth); }

/* Vertical rhythm. Replaces 64 hand-written inline margins in the old
   reference implementation. */
.stack > * + * { margin-block-start: var(--flow, var(--space-md)); }
.stack--xs { --flow: var(--space-xs); }
.stack--lg { --flow: var(--space-lg); }
.stack--xl { --flow: var(--space-xl); }

/* Horizontal grouping that wraps. */
.cluster { display: flex; flex-wrap: wrap; gap: var(--gap, var(--space-md)); align-items: center; }
.cluster--between { justify-content: space-between; }

/* Responsive grid with no breakpoint required. --grid-min is the knob. */
.grid { display: grid; gap: var(--gap, var(--space-lg));
        grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 260px), 100%), 1fr)); }
.grid--2 { --grid-min: 380px; }
.grid--4 { --grid-min: 190px; }

/* Two-column with a fixed-ish sidebar that collapses without a media query. */
.split { display: flex; flex-wrap: wrap; gap: var(--space-2xl); }
.split > :first-child { flex: 1 1 32rem; }
.split > :last-child  { flex: 1 1 18rem; }

.divider { border: 0; border-top: var(--border-hairline); margin-block: var(--space-xl); }
.hairline { position: relative; }
.hairline::before { content: ""; position: absolute; inset-inline: 0; top: 0; height: 1px;
                    background: var(--gradient-hairline); pointer-events: none; }

/* ============================================================
   7. BUTTONS
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-xs);
  min-height: 44px;               /* WCAG 2.2 SC 2.5.8 target size */
  padding: 12px 22px; border-radius: var(--radius-sm);
  font: 600 .9375rem/1.2 var(--font-sans); cursor: pointer;
  border: 1px solid transparent; text-decoration: none; white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-standard);
}
.btn--primary { background: var(--color-action); color: var(--color-action-text); border-color: var(--color-action); }
.btn--primary:hover { background: var(--color-action-hover); border-color: var(--color-action-hover);
                      box-shadow: var(--elevation-signal); transform: translateY(calc(-1 * var(--lift-sm))); }
.btn--secondary { background: transparent; color: var(--color-text); border-color: var(--color-line-strong); }
.btn--secondary:hover { background: var(--color-surface-hover); border-color: var(--color-text-muted);
                        transform: translateY(calc(-1 * var(--lift-sm))); }
.btn--ghost { background: transparent; color: var(--color-text-secondary); border-color: transparent; padding-inline: var(--space-xs); }
.btn--ghost:hover { color: var(--color-text); background: var(--color-surface-hover); }
.btn--danger { background: transparent; color: var(--color-danger); border-color: currentColor; }
.btn--danger:hover { background: rgb(255 107 107 / .12); }
.btn--compact { min-height: 36px; padding: 8px 14px; font-size: .875rem; }
.btn:active { transform: translateY(0); box-shadow: none; transition-duration: var(--duration-instant); }
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; box-shadow: none; transform: none;
}
/* Keyboard users must not reach a disabled anchor. The old .btn--disabled used
   pointer-events:none, which blocks the mouse and nothing else. */
a.btn[aria-disabled="true"] { pointer-events: none; }

/* ============================================================
   8. FORMS
   The previous system defined .input and nothing else, on a site whose
   only conversion surface is a contact form.
   ============================================================ */
.field { display: flex; flex-direction: column; gap: var(--space-2xs); }
.label { font: 500 .875rem/1.4 var(--font-sans); color: var(--color-text-secondary); }
.label .req { color: var(--color-danger); margin-inline-start: 2px; }
.help  { font: 400 .8125rem/1.5 var(--font-sans); color: var(--color-text-muted); }
.error { font: 500 .8125rem/1.5 var(--font-sans); color: var(--color-danger);
         display: flex; align-items: flex-start; gap: var(--space-2xs); }
.input, .select, .textarea {
  width: 100%; min-height: 44px; padding: 11px 14px;
  background: var(--color-surface); color: var(--color-text);
  border: 1px solid var(--color-line-strong); border-radius: var(--radius-sm);
  font: 400 1rem/1.5 var(--font-sans);
  transition: border-color var(--duration-fast) var(--ease-standard);
}
.textarea { min-height: 120px; resize: vertical; }
.input:hover, .select:hover, .textarea:hover { border-color: var(--color-text-muted); }
.input::placeholder, .textarea::placeholder { color: var(--color-text-muted); }
[aria-invalid="true"] { border-color: var(--color-danger); }
.checkbox, .radio { display: flex; align-items: flex-start; gap: var(--space-xs); cursor: pointer; }
.checkbox input, .radio input { width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--color-action); flex: none; }
fieldset { border: var(--border-hairline); border-radius: var(--radius-md); padding: var(--space-lg); margin: 0; }
legend { font: 600 .875rem/1 var(--font-sans); padding-inline: var(--space-xs); }

/* ============================================================
   9. SURFACES: CARD, WELL, PANEL
   Flat by default. Depth is opt-in, because a page where everything
   floats has no hierarchy left to spend.
   ============================================================ */
.card { background: var(--color-surface); border: var(--border-hairline);
        border-radius: var(--radius-md); padding: var(--space-xl); }
.card--raised { background: var(--color-surface-raised); box-shadow: var(--elevation-1), var(--elevation-edge); }
.card--interactive {
  --spotlight-alpha: 0;
  position: relative; display: block; color: inherit;
  background-color: var(--color-surface);
  background-image: radial-gradient(320px circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
                    rgb(var(--orange-signal-rgb) / calc(var(--spotlight-alpha) * .12)), transparent 72%);
  transition: box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-base) var(--ease-standard),
              border-color var(--duration-base) var(--ease-standard),
              --spotlight-alpha var(--duration-base) var(--ease-standard);
}
.card--interactive:hover, .card--interactive:focus-within {
  --spotlight-alpha: 1;
  transform: translateY(calc(-1 * var(--lift-md)));
  border-color: rgb(var(--orange-signal-rgb) / .45);
  box-shadow: var(--elevation-2);
}
.well { background: var(--color-well); border-radius: var(--radius-md);
        padding: var(--space-lg); border-inline-start: 0; }
.tile { width: 40px; height: 40px; border-radius: var(--radius-sm);
        background: var(--color-well); color: var(--color-text-signal);
        display: grid; place-items: center; font: 500 .75rem/1 var(--font-mono); }

/* ---- Iconography ----
   Line icons from holonova-icons.svg, 24px grid, 1.5 stroke, currentColor,
   never filled and never coloured independently. Version 1 defined no icon
   set and rendered icons as text glyphs inside tiles; two of those glyphs
   (U+2601 cloud, U+2699 gear) sit in the emoji block, so they rendered as
   full-colour emoji on Windows and Android in a system that bans emoji, and
   as tofu wherever the font lacked them.

   The stroke does NOT scale with the icon: a 1.5px stroke at 32px reads
   thinner than at 16px, which is correct. Optical size is the reason the
   scale below is short. */
.icon { width: var(--icon-size, 24px); height: var(--icon-size, 24px);
        flex: none; display: inline-block; vertical-align: middle;
        color: inherit; stroke-width: 1.5; }
.icon--sm { --icon-size: 16px; stroke-width: 1.6; } /* inline with body text */
.icon--md { --icon-size: 24px; }                    /* default, buttons, list markers */
.icon--lg { --icon-size: 32px; }                    /* section and card leads */
.icon--signal { color: var(--color-text-signal); }
/* An icon inside a tile is the one place the two combine. */
.tile .icon { --icon-size: 20px; }

/* ============================================================
   10. READOUT — the Instrument signature component
   A label/value pair list set in mono. This is how the system shows
   facts: not as a hero-metric grid, but as an instrument panel.
   ============================================================ */
.readout { border: var(--border-hairline); border-radius: var(--radius-md); overflow: hidden; }
.readout__row { display: flex; justify-content: space-between; gap: var(--space-md);
                padding: 12px 16px; border-bottom: var(--border-hairline);
                font: 400 .75rem/1.4 var(--font-mono); color: var(--color-text-muted); }
.readout__row:last-child { border-bottom: 0; }
.readout__row b { color: var(--color-text); font-weight: 500; }
.readout__row .is-signal { color: var(--color-text-signal); }

/* ============================================================
   11. BADGE, CHIP, AVATAR
   ============================================================ */
.badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px;
         border-radius: var(--radius-full); border: 1px solid currentColor;
         font: 500 .75rem/1.4 var(--font-mono); letter-spacing: .04em;
         color: var(--color-text-signal); background: transparent; }
.badge--success { color: var(--color-success); }
.badge--warning { color: var(--color-warning); }
.badge--danger  { color: var(--color-danger); }
.badge--neutral { color: var(--color-text-muted); }
.avatar { width: 40px; height: 40px; border-radius: var(--radius-full);
          background: var(--color-surface-raised); display: grid; place-items: center;
          font: 600 .875rem/1 var(--font-sans); color: var(--color-text-secondary); overflow: hidden; }

/* ============================================================
   12. NAVIGATION, HEADER, FOOTER
   Chrome that appears on 100% of pages and had no definition at all.
   ============================================================ */
.header { position: sticky; top: 0; z-index: var(--z-sticky);
          background: rgb(var(--ink-950-rgb) / .88);
          -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
          border-bottom: var(--border-hairline); }
.header__inner { display: flex; align-items: center; justify-content: space-between;
                 gap: var(--space-lg); min-height: 64px; }
.nav { display: flex; gap: var(--space-lg); align-items: center; }
.nav__link { font: 500 .875rem/1 var(--font-sans); color: var(--color-text-muted);
             padding: 10px 2px; position: relative;
             transition: color var(--duration-fast) var(--ease-standard); }
.nav__link:hover { color: var(--color-text); }
/* Current page is marked with aria-current, not with a class, so the
   accessible name and the visual state cannot drift apart. */
.nav__link[aria-current="page"] { color: var(--color-text); }
.nav__link[aria-current="page"]::after { content: ""; position: absolute;
  inset-inline: 0; bottom: 0; height: 2px; background: var(--color-action); }
.footer { border-top: var(--border-hairline); padding-block: var(--space-3xl) var(--space-xl);
          color: var(--color-text-muted); }

/* ============================================================
   13. CONTENT: TABLE, LIST, QUOTE, FIGURE, CODE
   ============================================================ */
.table { width: 100%; border-collapse: collapse; font-size: .9375rem; }
.table caption { text-align: left; padding-bottom: var(--space-sm); color: var(--color-text-muted);
                 font: 400 .8125rem/1.5 var(--font-sans); }
.table th { text-align: left; font: 500 .75rem/1.4 var(--font-mono); letter-spacing: .06em;
            text-transform: uppercase; color: var(--color-text-muted);
            padding: 10px 14px; border-bottom: var(--border-strong); }
.table td { padding: 12px 14px; border-bottom: var(--border-hairline); vertical-align: top; }
.table tbody tr:hover { background: var(--color-surface-raised); }
.table--numeric td:not(:first-child), .table--numeric th:not(:first-child) {
  text-align: right; font-variant-numeric: tabular-nums; }
.list { padding-inline-start: var(--space-lg); }
.list li + li { margin-block-start: var(--space-xs); }
.list--bare { list-style: none; padding: 0; }
.list--steps { list-style: none; padding: 0; counter-reset: step; }
.list--steps > li { counter-increment: step; display: grid; grid-template-columns: 2rem 1fr;
                    gap: var(--space-md); padding-block: var(--space-sm); }
.list--steps > li::before { content: counter(step, decimal-leading-zero);
  font: 500 .75rem/1.6 var(--font-mono); color: var(--color-text-signal); }
.quote { border-inline-start: 1px solid var(--color-action); padding-inline-start: var(--space-lg);
         font: 400 1.125rem/1.55 var(--font-sans); color: var(--color-text-secondary); }
.figure figcaption { font: 400 .8125rem/1.5 var(--font-sans); color: var(--color-text-muted);
                     margin-block-start: var(--space-xs); }
code, kbd, samp { font-family: var(--font-mono); font-size: .875em; color: var(--color-text-signal); }
pre { background: var(--color-surface); border: var(--border-hairline); border-radius: var(--radius-md);
      padding: var(--space-md); overflow-x: auto; }
pre code { color: var(--color-text); }

/* ============================================================
   14. FEEDBACK: ALERT, TOAST, DIALOG, TOOLTIP, PROGRESS
   ============================================================ */
.alert { display: flex; gap: var(--space-sm); padding: var(--space-md);
         border: 1px solid currentColor; border-radius: var(--radius-md); }
.alert__body { color: var(--color-text); }
.alert--info    { color: var(--color-text-signal); }
.alert--success { color: var(--color-success); }
.alert--warning { color: var(--color-warning); }
.alert--danger  { color: var(--color-danger); }
.toast { position: fixed; inset-block-end: var(--space-lg); inset-inline-end: var(--space-lg);
         z-index: var(--z-toast); background: var(--color-surface-raised);
         border: var(--border-hairline); border-radius: var(--radius-md);
         box-shadow: var(--elevation-3); padding: var(--space-md) var(--space-lg); }
dialog { background: var(--color-surface-raised); color: var(--color-text);
         border: var(--border-hairline); border-radius: var(--radius-lg);
         padding: var(--space-xl); max-width: min(560px, calc(100vw - 2 * var(--gutter)));
         box-shadow: var(--elevation-3); }
dialog::backdrop { background: rgb(var(--ink-950-rgb) / .72); }
.progress { height: 2px; background: var(--color-line); overflow: hidden; border-radius: var(--radius-full); }
.progress__bar { height: 100%; background: var(--color-action); transition: width var(--duration-slow) var(--ease-standard); }

/* Disclosure. Native <details>, so it works before any script loads. */
.disclosure { border-bottom: var(--border-hairline); }
.disclosure > summary { cursor: pointer; padding-block: var(--space-md);
  font: 600 1rem/1.4 var(--font-sans); list-style: none; display: flex;
  justify-content: space-between; align-items: center; gap: var(--space-md); }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::after { content: "+"; font-family: var(--font-mono); color: var(--color-text-signal); }
.disclosure[open] > summary::after { content: "\2212"; }
.disclosure__body { padding-block: 0 var(--space-md); color: var(--color-text-secondary); }

/* ============================================================
   15. STATES: LOADING, EMPTY, SELECTED
   ============================================================ */
.skeleton { background: linear-gradient(90deg, var(--color-surface) 0%, var(--color-surface-hover) 50%, var(--color-surface) 100%);
            background-size: 200% 100%; border-radius: var(--radius-sm);
            animation: skeleton-sweep 1.4s var(--ease-in-out) infinite; min-height: 1em; }
@keyframes skeleton-sweep { to { background-position: -200% 0; } }
.spinner { width: 16px; height: 16px; border: 2px solid var(--color-line-strong);
           border-top-color: var(--color-action); border-radius: 50%;
           animation: spin 700ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.empty { text-align: left; padding: var(--space-2xl) 0; color: var(--color-text-muted);
         border: 1px dashed var(--color-line-strong); border-radius: var(--radius-md);
         padding-inline: var(--space-xl); }
.is-selected { background: var(--color-well); }

/* ============================================================
   16. EFFECTS LAYER
   Governed, budgeted, and each one degrades to flat. See
   DESIGN_SYSTEM.md "The effects budget" for the rules that bind these.
   ============================================================ */
@property --spotlight-alpha { syntax: "<number>"; inherits: false; initial-value: 0; }

.grain { position: relative; isolation: isolate; }
.grain::after { content: ""; position: absolute; inset: 0; z-index: var(--z-texture);
  background-image: var(--texture-grain); background-size: 120px 120px;
  opacity: .06; mix-blend-mode: overlay; pointer-events: none; }
.grain > * { position: relative; z-index: var(--z-content); }

/* Orbit motif: the logo's three unequal circles, drifting.
   The rings are DASHED and unequal, because a perfect featureless circle
   rotating about its own centre is a visual no-op. The previous version
   animated three plain circles and spent the system's entire looping-motion
   budget on an animation that could not be seen. */
.orbit { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: var(--z-base); }
.orbit__ring { position: absolute; border-radius: 50%; transform-origin: 50% 50%;
  border: 1px dashed rgb(var(--ink-100-rgb) / .16);
  animation: orbit-drift 90s linear infinite; }
.orbit__ring:nth-child(2) { animation-duration: 130s; animation-direction: reverse;
  border-color: rgb(var(--orange-signal-rgb) / .18); }
.orbit__ring:nth-child(3) { animation-duration: 170s; }
@keyframes orbit-drift { to { rotate: 360deg; } }

/* Scroll reveal. The hidden state is opt-in per element via .is-armed, which
   only the script adds, and only to elements it is actually observing. The
   previous version applied opacity:0 to every .reveal the moment a global .js
   class landed, then scanned the DOM exactly once, so any element inserted
   later stayed invisible forever. In a client project UI that is a blank page. */
.reveal.is-armed {
  opacity: 0; transform: translateY(10px);
  transition: opacity var(--duration-reveal) var(--ease-out),
              transform var(--duration-reveal) var(--ease-out);
  transition-delay: calc(var(--reveal-index, 0) * var(--stagger-step));
}
.reveal.is-armed.is-visible { opacity: 1; transform: none; }

/* Counters carry their true value in the HTML. The script counts UP TO the
   text already there; it never supplies the number. A counter whose no-script
   fallback is "0" publishes a false figure, which is the one failure this
   system cannot afford on a page about sourcing numbers. */
.counter { font-variant-numeric: tabular-nums; }

/* ---- Medium-swapped logo ----
   CSS cannot change an <img> src, and the white lockup is invisible on a printed
   white page while the orange one is invisible on the dark canvas. Ship both and
   let the medium choose:
     <img class="logo logo--light" src="logo/holonova-logo-white.png"  alt="Holonova">
     <img class="logo logo--dark"  src="logo/holonova-logo-orange.png" alt="">
   The second carries alt="" so screen readers announce the brand once. */
.logo { display: block; }
.logo--dark { display: none; }
@media print {
  .logo--light { display: none !important; }
  .logo--dark  { display: block !important; }
}

/* ============================================================
   17. DEGRADATION
   Four paths, all implemented, none merely asserted.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important; animation-iteration-count: 1 !important;
    transition-duration: 1ms !important; scroll-behavior: auto !important;
  }
  .reveal.is-armed { opacity: 1; transform: none; }
  .btn:hover, .card--interactive:hover { transform: none; }
  .orbit, .skeleton { display: none; }
  /* A spinner still has to communicate. Reduced motion removes travel, not
     meaning, so it becomes a static ring with a visible gap. */
  .spinner { animation: none; border-top-color: var(--color-action); }
}

@media (forced-colors: active) {
  /* Windows High Contrast strips backgrounds and shadows. Anything that was
     carrying meaning through colour or depth needs a border to survive. */
  .card, .card--raised, .card--interactive, .readout, .well, .alert, .toast,
  .badge, dialog, .tile, .empty { border: 1px solid CanvasText; }
  .btn { border: 1px solid ButtonText; }
  .btn--primary { background: ButtonFace; color: ButtonText; }
  .grain::after, .orbit, .hairline::before { display: none; }
  .card--interactive { background-image: none; }
  .nav__link[aria-current="page"]::after { background: Highlight; }
  :is(a, button, .btn, [tabindex]):focus-visible { outline: 2px solid Highlight; }
  .progress__bar { background: Highlight; }
}

@media print {
  /* Documents print on paper, in the light token set, with no effects. */
  :root { color-scheme: light; }
  *, *::before, *::after {
    box-shadow: none !important; text-shadow: none !important;
    animation: none !important; transition: none !important; transform: none !important;
    background-image: none !important;
  }
  /* Flip the TOKENS, not every element.
     This block first forced `color` on `[data-theme="dark"] *`, which is a
     sledgehammer: it beats inline styles and any deliberate per-element colour,
     so a deck slide lost its intentional muted text. Redefining the semantic
     tokens for print makes every consumer of a token flip automatically, needs
     no !important on content, and leaves deliberate overrides intact. */
  :root, [data-theme="dark"] {
    --color-canvas: #fff;
    --color-surface: #fff;
    --color-surface-raised: var(--paper-50);
    --color-surface-hover: var(--paper-50);
    --color-well: var(--orange-tint);
    --color-text: var(--slate-900);
    --color-text-secondary: var(--slate-600);
    --color-text-muted: var(--slate-600);
    --color-text-faint: #6E747D;
    --color-text-signal: var(--orange-action);
    --color-line: rgb(22 24 28 / .18);
    --color-line-strong: #C9C7C1;
    --color-action: var(--orange-action);
    --color-action-text: #fff;
    --color-link: var(--orange-action);
  }
  body { background: #fff; color: var(--color-text); }
  /* Surfaces that painted a dark background-color must lose it too: overriding
     only background-image left dark ground under dark text wherever the reader
     had background graphics switched ON. */
  .section--depth, .section--grid, .section--raised, .header, .footer,
  .card, .card--raised, .card--interactive, .readout, .well, dialog, .toast, .empty {
    background-color: #fff !important;
  }
  .grain::after, .hairline::before, .orbit, .skip-link, .toast { display: none !important; }
  .reveal.is-armed { opacity: 1 !important; transform: none !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .85em; color: #4E535B; }
  .card, .readout, .table, .list--steps > li { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
}
