/* ============================================================
   Soltari — Cut Standard v1
   Pale mineral working surfaces, dark mechanism glimpses,
   1px structural grid, oxide as semantic proof only.
   Fonts are self-hosted (OFL): Instrument Sans (variable),
   IBM Plex Mono 500 (labels/state only).
   ============================================================ */

/* ---------- Fonts ---------- */
@font-face {
  font-family: "Instrument Sans";
  src: url("/assets/fonts/InstrumentSans-Variable-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-stretch: 75% 100%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/IBMPlexMono-500-latin.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  /* Static 600-italic instance (wght/opsz pinned, latin subset,
     21KB vs 81KB variable; full variable master archived in
     design/fonts/). Only .sol-name uses it. */
  font-family: "Fraunces";
  src: url("/assets/fonts/Fraunces-Italic-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  /* Grey-violet palette (owner decision 2026-08-01; diverges from the
     original warm-mineral DESIGN.md tokens). All pairs AA-verified. */
  --c-void: #141318;        /* cool near-black mechanism */
  --c-graphite: #34333B;
  --c-oxide: #6A5E94;       /* Ultra Violet accent, grey-violet cast (owner iteration 2026-08-03; was #5F4B8B) */
  --c-field: #EAE9EE;       /* default working surface */
  --c-proof: #F5F4F8;       /* reading stock */
  --c-stone: #B6B4C0;       /* material studies */
  --c-ink: #26252D;         /* softened from #19181C (owner 2026-08-03): dark grey, 12.6:1 on field */
  --c-muted: #55535E;       /* AA-verified small secondary text (6.2:1 on field) */
  --c-muted-soft: #6E6C78;  /* large/decorative secondary only */
  --c-line: #B3B1BC;
  --c-line-dark: rgba(245, 244, 248, 0.22);

  --font-sans: "Instrument Sans", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "IBM Plex Mono", "Cascadia Mono", Consolas, monospace;

  --shell-max: 1460px;
  --shell-pad: clamp(22px, 4.4vw, 64px);
  --sect-pad: clamp(72px, 4vw + 3.5rem, 140px);

  --ease-fit: cubic-bezier(0.2, 0.8, 0.2, 1);
  --t-state: 120ms;    /* immediate state response */
  --t-fit: 260ms;      /* fitted transition */
  --t-reveal: 420ms;   /* material reveal */
  --t-threshold: 600ms;/* rare threshold moment */
  --t-proof: 180ms;    /* maker's proof */
  /* Opt-in keyword-size interpolation: lets the explorer accordion
     animate ::details-content height 0 -> auto (Chromium; a no-op
     where unsupported). */
  interpolate-size: allow-keywords;

  --c-lede: #34333B;        /* strong secondary text (hero lede) */
  --c-oxide-bright: #9F8BD0;/* accent on dark surfaces (Sol) */
  --c-on-oxide: #F5F4F8;    /* label color on oxide fills (5.3:1 on #6A5E94) */
  --c-oxide-deep: #4E3D78;  /* hover wipe on oxide fills (9.3:1 vs proof) */
  --focus: var(--c-void);
  --c-run: #4C9A62;         /* run-state (ordered diagrams) */
  --c-run-bright: #7DC98F;

  /* Theme-varying component values. Light values live here; the dark
     values live ONLY in the two dark entry blocks below (attribute +
     media). Components consume the vars unconditionally, so there are
     no per-component dark rules anywhere else in this file. */
  --sect-dark-bg: var(--c-void);
  --btn-mail-bg: var(--c-oxide);
  --btn-mail-fg: var(--c-on-oxide);
  --btn-mail-border: transparent;
  --btn-mail-wipe: var(--c-oxide-deep);
  --btn-mail-hover-fg: var(--c-on-oxide);
  --btn-2nd-hover-bg: var(--c-proof);
  --btn-2nd-hover-border: var(--c-void);
  --btn-2nd-hover-fg: var(--c-ink);
  /* Hero: hairline texture color, breathing glow (light = white sheen
     + two violet fields), 45-degree sweep band, blurred atmosphere. */
  --hero-hairline: rgba(52, 51, 59, 0.05);
  --hero-glow:
    radial-gradient(60% 55% at 58% 12%, rgba(255, 255, 255, 0.35), transparent 64%),
    radial-gradient(38% 42% at 22% 32%, rgba(95, 75, 139, 0.12), transparent 68%),
    radial-gradient(46% 50% at 80% 78%, rgba(95, 75, 139, 0.08), transparent 70%);
  --hero-sweep: linear-gradient(45deg,
    transparent 38%, rgba(95, 75, 139, 0.16) 50%, transparent 62%);
  /* Language-swap scan line (vertical band, sweeps left to right). */
  --lang-scan-band: rgba(106, 94, 148, 0.12);
  --lang-scan-line: rgba(106, 94, 148, 0.55);
  --hero-video-filter: blur(30px) saturate(1.15);
  --hero-video-opacity: 0.6;
  /* Mech panel: light = light sheet, dark-ink mechanism. */
  --mech-bg: var(--c-proof);
  --mech-structure: #34333B;
  --mech-soft: #55535E;
  --mech-mass: #26252D;
  --mech-video-opacity: 0.65;
  --cursor-light-vis: hidden;
  --sol-more-hover-bg: var(--c-oxide);
  --sol-more-hover-fg: var(--c-on-oxide);
  --state-after-bg: var(--c-oxide);
  --state-after-fg: var(--c-on-oxide);
  --sun-on: 0;  /* 1 = dark theme: sun icon shown, moon hidden */
}

/* ---------- Dark theme ---------- */
/* Manual override via data-theme; otherwise follows the system.
   These two blocks are the ONLY dark entry points: the attribute
   block and its media twin carry the identical payload (irreducible
   duality while no-JS must keep working). Edit both together. */
[data-theme="dark"] {
  --c-void: #0E0D11;
  --c-graphite: #45444F;
  --c-field: #131217;
  --c-proof: #1B1A21;
  --c-stone: #8F8D9B;
  --c-ink: #EDECF2;
  --c-muted: #A6A4B0;
  --c-muted-soft: #8B8996;
  --c-line: #34333C;
  --c-lede: #C9C7D3;
  --focus: var(--c-ink);
  /* Brighter accent for dark surfaces; labels on it flip to ink
     (#19181C on #9F8BD0 = 5.95:1). */
  --c-oxide: #9F8BD0;
  --c-on-oxide: #19181C;
  /* Dark sections sit slightly ABOVE the page background instead of
     below it (owner: #0E0D11 read as holes). */
  --sect-dark-bg: #17161C;
  /* Buttons (owner decision 2026-08-02): mail = white on mid-purple
     #7A63AC (4.8:1 AA), full outline, hover fills white and the label
     flips dark. Secondary hover fills light, mirroring light mode. */
  --btn-mail-bg: #7A63AC;
  --btn-mail-fg: #F5F4F8;
  --btn-mail-border: var(--c-line-dark);
  --btn-mail-wipe: #F5F4F8;
  --btn-mail-hover-fg: #19181C;
  --btn-2nd-hover-bg: #EDECF2;
  --btn-2nd-hover-border: #EDECF2;
  --btn-2nd-hover-fg: #19181C;
  --hero-hairline: rgba(245, 244, 248, 0.007);
  --hero-glow:
    radial-gradient(38% 42% at 24% 28%, rgba(159, 139, 208, 0.09), transparent 70%),
    radial-gradient(46% 50% at 78% 74%, rgba(122, 99, 172, 0.07), transparent 72%);
  --hero-sweep: linear-gradient(45deg,
    transparent 42%, rgba(159, 139, 208, 0.07) 50%, transparent 58%);
  --lang-scan-band: rgba(159, 139, 208, 0.12);
  --lang-scan-line: rgba(159, 139, 208, 0.75);
  --hero-video-filter: blur(36px) saturate(1.15);
  --hero-video-opacity: 0.16;
  /* Mech panel: dark = the original dark window, light mechanism. */
  --mech-bg: var(--c-void);
  --mech-structure: #F5F4F8;
  --mech-soft: #B6B4C0;
  --mech-mass: #EAE9EE;
  --mech-video-opacity: 0.55;
  --cursor-light-vis: visible;
  --sol-more-hover-bg: #F5F4F8;
  --sol-more-hover-fg: #19181C;
  --state-after-bg: #7A63AC;
  --state-after-fg: #F5F4F8;
  --sun-on: 1;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --c-void: #0E0D11;
    --c-graphite: #45444F;
    --c-field: #131217;
    --c-proof: #1B1A21;
    --c-stone: #8F8D9B;
    --c-ink: #EDECF2;
    --c-muted: #A6A4B0;
    --c-muted-soft: #8B8996;
    --c-line: #34333C;
    --c-lede: #C9C7D3;
    --focus: var(--c-ink);
    --c-oxide: #9F8BD0;
    --c-on-oxide: #19181C;
    --sect-dark-bg: #17161C;
    --btn-mail-bg: #7A63AC;
    --btn-mail-fg: #F5F4F8;
    --btn-mail-border: var(--c-line-dark);
    --btn-mail-wipe: #F5F4F8;
    --btn-mail-hover-fg: #19181C;
    --btn-2nd-hover-bg: #EDECF2;
    --btn-2nd-hover-border: #EDECF2;
    --btn-2nd-hover-fg: #19181C;
    --hero-hairline: rgba(245, 244, 248, 0.007);
    --hero-glow:
      radial-gradient(38% 42% at 24% 28%, rgba(159, 139, 208, 0.09), transparent 70%),
      radial-gradient(46% 50% at 78% 74%, rgba(122, 99, 172, 0.07), transparent 72%);
    --hero-sweep: linear-gradient(45deg,
      transparent 42%, rgba(159, 139, 208, 0.07) 50%, transparent 58%);
    --lang-scan-band: rgba(159, 139, 208, 0.12);
    --lang-scan-line: rgba(159, 139, 208, 0.75);
    --hero-video-filter: blur(36px) saturate(1.15);
    --hero-video-opacity: 0.16;
    --mech-bg: var(--c-void);
    --mech-structure: #F5F4F8;
    --mech-soft: #B6B4C0;
    --mech-mass: #EAE9EE;
    --mech-video-opacity: 0.55;
    --cursor-light-vis: visible;
    --sol-more-hover-bg: #F5F4F8;
    --sol-more-hover-fg: #19181C;
    --state-after-bg: #7A63AC;
    --state-after-fg: #F5F4F8;
    --sun-on: 1;
  }
}
/* Always-dark surfaces keep their light-on-dark semantics in EVERY
   theme: proof = light text/tiles, ink = text on light tiles. The
   pinned values equal the light-theme tokens, so this needs no theme
   split at all. */
.sect-dark,
.site-foot,
.hero-mech {
  --c-proof: #F5F4F8;
  --c-ink: #26252D;
  --c-stone: #B6B4C0;
}

/* ---------- Reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
body {
  margin: 0;
  background: var(--c-field);
  color: var(--c-ink);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
h1, h2, h3, p, ul, ol, dl, dd { margin: 0; }
/* Avoid short orphan last lines in running text; the span-based
   blocks (cards, band, ledes) need the rule explicitly. */
p, .exp-problem, .exp-fix, .exp-teaser, .sol-band-line, .lede-line { text-wrap: pretty; }
ul { padding: 0; list-style: none; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

::selection { background: var(--c-oxide); color: var(--c-on-oxide); }

.shell {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: var(--shell-pad);
}

/* ---------- Typography ---------- */
.display {
  font-weight: 540;
  font-stretch: 78%;
  letter-spacing: -0.045em;
  line-height: 0.88;
  font-size: clamp(3.1rem, 1.2rem + 7.4vw, 7.6rem);
  text-wrap: balance;
}
.h2 {
  font-weight: 540;
  font-stretch: 84%;
  letter-spacing: -0.035em;
  line-height: 0.98;
  font-size: clamp(2.05rem, 1.4rem + 2.4vw, 3rem);
  text-wrap: balance;
}
.h3 {
  font-weight: 570;
  letter-spacing: -0.03em;
  line-height: 1.05;
  font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.75rem);
}
.body-lg {
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.375rem);
  line-height: 1.35;
  letter-spacing: -0.015em;
}
.mono-label {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.105em;
  text-transform: uppercase;
  line-height: 1.3;
}
.kicker {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--c-muted);
  margin-bottom: clamp(20px, 3vw, 36px);
}
.kicker::before {
  content: "";
  width: 26px;
  height: 1px;
  background: var(--c-ink);
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding: 0 22px;
  border: 1px solid transparent;
  border-radius: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.75rem;
  letter-spacing: 0.105em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease-fit),
              color var(--t-state) var(--ease-fit),
              border-color var(--t-state) var(--ease-fit),
              transform var(--t-state) var(--ease-fit);
}
.btn:active { transform: translateY(1px); }
.btn-secondary {
  /* Frosted like the header: content stays readable, the hero
     texture never bleeds through the label (solid-ish fallback). */
  background: color-mix(in srgb, var(--c-proof) 55%, transparent);
  color: var(--c-ink);
  border-color: var(--c-graphite);
}
@supports (backdrop-filter: blur(1px)) {
  .btn-secondary {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}
.btn-secondary:hover {
  background: var(--btn-2nd-hover-bg);
  border-color: var(--btn-2nd-hover-border);
  color: var(--btn-2nd-hover-fg);
}
/* Email CTA: the one decision on the page. Oxide is reserved for it.
   Carries the mark's 45-degree cut in its corner; on hover a warm-black
   fill seats in from the left (fitted transition, 220ms). */
.btn-mail {
  position: relative;
  overflow: hidden;
  background: var(--btn-mail-bg);
  color: var(--btn-mail-fg);
  border-color: var(--btn-mail-border);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
}
/* Skew-slide (owner-picked 2026-08-02): the wipe leads with a
   45-degree edge PARALLEL to the button's cut corner (top leads,
   bottom trails). Slides via translate so the angle never distorts. */
.btn-mail::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: calc(100% + 48px);
  /* Light mode (owner-final after three rounds): purple seats into
     deeper purple, label stays white. Dark/dark-section wipes are
     white with an ink label (see var overrides). */
  background: var(--btn-mail-wipe);
  clip-path: polygon(0 0, 100% 0, calc(100% - 48px) 100%, 0 100%);
  transform: translateX(-102%);
  transition: transform 240ms var(--ease-fit);
}
.btn-mail:hover::before,
.btn-mail:focus-visible::before { transform: translateX(0); }
.btn-mail > span { position: relative; z-index: 1; transition: color 140ms var(--ease-fit); }
.btn-mail:hover > span,
.btn-mail:focus-visible > span { color: var(--btn-mail-hover-fg); }
/* The cut corner gets its missing edge: a short diagonal segment on
   the clip boundary (half its height is clipped away, so it renders
   as a 1px line exactly on the cut). Color follows --btn-edge, set
   wherever the button carries a visible border. */
.btn-mail { --btn-edge: var(--btn-mail-border); }
.btn-mail::after {
  content: "";
  position: absolute;
  right: -3px;
  bottom: 4px;
  width: 16px;
  height: 2px;
  background: var(--btn-edge);
  transform: rotate(-45deg);
  z-index: 2;
  pointer-events: none;
}
/* On dark sections the mail button carries a visible border and the
   wipe turns light with an ink label (identical in both themes). */
.sect-dark .btn-mail {
  border-color: var(--c-line-dark);
  --btn-edge: var(--c-line-dark);
  --btn-mail-wipe: var(--c-proof);
  --btn-mail-hover-fg: var(--c-ink);
}
@media (prefers-reduced-motion: reduce) {
  .btn-mail::before { transition: none; }
}

/* ---------- Skip link ---------- */
.skip-link {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 100;
  padding: 14px 18px;
  background: var(--c-void);
  color: var(--c-proof);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-decoration: none;
  transition: top var(--t-state) var(--ease-fit);
}
.skip-link:focus { top: 16px; --focus: var(--c-oxide); }

/* ---------- Header ---------- */
.site-head {
  position: sticky;
  top: 0;
  z-index: 50;
  /* Translucent chrome over content; falls back to solid where
     backdrop-filter is unsupported. */
  background: var(--c-field);
  border-bottom: 1px solid var(--c-line);
}
@supports (backdrop-filter: blur(1px)) {
  .site-head {
    background: color-mix(in srgb, var(--c-proof) 72%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
  }
}
.head-row {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2.5vw, 40px);
  min-height: 72px;
}
.lockup {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  padding-block: 10px;
}
.lockup .mark { flex: none; }
.lockup-word {
  font-weight: 570;
  font-stretch: 86%;
  font-size: 1.3rem;
  letter-spacing: -0.03em;
}
.site-nav { margin-left: auto; }
.site-nav ul { display: flex; gap: clamp(8px, 1.6vw, 28px); flex-wrap: wrap; }
.site-nav a {
  display: inline-block;
  padding: 14px 4px;
  font-size: 0.9375rem;
  letter-spacing: -0.01em;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-state) var(--ease-fit);
}
.site-nav a:hover { border-bottom-color: var(--c-ink); }
.head-tools { display: flex; align-items: center; gap: clamp(10px, 1.4vw, 22px); }
.lang { display: flex; align-items: center; font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; }
.lang a {
  padding: 14px 7px;
  text-decoration: none;
  color: var(--c-muted);
  transition: color var(--t-state) var(--ease-fit);
}
.lang a:hover { color: var(--c-ink); }
.lang [aria-current] { color: var(--c-ink); text-decoration: underline; text-underline-offset: 4px; }
.lang-sep { color: var(--c-line); }
/* Header switch: ONE link that flips to the other language; spans
   reproduce the old two-link look exactly (same paddings, sep,
   underline on the current language). Hover lights only the target
   label, as before. The footer keeps real per-language links. */
.lang-switch a { display: inline-flex; align-items: center; padding: 14px 0; }
.lang-switch a > span:not(.lang-sep) { padding: 0 7px; }
.lang-switch a:hover { color: var(--c-muted); }
.lang-switch a:hover > span:not([aria-current]):not(.lang-sep) { color: var(--c-ink); }
.btn-head { min-height: 44px; padding: 0 16px; }

/* Anchored sections clear the sticky header. */
section[id] { scroll-margin-top: 73px; }

.menu-toggle {
  display: none;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  min-width: 44px;
  padding: 0 12px;
  background: transparent;
  border: 1px solid var(--c-graphite);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.105em;
  text-transform: uppercase;
  color: var(--c-ink);
  cursor: pointer;
}

/* ---------- Theme toggle ---------- */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--c-ink);
  cursor: pointer;
}
/* Both icons share one cell; the active one arrives through the cut
   angle (owner-picked variant A 2026-08-03): incoming rotates
   +45deg -> 0 while the outgoing leaves at -45deg, 180ms fitted. */
.theme-toggle svg {
  grid-area: 1 / 1;
  width: 19px;
  height: 19px;
  transition: opacity 180ms var(--ease-fit), transform 180ms var(--ease-fit);
}
.theme-toggle .i-sun {
  opacity: var(--sun-on, 0);
  transform: rotate(calc((1 - var(--sun-on, 0)) * -45deg))
             scale(calc(0.6 + 0.4 * var(--sun-on, 0)));
}
.theme-toggle .i-moon {
  opacity: calc(1 - var(--sun-on, 0));
  transform: rotate(calc(var(--sun-on, 0) * 45deg))
             scale(calc(1 - 0.4 * var(--sun-on, 0)));
}
/* Burger: two 1px pseudo bars; expanded they fold into an X so the
   control never reads empty (old scaleY(0.1) collapsed to subpixels
   on mobile DPR and the icon vanished). */
.menu-toggle-bars { position: relative; width: 14px; height: 9px; }
.menu-toggle-bars::before,
.menu-toggle-bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 14px;
  height: 1px;
  background: currentColor;
  transition: transform var(--t-state) var(--ease-fit);
}
.menu-toggle-bars::before { top: 0; }
.menu-toggle-bars::after { bottom: 0; }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars::before { transform: translateY(4px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars::after { transform: translateY(-4px) rotate(-45deg); }

/* ---------- Sections ---------- */
.sect { padding-block: var(--sect-pad); border-bottom: 1px solid var(--c-line); }
.sect-proof { background: var(--c-proof); }
.sect-dark {
  background: var(--sect-dark-bg);
  color: var(--c-proof);
  --focus: var(--c-proof);
  /* Same separator rhythm as the light sections, in the dark line
     tone (owner request 2026-08-02: Vorgehen/Grundsätze had none). */
  border-bottom: 1px solid var(--c-line-dark);
}
.sect-dark .kicker { color: var(--c-stone); }
.sect-dark .kicker::before { background: var(--c-stone); }
.sect-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: clamp(36px, 5vw, 72px);
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  isolation: isolate; /* keeps the ::before glow behind the content */
  border-bottom: 1px solid var(--c-line);
  /* Generic dense texture in the design language: fine parallel
     hairlines at the cut angle (45deg), static so nothing shimmers
     under the breathing gradient above it. */
  background-image: repeating-linear-gradient(45deg,
    var(--hero-hairline) 0 1px, transparent 1px 23px);
}
/* Breathing gradient field behind the whole hero (owner-picked H1,
   2026-08-02): a faint grey-violet glow that drifts over ~45s.
   Motion lives in the no-preference block. */
.hero::before {
  content: "";
  position: absolute;
  inset: -12%;
  z-index: -1;
  pointer-events: none;
  background: var(--hero-glow);
}
/* Light sweep (B, replaced the ghost cut 2026-08-02): every ~28s a
   soft 45-degree light band crosses the hero. Static layers stay
   invisible; the animation lives in the no-preference block. */
.hero::after {
  content: "";
  position: absolute;
  inset: -50%;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  /* Light mode: a violet tint band reads on the pale field (white was
     invisible); dark mode keeps the bright glow below. */
  background: var(--hero-sweep);
  will-change: transform;
}
/* Full-bleed atmosphere clip (same file as the panel video),
   oversized so blurred edges never show. Light mode re-exposes the
   dark material via invert + hue-rotate (owner-picked variant B,
   2026-08-02): pale lavender clouds instead of an invisible grey
   wash. Dark mode shows it as-is, blurred and quiet. */
.hero-bg-video {
  position: absolute;
  inset: -48px;
  width: calc(100% + 96px);
  height: calc(100% + 96px);
  object-fit: cover;
  z-index: -1;
  /* Light mode: native day-exposure clip (hero-light.mp4 via JS),
     so no invert trickery anymore, just blur. */
  filter: var(--hero-video-filter);
  opacity: var(--hero-video-opacity);
  pointer-events: none;
}
/* Drawing-sheet instruments (A): corner crosses and dimension ticks. */
.hero-sheet {
  position: absolute;
  inset: 18px;
  z-index: -1;
  pointer-events: none;
  color: var(--c-graphite);
  opacity: 0.35;
}
.hs-cross { position: absolute; }
.hs-tl { top: 0; left: 0; }
.hs-tr { top: 0; right: 0; }
.hs-bl { bottom: 0; left: 0; }
.hs-br { bottom: 0; right: 0; }
.hs-tick { position: absolute; background: currentColor; }
.hs-th { top: 0; width: 1px; height: 7px; }
.hs-tv { left: 0; width: 7px; height: 1px; }
@media (max-width: 767px) {
  .hero-sheet { display: none; }
}
/* "Neu"/"New" chip on Sol touchpoints: quiet, tiny, accent-toned. */
.new-chip {
  display: inline-block;
  padding: 2px 6px;
  margin-left: 8px;
  border: 1px solid var(--c-oxide);
  border-radius: 2px;
  font-size: 0.5625rem;
  letter-spacing: 0.12em;
  color: var(--c-oxide);
  vertical-align: 2px;
}
/* On the always-dark Sol band the deep violet is too dim in light
   theme; use the bright accent there. */
.sol-band .new-chip { border-color: var(--c-oxide-bright); color: var(--c-oxide-bright); }
/* The product name: Fraunces italic at real weight — keeps the
   display-serif-italic voice (owner direction 2026-08-04) but with
   enough presence to hold on the light hero, where Instrument
   Serif's single 400 weight ran too thin. Variable wght axis. */
.sol-name {
  font-family: "Fraunces", Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.08em;
  letter-spacing: 0.01em;
  text-transform: none; /* containers (tease, buttons) run uppercase */
  color: var(--c-oxide);
}
.sol-band .sol-name { color: var(--c-oxide-bright); }
.sol-band-h .sol-name { margin-left: 4px; }
/* Inside buttons the name follows the button color (hover flips). */
.exp-sol-more .sol-name { color: inherit; }
/* The dialog is always dark: use the bright accent there. */
.sol-dialog .sol-name { color: var(--c-oxide-bright); }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(28px, 4vw, 72px);
  align-items: stretch;
  padding-block: clamp(40px, 4vw, 72px);
  /* Fill the viewport below the sticky header on any resolution.
     vh fallback first: Safari < 16.4 drops the dvh line. */
  min-height: calc(100vh - 73px);
  min-height: calc(100dvh - 73px);
}
.hero-copy { display: flex; flex-direction: column; justify-content: center; }
.hero-copy .display { margin-bottom: clamp(24px, 3vw, 44px); }
.hero-lede { color: var(--c-lede); max-width: 30em; margin-bottom: clamp(28px, 3.4vw, 48px); }
/* DE: line 1 ends exactly at "System:", the list starts line 2.
   Valid window measured live: (26.5em, 27.5em); 27em sits centered. */
:lang(de) .hero-lede { max-width: 27em; }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 14px; }
/* Hero panel frame. With the day-exposure clip (2026-08-02) the
   panel is themed: LIGHT mode = light sheet with dark-ink mechanism
   (matching the real logo rendering), DARK mode = the original dark
   window with light mechanism. SVG colors ride on the vars below
   (CSS beats the SVG presentation attributes). */
.hero-mech {
  position: relative;
  background: var(--mech-bg);
  border: 1px solid var(--c-graphite);
  min-height: 380px;
  overflow: hidden;
  /* Keep the near-square panel proportions and center it in the
     fullscreen hero column. */
  aspect-ratio: 1 / 1.02;
  max-height: 540px;
  align-self: center;
  width: 100%;
}

/* ---------- Cursor light (v2, quiet) ----------
   Back to a glow (owner call, coordinates dropped), but softer than
   v1: smaller radius, roughly half the strength, and violet-tinted
   so it reads as the brand accent catching the surface rather than a
   flashlight. Mounted on every section (JS); visible wherever the
   surface is dark: always on .sect-dark/.site-foot, everywhere in
   dark theme. */
.sect-dark, .site-foot, .hero, .sect { position: relative; overflow: hidden; }
.cursor-light {
  position: absolute;
  left: 0;
  top: 0;
  width: 460px;
  height: 460px;
  margin: -230px 0 0 -230px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(245, 244, 248, 0.028) 0%,
    rgba(159, 139, 208, 0.022) 34%,
    transparent 60%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 420ms var(--ease-fit);
  will-change: transform;
  visibility: var(--cursor-light-vis, hidden);
}
.sect-dark .cursor-light,
.site-foot .cursor-light { visibility: visible; }
:is(.sect-dark, .site-foot, .hero, .sect):hover .cursor-light { opacity: 1; }

/* Mech panel internals (hero brand animation) */
/* Background clip behind the mechanism (mounts when
   src/assets/video/hero.mp4 exists at build time). Kept quiet: the
   mechanism stays the subject, the material stays atmosphere. */
.hero-mech-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: var(--mech-video-opacity);
}
/* Theme-swap ghost: JS freezes the outgoing video frame onto a canvas
   while the other theme's clip loads. Purely anti-flash: the visible
   transition aesthetics come from the view transition below; the
   ghost just fades once the new clip can play. */
.video-ghost { pointer-events: none; transition: opacity 300ms var(--ease-fit); }
.video-ghost.is-fading { opacity: 0 !important; }
.hero-mech svg.mech { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Mechanism parts follow the panel theme vars. */
.mech-reg { stroke: var(--mech-structure); }
.mech-trace { stroke: var(--mech-soft); }
.mech-scan { stroke: var(--mech-structure); }
.mech-mass, .mech-offcut { fill: var(--mech-mass); }
.hero-mech .anno { color: var(--mech-soft); }
.anno { position: absolute; }
.anno-tl { top: 14px; left: 16px; }
.anno-br { bottom: 14px; right: 16px; text-align: right; }
.mech-offcut-pos { transition: transform var(--t-state) var(--ease-fit); }
.mech-scan { opacity: 0; }
.hero-mech:hover .mech-offcut-pos { transform: translate(-8px, -8px); }
@media (prefers-reduced-motion: reduce) {
  .hero-mech:hover .mech-offcut-pos { transform: none; }
}

/* ---------- Branchen-Explorer (Kennen Sie das?) ---------- */
.exp { display: flex; flex-direction: column; gap: 26px; }
/* Sol finale band: sits under the accordion, above all workflows. */
.sol-band {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  padding: 20px 22px;
  background: #1C1B21;
  border: 1px solid var(--c-line-dark);
  border-left: 3px solid var(--c-oxide-bright);
  color: var(--c-proof);
}
/* Sol atmosphere (owner 2026-08-04): the eclipse-and-glints study
   (series sibling of the explorer images, more chroma — core
   product) sits permanently behind the band, brightening a touch on
   hover of the clickable band. Same layering trick as .exp-group. */
.sol-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("/assets/img/explorer/sol.webp") center / cover no-repeat;
  opacity: 0.22;
  transition: opacity var(--t-reveal) var(--ease-fit);
}
.sol-band.is-clickable:hover::before { opacity: 0.32; }
/* The same study in motion (owner 2026-08-04): while the video is
   live (JS sets .sol-live on the band during hover playback) it
   REPLACES the still — the two layers crossfade, never stack, so no
   double-exposure ghosting. The clip plays once per hover and holds
   its final frame; hover-off fades back to the still and the next
   hover rewinds while invisible. Hidden from touch devices. */
.sol-band-video {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--t-reveal) var(--ease-fit);
  pointer-events: none;
}
@media (hover: none) {
  .sol-band-video { display: none; }
}
@media (hover: hover) {
  .sol-band.sol-live:hover .sol-band-video { opacity: 0.32; }
  .sol-band.sol-live:hover::before { opacity: 0; }
}
.sol-band > .sol-mark { color: var(--c-oxide-bright); flex: none; }
.sol-band-copy { display: flex; flex-direction: column; gap: 3px; min-width: min(100%, 300px); flex: 1; }
.sol-band-h { font-weight: 570; letter-spacing: -0.02em; font-size: 1.15rem; }
.sol-band-line { color: var(--c-stone); font-size: 0.9375rem; max-width: 46em; }
/* Mobile swap (owner 2026-08-03): the big band mark becomes a small
   inline glyph right after the SOL wordmark (media block below). */
.sol-mark-inline {
  display: none;
  color: var(--c-oxide-bright);
  vertical-align: -1px;
  margin-left: 5px;
}
/* Clickable band (JS): opens the Sol overlay. */
.sol-band.is-clickable { cursor: pointer; transition: border-color var(--t-state) var(--ease-fit); }
.sol-band.is-clickable:hover {
  border-color: color-mix(in srgb, var(--c-muted-soft) 55%, var(--c-graphite));
  border-left-color: var(--c-oxide-bright);
}
.exp-sol-more {
  margin-left: auto;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--c-line-dark);
  background: transparent;
  color: var(--c-oxide-bright);
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease-fit),
              color var(--t-state) var(--ease-fit);
}
.sol-band.is-clickable:hover .exp-sol-more,
.exp-sol-more:hover,
.exp-sol-more:focus-visible {
  background: var(--sol-more-hover-bg);
  border-color: var(--sol-more-hover-bg);
  color: var(--sol-more-hover-fg);
}
/* Hero teaser: quiet line under the CTAs, opens the same overlay. */
.sol-tease {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--c-muted);
  cursor: pointer;
  align-self: flex-start;
  transition: color var(--t-state) var(--ease-fit);
}
.sol-tease svg { color: var(--c-oxide); transition: color var(--t-state) var(--ease-fit); }
.sol-tease:hover, .sol-tease:focus-visible { color: var(--c-oxide); }
/* Sol overlay (native dialog). */
.sol-dialog {
  padding: 0;
  border: 1px solid var(--c-graphite);
  background: #1C1B21;
  color: #F5F4F8;
  max-width: min(620px, calc(100vw - 44px));
  margin: auto;
}
.sol-dialog::backdrop { background: rgba(10, 9, 13, 0.72); }
.sol-d-inner {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(24px, 4vw, 44px);
}
/* Atmosphere layer: slow violet drift + faint 45-degree hairlines
   (brand pattern). Drift animates in the no-preference block. */
.sol-d-inner::before {
  content: "";
  position: absolute;
  inset: -35%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(42% 38% at 30% 18%, rgba(159, 139, 208, 0.11), transparent 62%),
    radial-gradient(50% 46% at 76% 78%, rgba(95, 75, 139, 0.09), transparent 66%),
    repeating-linear-gradient(45deg, rgba(245, 244, 248, 0.022) 0 1px, transparent 1px 9px);
}
.sol-d-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 44px;
  height: 44px;
  border: 1px solid var(--c-line-dark);
  /* Frosted like .btn-secondary (owner 2026-08-04): the discovery
     wave and floating icons now travel under the X, so it needs a
     readable pane (solid-ish fallback below). */
  background: color-mix(in srgb, #1C1B21 55%, transparent);
  z-index: 1;
  color: #B6B4C0;
  font-size: 1.1rem;
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease-fit),
              color var(--t-state) var(--ease-fit);
}
.sol-d-close:hover, .sol-d-close:focus-visible { background: #F5F4F8; color: #19181C; }
@supports (backdrop-filter: blur(1px)) {
  .sol-d-close {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}
/* Sol scene: breathing corona behind the eclipse mark, two counter-
   rotating orbits carrying tool dots ("sieht alle Tools" as motion).
   Rotation/breathing run only under no-preference (see motion block). */
.sol-d-anim {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 250px;
  margin-bottom: clamp(8px, 1.6vw, 16px);
  color: var(--c-oxide-bright);
  /* overflow stays visible: the discovery wave (.sol-wave) escapes
     the scene and travels across the whole panel (owner 2026-08-04).
     The theater pieces all move <=64px around center, so nothing
     else can leak; .sol-d-inner still clips at the panel edge. */
}
/* (The blurred Carcosa still was removed 2026-08-02 on owner verdict;
   liveliness now comes from the eclipse opening + data sparks.) */
/* Opening theater pieces (invisible unless the animation runs). */
.sol-sun,
.sol-moon {
  position: absolute;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
.sol-sun { background: var(--c-oxide-bright); }
.sol-moon { background: #1C1B21; }
/* Discovery wave: expands once from the mark after totality; each
   tool registers the moment it passes (see per-icon --d delays). */
.sol-wave {
  position: absolute;
  width: 110px;
  height: 110px;
  border: 1px solid var(--c-oxide-bright);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
/* Tool cloud around the scene: original brand colors, tiny, floating. */
.sol-tool {
  position: absolute;
  transform: translate(-50%, -50%);
  opacity: 0.9;
  pointer-events: none;
}
.sol-tool img { display: block; width: 15px; height: 15px; }
/* Tethers: SOL's hands, curved hairlines drawn by JS between the
   orbiting rectangles and tools on their side; tracked per frame. */
.sol-tethers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.sol-tethers path {
  fill: none;
  stroke: var(--c-oxide-bright);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0;
}
.sol-d-mark { position: relative; z-index: 1; }
/* Hover: the cut opens (halves slide apart along the cut normal)
   and the corona answers with a flare pulse. */
.sol-d-mark path { transition: transform 240ms var(--ease-fit); }
.sol-d-anim:hover .sol-d-mark path:first-child { transform: translate(-1.1px, -1.1px); }
.sol-d-anim:hover .sol-d-mark path:last-child { transform: translate(1.1px, 1.1px); }
.sol-d-anim:hover .sol-corona { filter: brightness(1.4); }
.sol-corona { transition: filter 240ms var(--ease-fit); }
.sol-pulse {
  position: absolute;
  width: 110px;
  height: 110px;
  border: 1px solid var(--c-oxide-bright);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
.sol-corona {
  position: absolute;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(159, 139, 208, 0.32) 0%,
    rgba(159, 139, 208, 0.08) 45%,
    transparent 70%);
}
.sol-orbit {
  position: absolute;
  border: 1px solid var(--c-line-dark);
  border-radius: 50%;
}
.sol-orbit-1 { width: 168px; height: 168px; --r: 84px; }
.sol-orbit-2 { width: 224px; height: 224px; --r: 112px; }
.sol-orbit i {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px;
  background: var(--c-stone);
  transform: rotate(var(--a, 0deg)) translateY(calc(-1 * var(--r)));
}
.sol-orbit i:first-child { background: var(--c-oxide-bright); }
/* Opening: panel rises and fades, backdrop fades (no-op without
   @starting-style support; transitions die under reduced motion). */
.sol-dialog[open] {
  opacity: 1;
  transform: none;
  transition: opacity 240ms var(--ease-fit), transform 300ms var(--ease-fit);
}
@starting-style {
  .sol-dialog[open] { opacity: 0; transform: translateY(14px); }
}
.sol-dialog::backdrop { transition: background-color 240ms linear; }
@starting-style {
  .sol-dialog[open]::backdrop { background: rgba(10, 9, 13, 0); }
}
.sol-dialog .h3 { margin-bottom: 0.6em; }
.sol-d-body { color: #B6B4C0; max-width: 44em; }
.sol-d-body + .sol-d-body { margin-top: 0.8em; }
/* Accordion of company types (native details, exclusive via name). */
.exp-groups { display: flex; flex-direction: column; gap: 10px; }
.exp-group {
  background: #1C1B21;
  border: 1px solid var(--c-graphite);
  transition: background-color var(--t-state) var(--ease-fit);
}
/* Frame under the pointer: the whole chrome takes the hover lift —
   open, closed, AND mid-collapse (the rule is state-agnostic, so the
   lift survives the closing height animation while hovered, owner
   2026-08-04). Cards are opaque (#17161B) and stay untouched; for a
   closed row this paints the summary strip, replacing the old
   summary-scoped translucent lift. */
.exp-group:hover { background-color: #232229; }
/* Industry atmosphere (owner 2026-08-04): each group carries a dark
   AI material study (--exp-bg inline, img/explorer/*.webp served
   verbatim from the generator — recompressing the lossy source
   visibly degraded it; same file for DE/EN). It surfaces faintly on
   hover/focus and stays visible while the group is open. Gated on
   hover-capable devices: touch never fetches the images. The group
   isolates so the pseudo can sit under the content but above the
   group's own background. */
@media (hover: hover) {
  .exp-group {
    position: relative;
    isolation: isolate;
  }
  .exp-group::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background-size: cover;
    background-position: center top; /* v2 images: motif in the upper half */
    opacity: 0;
    transition: opacity var(--t-reveal) var(--ease-fit);
  }
  /* Lazy arming (perf, 2026-08-04): ~900KB of webp must not ride the
     initial load. JS adds .is-armed when the explorer approaches the
     viewport (IntersectionObserver, generous margin), only then do
     the images download. No-JS visitors skip the decoration. */
  .exp-groups.is-armed .exp-group::before { background-image: var(--exp-bg); }
  /* :focus-visible (not :focus-within): a mouse click on the summary
     must not pin the background after closing; keyboard focus still
     reveals it, and open groups hold it via [open]. */
  .exp-group:hover::before,
  .exp-group:has(> .exp-summary:focus-visible)::before,
  .exp-group[open]::before { opacity: 0.11; }
  @media (prefers-reduced-motion: reduce) {
    .exp-group::before { transition: none; }
  }
}
/* Open state reads selected without shouting (softened 2026-08-02). */
.exp-group[open] { border-color: color-mix(in srgb, var(--c-muted-soft) 55%, var(--c-graphite)); }
.exp-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 18px;
  padding: 16px 20px;
  cursor: pointer;
  list-style: none;
  transition: background-color var(--t-state) var(--ease-fit);
}
.exp-summary::-webkit-details-marker { display: none; }
.exp-teaser { color: var(--c-stone); font-size: 0.9375rem; grid-row: 2; }
.exp-marker {
  grid-row: 1 / span 2;
  align-self: center;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--c-stone);
  transition: transform var(--t-fit) var(--ease-fit);
}
.exp-group[open] .exp-marker { transform: rotate(45deg); }
.exp-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  /* Top gap matches the bottom edge padding (summary hover-bg makes
     the row read as a block, so the visible gap is this padding). */
  padding: 14px;
}
.exp-card {
  position: relative;
  margin: 0;
  background: #17161B;
  border: 1px solid var(--c-line-dark);
  color: var(--c-proof);
  display: flex;
  flex-direction: column;
}
/* Responsiveness: cards lift slightly under the pointer. */
.js .exp-card {
  transition: transform var(--t-state) var(--ease-fit),
              border-color var(--t-state) var(--ease-fit);
}
.js .exp-card:hover { transform: translateY(-2px); border-color: rgba(245, 244, 248, 0.36); }
.exp-head { padding: 14px 16px 0; display: flex; flex-direction: column; gap: 6px; }
.exp-title { color: var(--c-stone); }
.exp-problem, .exp-fix { font-size: 0.9375rem; letter-spacing: -0.01em; }
/* Today reads muted (the mess), the Soltari state reads clear. */
.exp-problem { color: var(--c-stone); }
/* With JS the two lines share one cell and swap with the state;
   without JS both stand and the ordered diagram is the still. */
.js .exp-head { display: grid; grid-template-columns: 100%; row-gap: 6px; }
.js .exp-title { grid-row: 1; }
.js .exp-problem,
.js .exp-fix {
  grid-row: 2;
  grid-column: 1;
  min-height: 4.4em;
  transition: opacity 240ms var(--ease-fit);
}
.js .exp-card.is-today .exp-fix { opacity: 0; }
.js .exp-card:not(.is-today) .exp-problem { opacity: 0; }
/* Two-state diagram stage. */
.exp-stage { position: relative; height: 230px; overflow: hidden; }
.js .exp-stage { cursor: pointer; }
.exp-lines { position: absolute; inset: 0; width: 100%; height: 100%; }
.exp-lines .l-chaos path {
  stroke: var(--c-stone);
  stroke-width: 1;
  stroke-dasharray: 3 5;
  fill: none;
  opacity: 0;
  transition: opacity 260ms var(--ease-fit);
}
.exp-card.is-today .l-chaos path { opacity: 0.5; }
.exp-lines .l-order line {
  stroke: var(--c-run);
  stroke-width: 1.2;
  stroke-dasharray: 4 6;
  opacity: 1;
  transition: opacity 260ms var(--ease-fit) 160ms;
}
.exp-card.is-today .l-order line { opacity: 0; transition-delay: 0ms; }
.exp-node {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 10px;
  background: var(--c-proof);
  color: var(--c-ink);
  font-size: 0.75rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  transition: transform 380ms var(--ease-fit), opacity 260ms var(--ease-fit);
}
.exp-node img { width: 15px; height: 15px; flex: none; }
/* Nodes settle with a slight stagger (both directions). */
span.exp-node:nth-of-type(2) { transition-delay: 40ms; }
span.exp-node:nth-of-type(3) { transition-delay: 80ms; }
.exp-card.is-today .exp-node {
  transform: translate(-50%, -50%) translate(var(--dx, 0px), var(--dy, 0px)) rotate(var(--r, 0deg));
}
/* Green proof tick in the ordered state. */
.exp-node::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  width: 7px;
  height: 7px;
  background: var(--c-run-bright);
  transition: opacity 200ms var(--ease-fit) 260ms, transform 200ms var(--ease-fit) 260ms;
}
.exp-card.is-today .exp-node::after { opacity: 0; transform: scale(0.5); transition-delay: 0ms; }
.exp-node.is-soltari {
  background: var(--c-void);
  color: var(--c-proof);
  border: 1px solid var(--c-proof);
}
/* Result chip anchors to the right edge so long labels (Lexware
   Office, HubSpot) never leave the stage. Rides the state-after vars
   (owner 2026-08-04): identical in light, and dark gets white on
   #7A63AC (4.8:1) instead of ink on the bright accent — matches the
   MIT-SOLTARI chip. */
.exp-node.is-result {
  background: var(--state-after-bg);
  color: var(--state-after-fg);
  transform: translateY(-50%);
}
.exp-node.is-soltari::after,
.exp-node.is-result::after { display: none; }
.exp-card.is-today .exp-node.is-soltari {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.8);
}
.exp-card.is-today .exp-node.is-result {
  opacity: 0;
  transform: translateY(-50%) scale(0.8);
}
/* Oxide scan hairline, sweeps once per flip to the ordered state. */
.exp-scan {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--c-oxide);
  opacity: 0;
}
/* State chip: shows the current state; real button for touch/keyboard.
   Pointer users flip by hovering the card. */
.exp-foot { display: flex; justify-content: flex-end; padding: 0 14px 12px; }
.exp-state {
  min-height: 44px;
  padding: 0 14px;
  /* Typography identical to .btn so the chip never reads heavier. */
  font-size: 0.75rem;
  letter-spacing: 0.105em;
  border: 1px solid var(--c-line-dark);
  background: transparent;
  color: var(--c-stone);
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease-fit),
              color var(--t-state) var(--ease-fit),
              border-color var(--t-state) var(--ease-fit);
}
.exp-state-after { display: none; }
.exp-card:not(.is-today) .exp-state {
  background: var(--state-after-bg);
  border-color: var(--state-after-bg);
  color: var(--state-after-fg);
}
.exp-card:not(.is-today) .exp-state .exp-state-today { display: none; }
.exp-card:not(.is-today) .exp-state .exp-state-after { display: inline; }

/* ---------- Index rows (fits / principles) ---------- */
.rows { border-top: 1px solid var(--c-line); }
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr);
  gap: clamp(14px, 2.4vw, 40px);
  padding-block: clamp(20px, 2.6vw, 30px);
  padding-inline: clamp(12px, 1.6vw, 24px);
  border-bottom: 1px solid var(--c-line);
  transition: background-color var(--t-state) var(--ease-fit);
}
.row:hover { background: var(--c-proof); }
.sect-proof .row:hover { background: var(--c-field); }
.row-body { color: var(--c-muted); max-width: 36em; }
.row-title { padding-top: 1px; }

/* Intro line above the steps (dark section). */
.step-intro {
  color: var(--c-stone);
  margin-bottom: clamp(24px, 3vw, 40px);
}

/* ---------- Steps (dark mechanism) ---------- */
.steps { border-top: 1px solid var(--c-line-dark); }
.step {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: clamp(14px, 2.4vw, 40px);
  padding-block: clamp(26px, 3vw, 40px);
  padding-inline: clamp(12px, 1.6vw, 24px);
  border-bottom: 1px solid var(--c-line-dark);
}
.step-head { display: flex; align-items: baseline; gap: 18px; }
.step-no {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8125rem;
  letter-spacing: 0.105em;
  color: var(--c-stone);
}
.step-body { color: var(--c-stone); max-width: 38em; }
.step-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.chip {
  display: inline-block;
  padding: 5px 9px;
  border-radius: 2px; /* rounded.proof — tiny state details only */
  background: transparent;
  border: 1px solid var(--c-line-dark);
  color: var(--c-stone);
}

/* ---------- About ---------- */
.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
  gap: clamp(28px, 4vw, 80px);
}
.about-rule {
  height: 2px;
  width: 64px;
  background: var(--c-oxide);
  margin-bottom: 22px;
}
.about-note { color: var(--c-muted); margin-top: 1.2em; }

/* ---------- Contact (dark) ---------- */
.contact .display { font-size: clamp(2.6rem, 1.2rem + 5.4vw, 5.6rem); margin-bottom: clamp(22px, 3vw, 40px); }
.contact-lede { color: var(--c-stone); max-width: 37em; margin-bottom: clamp(30px, 3.6vw, 52px); }
/* One sentence per line while the box is wide enough (37em holds the
   36.4em German second sentence); below that they wrap naturally. */
.lede-line { display: block; }
@media (max-width: 860px) {
  .lede-line { display: inline; }
}
.contact-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; }
.email-state { color: var(--c-stone); }

/* ---------- Footer ---------- */
.site-foot {
  background: var(--c-void);
  color: var(--c-proof);
  --focus: var(--c-proof);
  border-top: 1px solid var(--c-line-dark);
  padding-block: clamp(40px, 5vw, 72px) 32px;
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 22px 40px;
  align-items: center;
}
.foot-brand { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.foot-legal { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap; }
.foot-legal a { font-size: 0.9375rem; text-decoration-thickness: 1px; text-underline-offset: 4px; padding-block: 10px; }
.foot-pending { color: var(--c-stone); }
.foot-lang a { color: var(--c-stone); }
.foot-lang a:hover, .foot-lang a[aria-current] { color: var(--c-proof); }
.foot-note { grid-column: 1 / -1; color: var(--c-stone); font-size: 0.9375rem; max-width: 44em; }
.foot-copy { grid-column: 1 / -1; color: var(--c-stone); }

/* ---------- Legal shell pages ---------- */
.legal { padding-block: clamp(56px, 6vw, 100px); min-height: 46vh; }
.legal .h2 { margin-bottom: 20px; }
.legal-state {
  display: inline-block;
  margin-bottom: 28px;
  padding: 5px 9px;
  border-radius: 2px;
  border: 1px solid var(--c-graphite);
  color: var(--c-ink);
}
.legal p { max-width: 38em; }
.legal p + p { margin-top: 1em; }
.legal .legal-h { margin-top: 36px; margin-bottom: 10px; }
.legal-meta { color: var(--c-muted); margin-top: 36px; }
.legal .back { display: inline-block; margin-top: 36px; padding-block: 10px; }

/* ---------- Combined dark section: inner block headings ---------- */
.block-h {
  margin-top: clamp(56px, 6vw, 96px);
  margin-bottom: 10px;
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2.1rem);
}
/* ---------- Motion: reveals ---------- */
@media (prefers-reduced-motion: no-preference) {
  /* Theme switch = threshold moment (600ms): the old page blurs out
     while gently growing, the new one de-blurs in and settles. The
     plus-lighter pair keeps the midpoint luminous instead of dipping
     dark. JS wraps the data-theme flip in a view transition; browsers
     without support (and reduced motion) swap instantly as before. */
  ::view-transition-image-pair(root) { isolation: isolate; }
  ::view-transition-old(root),
  ::view-transition-new(root) { mix-blend-mode: plus-lighter; }
  /* Language swap: light-scan sweep (owner-picked 2026-08-04), the
     same left-to-right pass as the hero mech scan, just quicker.
     The scan line (html::before below) crosses the page while a
     fixed html::after veil blurs the whole page via backdrop-filter,
     the words swap behind the held blur, then it sweeps back and the
     page resolves. `both` fill keeps the veil held between the two
     phases, so the swap itself is invisible. backdrop-filter instead
     of filter on body: edge sampling is clamped, so no light canvas
     fringe at the viewport edges over dark sections, and no fixed-
     position containing block on body. */
  html.lang-out::after,
  html.lang-in::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 2147482999; /* just under the scan line */
    pointer-events: none;
  }
  html.lang-out::after {
    animation: lang-veil-out 150ms cubic-bezier(0.25, 0.6, 0.4, 1) both;
  }
  html.lang-in::after {
    animation: lang-veil-in 220ms cubic-bezier(0.2, 0.7, 0.4, 1) both;
  }
  /* The scan line itself: a fixed vertical light band whose leading
     hairline crosses the viewport in lockstep with the blur wave
     (180ms here === SWEEP_MS in main.js; edit both together). Runs
     once on the way out and once on the way back in. */
  html.lang-out::before,
  html.lang-in::before {
    content: "";
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(24vw, 280px);
    z-index: 2147483000;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--lang-scan-band));
    border-right: 1px solid var(--lang-scan-line);
    will-change: transform;
    animation: lang-scan 180ms linear both;
  }
  /* Theme toggle (html.vt-theme set by JS for the duration): the full
     600ms threshold dissolve with blur and breath. */
  html.vt-theme::view-transition-old(root) { animation: vt-dissolve-out var(--t-threshold) cubic-bezier(0.45, 0.05, 0.55, 0.95) both; }
  html.vt-theme::view-transition-new(root) { animation: vt-dissolve-in var(--t-threshold) cubic-bezier(0.45, 0.05, 0.55, 0.95) both; }
  /* Underlay for the dissolve: the snapshots' blur feathers their
     edges translucent, and the live page behind them already renders
     the NEW theme — without this, its canvas fringes through at the
     viewport edges (white flash on dark->light). The image-pair is
     isolation:isolate per UA styles, so the plus-lighter dissolve is
     untouched; only the fringe backdrop tracks the field color
     old -> new on the same curve. --vt-from-field is set inline by
     JS just before the transition starts. */
  html.vt-theme::view-transition {
    animation: vt-underlay var(--t-threshold) cubic-bezier(0.45, 0.05, 0.55, 0.95) both;
  }
  /* Edge repair (replaces the rejected breath/zoom): the fringe is
     the composite's own content, just with blur-feathered alpha. The
     vt-edge SVG filter (inline in base.njk) multiplies alpha x8 and
     clamps, restoring those edge pixels to full opacity in their own
     smeared colors. Interior alpha is already 1, so every interior
     pixel is mathematically unchanged; no geometry is touched. The
     outermost sub-pixel tail falls back to the underlay above. */
  html.vt-theme::view-transition-image-pair(root) {
    filter: url("#vt-edge");
  }
  .js [data-reveal] {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--t-reveal) var(--ease-fit),
                transform var(--t-reveal) var(--ease-fit);
  }
  .js [data-reveal="2"] { transition-delay: 70ms; }
  .js [data-reveal="3"] { transition-delay: 140ms; }
  .js [data-reveal].is-in { opacity: 1; transform: none; }
  /* One threshold moment: entering the dark mechanism section. */
  .js .sect-dark .threshold {
    opacity: 0;
    transform: translateY(22px);
    transition: opacity var(--t-threshold) var(--ease-fit),
                transform var(--t-threshold) var(--ease-fit);
  }
  .js .sect-dark .threshold.is-in { opacity: 1; transform: none; }

  /* Mech choreography: runs on each re-entry into view. */
  .js .hero-mech:not(.is-in) .mech-mass,
  .js .hero-mech:not(.is-in) .mech-offcut,
  .js .hero-mech:not(.is-in) .mech-reg,
  .js .hero-mech:not(.is-in) .mech-trace,
  .js .hero-mech:not(.is-in) .mech-proof { opacity: 0; }
  .js .hero-mech.is-in .mech-reg { animation: mech-fade 320ms var(--ease-fit) 120ms both; }
  .js .hero-mech.is-in .mech-mass { animation: mech-mass-in 320ms var(--ease-fit) 100ms both; }
  .js .hero-mech.is-in .mech-offcut { animation: mech-cut 480ms var(--ease-fit) 400ms both; }
  .js .hero-mech.is-in .mech-trace { animation: mech-draw 500ms var(--ease-fit) 300ms both; }
  .js .hero-mech.is-in .mech-proof { animation: mech-fade 180ms var(--ease-fit) 1050ms both; }
  .js .hero-mech.is-in .mech-scan { animation: mech-scan 650ms linear 150ms both; }
  /* Opening a group (owner 2026-08-04, simplified from the develop
     variant: card blur read as too much): the panel height eases
     open — ::details-content + interpolate-size, Chromium-only,
     instant toggle elsewhere — and the cards are simply revealed by
     the expansion. Closing collapses on the same curve, no reverse
     choreography (exclusive accordion: quiet exits). */
  .exp-group::details-content {
    height: 0;
    overflow: clip;
    transition: height 300ms var(--ease-fit), content-visibility 300ms allow-discrete;
  }
  .exp-group[open]::details-content { height: auto; }
  /* Explorer: ordered lines march, the scan sweeps once per flip,
     the result chip pops in. */
  .js .exp-card:not(.is-today) .l-order line { animation: ws-run 1.1s linear infinite; }
  .js .exp-card:not(.is-today) .exp-scan { animation: mech-scan 500ms linear both; }
  .js .exp-card:not(.is-today) .exp-node.is-result { animation: exp-pop 340ms var(--ease-fit) 300ms both; }
  /* Sol scene, choreographed per open (owner-picked combo 2026-08-02):
     1. sun rises (0-0.3s)   2. dark disc passes over it (0.2-1.3s)
     3. corona flashes at totality, theater fades (1.3-1.6s)
     4. mark arrives, orbits fade in, sparks feed Sol continuously. */
  .sol-dialog[open] .sol-sun { animation: sol-sunrise 1.6s var(--ease-fit) both; }
  .sol-dialog[open] .sol-moon { animation: sol-moonpass 1.6s var(--ease-fit) both; }
  .sol-dialog[open] .sol-corona {
    animation: sol-corona-in 1.6s var(--ease-fit) both,
               sol-breathe 5.5s var(--ease-fit) 1.6s infinite alternate;
  }
  .sol-dialog[open] .sol-d-mark {
    animation: sol-arrive 420ms var(--ease-fit) 1.4s both,
               sol-heartbeat 2.6s ease-in-out 2.6s infinite;
  }
  .sol-dialog[open] .sol-orbit-1 {
    animation: mech-fade 400ms var(--ease-fit) 1.5s both,
               sol-spin 48s linear infinite;
  }
  .sol-dialog[open] .sol-orbit-2 {
    animation: mech-fade 400ms var(--ease-fit) 1.65s both,
               sol-spin 76s linear infinite reverse;
  }
  /* forwards, NOT both: a backwards fill would show the ring's 0%
     frame during the 1.95s delay, clashing with the eclipse. */
  /* Wave now crosses the WHOLE panel (owner 2026-08-04): same radius
     speed as before (0.30px/ms -> icon --d delays stay in sync),
     just travelling further and longer. */
  .sol-dialog[open] .sol-wave { animation: sol-discover 1890ms linear 1.95s forwards; }
  /* +120ms after the wave front reaches each icon (--d precomputed):
     the ring visibly arrives first, THEN the icon registers — the
     wave reads as the cause (owner 2026-08-04). */
  .sol-dialog[open] .sol-tool { animation: sol-tool-in 340ms var(--ease-fit) calc(var(--d, 2.2s) + 120ms) both; }
  .sol-dialog[open] .sol-tool img { animation: sol-bob var(--bd, 6s) ease-in-out calc(var(--d, 2.2s) + 120ms) infinite alternate; }
  /* Closing: the whole dialog ELEMENT melts out — it owns the opaque
     panel background and border; animating only .sol-d-inner left
     the dark slab at 100% until close() (the hard cut, owner
     2026-08-04). Ease-out: fade front-loaded, long glide to zero.
     JS holds .is-closing for 400ms. */
  .sol-dialog.is-closing { animation: sol-close 380ms cubic-bezier(0.15, 0.5, 0.3, 1) both; }
  .sol-dialog.is-closing::backdrop { animation: sol-backdrop-out 380ms cubic-bezier(0.15, 0.5, 0.3, 1) both; }
  /* Hero gradient breathes; popup atmosphere drifts; flare on hover. */
  .hero::before { animation: hero-breathe 45s var(--ease-fit) infinite alternate; }
  .hero::after { animation: hero-sweep 24s linear infinite 4s; }
  .sol-dialog[open] .sol-d-inner::before { animation: sol-drift 40s ease-in-out infinite alternate; }
  .sol-d-anim:hover .sol-pulse { animation: sol-flare 800ms var(--ease-fit); }
}
/* No scale on the snapshots: geometric offset double-exposed hard
   section boundaries in Chrome (ghost edges, shaky feel). Blur kept
   moderate so its edge feathering stays invisible. */
@keyframes vt-dissolve-out {
  from { opacity: 1; filter: blur(0); }
  to { opacity: 0; filter: blur(4px); }
}
@keyframes vt-dissolve-in {
  from { opacity: 0; filter: blur(4px); }
  to { opacity: 1; filter: blur(0); }
}
@keyframes vt-underlay {
  from { background-color: var(--vt-from-field, var(--c-field)); }
  to { background-color: var(--c-field); }
}
/* Veil tint mirrors the old body-opacity dip: 20% field color. */
@keyframes lang-veil-out {
  from {
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
    background: transparent;
  }
  to {
    -webkit-backdrop-filter: blur(5px);
    backdrop-filter: blur(5px);
    background: color-mix(in srgb, var(--c-field) 20%, transparent);
  }
}
@keyframes lang-veil-in {
  from {
    -webkit-backdrop-filter: blur(5px);
    backdrop-filter: blur(5px);
    background: color-mix(in srgb, var(--c-field) 20%, transparent);
  }
  to {
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
    background: transparent;
  }
}
/* Leading (right) edge travels 0 -> 100vw; the band trails behind. */
@keyframes lang-scan {
  0% { transform: translate3d(-100%, 0, 0); opacity: 0; }
  10% { opacity: 1; }
  85% { opacity: 1; }
  100% { transform: translate3d(calc(100vw - 100%), 0, 0); opacity: 0; }
}
/* Theme-switch bloom: a soft disc of light blooming from the toggle,
   sunrise gold toward light, moonlit violet toward dark (JS sets
   --bloom-c and position). The circle and the warm gold deliberately
   contrast the site's angular grey-violet language for one beat. */
.theme-bloom {
  position: fixed;
  width: 360px;
  height: 360px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 200;
  opacity: 0;
  background: radial-gradient(circle, var(--bloom-c, rgba(159, 139, 208, 0.5)) 0%, transparent 62%);
  transform: translate(-50%, -50%) scale(0.25);
}
@media (prefers-reduced-motion: no-preference) {
  .theme-bloom.is-on { animation: theme-bloom 900ms cubic-bezier(0.3, 0.1, 0.3, 1) forwards; }
}
@keyframes theme-bloom {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.25); }
  35% { opacity: 0.55; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(2.1); }
}
@keyframes hero-sweep {
  0% { transform: translate3d(-40%, 40%, 0); opacity: 0; }
  2% { opacity: 1; }
  16% { opacity: 1; }
  20% { transform: translate3d(40%, -40%, 0); opacity: 0; }
  100% { transform: translate3d(40%, -40%, 0); opacity: 0; }
}
@keyframes sol-discover {
  0% { opacity: 0.55; transform: scale(0.5); }
  40% { opacity: 0.22; }
  100% { opacity: 0; transform: scale(10.8); }
}
@keyframes sol-close {
  to { opacity: 0; transform: scale(0.965); filter: blur(6px); }
}
@keyframes sol-backdrop-out {
  to { background-color: rgba(10, 9, 13, 0); }
}
@keyframes sol-tool-in {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.35); }
  65% { opacity: 1; transform: translate(-50%, -50%) scale(1.18); }
  100% { opacity: 0.9; transform: translate(-50%, -50%) scale(1); }
}
@keyframes sol-bob {
  from { transform: translateY(-2.5px); }
  to { transform: translateY(2.5px); }
}
@keyframes mech-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes sol-arrive {
  from { opacity: 0; transform: scale(0.82) rotate(-10deg); }
  to { opacity: 1; transform: none; }
}
@keyframes sol-breathe {
  from { transform: scale(0.94); opacity: 0.72; }
  to { transform: scale(1.1); opacity: 1; }
}
@keyframes sol-spin {
  to { transform: rotate(360deg); }
}
@keyframes sol-sunrise {
  0% { opacity: 0; transform: scale(0.55); }
  16% { opacity: 1; transform: scale(1); }
  82% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes sol-moonpass {
  0% { opacity: 0; transform: translate(64px, -64px); }
  14% { opacity: 0; transform: translate(64px, -64px); }
  24% { opacity: 1; }
  78% { opacity: 1; transform: translate(9px, -9px); }
  100% { opacity: 0; transform: translate(9px, -9px); }
}
@keyframes sol-corona-in {
  0%, 62% { opacity: 0; transform: scale(0.8); }
  80% { opacity: 1; transform: scale(1.16); }
  100% { opacity: 0.9; transform: scale(1); }
}
@keyframes sol-heartbeat {
  0%, 20%, 100% { transform: none; }
  4% { transform: scale(1.03); }
  8% { transform: none; }
  13% { transform: scale(1.045); }
}
@keyframes hero-breathe {
  from { transform: translate(0, 0) scale(1); }
  to { transform: translate(2.5%, 3%) scale(1.07); }
}
@keyframes sol-drift {
  from { transform: translate(0, 0) rotate(0deg); }
  to { transform: translate(4%, -3%) rotate(2deg); }
}
@keyframes sol-flare {
  from { opacity: 0.65; transform: scale(0.72); }
  to { opacity: 0; transform: scale(1.6); }
}
@keyframes exp-pop {
  0% { opacity: 0; transform: translateY(-50%) scale(0.7); }
  70% { opacity: 1; transform: translateY(-50%) scale(1.07); }
  100% { opacity: 1; transform: translateY(-50%) scale(1); }
}
@keyframes mech-scan {
  0% { opacity: 0; transform: translateX(0); }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { opacity: 0; transform: translateX(480px); }
}
@keyframes mech-mass-in {
  from { opacity: 0; transform: translateX(-22px); }
  to { opacity: 1; transform: none; }
}
@keyframes mech-cut {
  from { opacity: 1; transform: translate(-36px, -36px); }
  to { opacity: 1; transform: none; }
}
@keyframes mech-draw {
  from { opacity: 1; stroke-dashoffset: 100; }
  to { opacity: 1; stroke-dashoffset: 0; }
}
@keyframes ws-run {
  to { stroke-dashoffset: -20; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* ---------- Responsive ---------- */
@media (max-width: 1023px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-mech { min-height: 300px; }
  .about-grid { grid-template-columns: 1fr; }
  .exp-cards { grid-template-columns: 1fr; }
  .row { grid-template-columns: 1fr; gap: 8px; }
  .row-body { grid-column: auto; }
  .step { grid-template-columns: 1fr; gap: 10px; }
}

@media (max-width: 767px) {
  /* Wrapped rows must not redistribute the container min-height:
     first row keeps its height via the lockup, not the container. */
  .head-row { flex-wrap: wrap; padding-block: 8px; gap: 10px; min-height: 0; }
  .lockup { min-height: 56px; }
  /* SOL band: big mark out, inline glyph in (owner 2026-08-03). */
  .sol-band > .sol-mark { display: none; }
  .sol-mark-inline { display: inline-block; }
  /* Kicker fits one line on phones (owner 2026-08-03): slightly
     smaller mono, tighter tracking, shorter dash. */
  .kicker { font-size: 0.625rem; letter-spacing: 0.07em; gap: 8px; }
  .kicker::before { width: 16px; }
  /* Narrow: smaller node chips, steeper vertical / flatter horizontal
     scatter (three full-width labels cannot share one 320px line). */
  .exp-node { font-size: 0.6875rem; padding: 5px 8px; gap: 6px; }
  .exp-node img { width: 13px; height: 13px; }
  .exp-card.is-today .exp-node {
    transform: translate(-50%, -50%)
               translate(calc(var(--dx, 0px) * 0.6), calc(var(--dy, 0px) * 0.2))
               rotate(calc(var(--r, 0deg) * 0.5));
  }
  .exp-stage { height: 210px; }
  .exp-summary { grid-template-columns: minmax(0, 1fr) auto; }
  .lockup { gap: 8px; }
  .lockup-word { font-size: 1.15rem; }
  .lockup .mark { width: 19px; height: 19px; }
  .site-nav { order: 5; flex-basis: 100%; margin-left: 0; }
  .site-nav ul { flex-direction: column; gap: 0; border-top: 1px solid var(--c-line); }
  .site-nav a { display: block; padding: 13px 2px; font-size: 1.05rem; }
  .head-tools { margin-left: auto; gap: 8px; }
  .lang { font-size: 0.6875rem; }
  .lang a { padding: 14px 5px; }
  .lang-switch a { padding: 14px 0; }
  .lang-switch a > span:not(.lang-sep) { padding: 0 5px; }
  .btn-head { padding: 0 12px; font-size: 0.6875rem; }
  .menu-toggle { padding: 0; justify-content: center; }
  .menu-toggle-label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* JS enhances the stacked nav into a disclosure menu. The panel
     settles open: height animates via grid rows (auto-height safe),
     the list fades in with a short -6px drop. Visibility gating keeps
     closed links out of the tab order. */
  .js .menu-toggle { display: inline-flex; }
  .js .site-nav {
    display: grid;
    grid-template-rows: 0fr;
    visibility: hidden;
    transition: grid-template-rows 180ms var(--ease-fit),
                visibility 0s 180ms;
  }
  .site-nav ul { flex-wrap: nowrap; } /* column + inherited wrap = phantom second column while height animates */
  .js .site-nav ul {
    overflow: hidden;
    min-height: 0;
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 150ms var(--ease-fit),
                transform 180ms var(--ease-fit);
  }
  .js .site-nav.is-open {
    grid-template-rows: 1fr;
    visibility: visible;
    transition: grid-template-rows 180ms var(--ease-fit), visibility 0s;
  }
  .js .site-nav.is-open ul { opacity: 1; transform: none; }

  .hero-mech { min-height: 240px; }
  .foot-grid { grid-template-columns: 1fr; align-items: start; }
}

@media (max-width: 419px) {
  .head-row { gap: 6px; }
  .btn-head { display: none; }
}

/* SOL dialog on narrow screens: smaller theater, scene drops below
   the close button, the innermost tools sit out (.sol-tool-inner,
   set in the template from their coordinates). */
@media (max-width: 559px) {
  .sol-d-anim { height: 200px; margin-top: 30px; }
  .sol-orbit-1 { width: 128px; height: 128px; --r: 64px; }
  .sol-orbit-2 { width: 172px; height: 172px; --r: 86px; }
  .sol-corona { width: 118px; height: 118px; }
  .sol-d-mark, .sol-sun, .sol-moon { width: 66px; height: 66px; }
  .sol-wave, .sol-pulse { width: 90px; height: 90px; }
  .sol-tool img { width: 12px; height: 12px; }
  .sol-tool-inner { display: none; }
}
