/* ------------------------------------------------------------------
   salman.build — system stylesheet
   Warm stone page, one green, thin rules, buttons that press.
   Tokens at the top. Everything else reads from them.
   Page-specific styles live next to this file: home.css, projects.css,
   about.css, cv.css. This file is the system.
   ------------------------------------------------------------------ */

/* 0. Fonts (self-hosted, latin subsets) ---------------------------- */

@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/bricolage-grotesque.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plex-mono-400.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/plex-mono-500.woff2") format("woff2");
}

/* 1. Tokens ------------------------------------------------------- */

/* Registered so it can be transitioned (the chrome pour on the home page). */
@property --pour {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

:root {
  --page: #f3f1ea;
  --surface: #e9e6dc;
  --surface-2: #dfdccf;
  --ink: #171a17;
  --ink-2: #474c47;
  --ink-3: #74796f;
  --rule: #cfcbbf;
  --rule-soft: #dedbd0;
  --green: #2d5a3d;
  --green-deep: #24492f;
  --green-ink: #f3f1ea;      /* text on green */
  --moss: #5e7f5a;
  --sage: #a8b89a;
  --sage-tint: rgba(168, 184, 154, 0.22);

  /* Duotone: shadows go to one colour, highlights to the other. */
  --duo-shadow: #2d5a3d;
  --duo-light: #f3f1ea;

  /* Chrome ramp. A hard horizon in the middle, sky above, ground below. */
  --chrome-sky-1: #fbfaf6;
  --chrome-sky-2: #cdcabf;
  --chrome-sky-3: #85837a;
  --chrome-horizon: #1c1e1a;
  --chrome-glint: #f4f2ec;
  --chrome-ground-1: #6e6c64;
  --chrome-ground-2: #c9c6bb;
  --chrome-ground-3: #4c4a43;
  --chrome-depth: #2c2e29;
  --chrome-depth-2: #171916;
  --chrome-hi: rgba(255, 255, 255, 1);
  --chrome-stroke: rgba(23, 26, 23, 0.7);
  --chrome-bevel: rgba(255, 255, 255, 0.9);

  --grain-opacity: 0.15;
  --grain-blend: multiply;

  --font-sans: "Bricolage Grotesque", "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  --radius: 6px;
  --radius-sm: 4px;
  --gutter: clamp(16px, 4vw, 48px);
  --measure: 60ch;
  --max: 1200px;

  /* Press physics. In: instant. Out: a hint of spring. */
  --press-in: 55ms cubic-bezier(0.2, 0, 0, 1);
  --press-out: 260ms cubic-bezier(0.2, 1.45, 0.4, 1);
  --lift: 180ms cubic-bezier(0.22, 1, 0.36, 1);
  --open: 360ms cubic-bezier(0.22, 1, 0.36, 1);

  color-scheme: light;
}

:root[data-theme="dark"] {
  --page: #141714;
  --surface: #1c201c;
  --surface-2: #242924;
  --ink: #ece9e0;
  --ink-2: #b9b7ad;
  --ink-3: #848a82;
  --rule: #2a2f2a;
  --rule-soft: #232823;
  --green: #7fa783;
  --green-deep: #6a9270;
  --green-ink: #0f130f;
  --moss: #5e7f5a;
  --sage: #a8b89a;
  --sage-tint: rgba(168, 184, 154, 0.12);

  --duo-shadow: #0f140f;
  --duo-light: #b3c2a6;

  --chrome-sky-1: #ffffff;
  --chrome-sky-2: #dedbd1;
  --chrome-sky-3: #a3a198;
  --chrome-horizon: #23251f;
  --chrome-glint: #f6f4ee;
  --chrome-ground-1: #8b8980;
  --chrome-ground-2: #dfdcd2;
  --chrome-ground-3: #6f6d64;
  --chrome-depth: #090b09;
  --chrome-depth-2: #030403;
  --chrome-hi: rgba(255, 255, 255, 1);
  --chrome-stroke: rgba(236, 233, 224, 0.4);
  --chrome-bevel: rgba(255, 255, 255, 0.55);

  --grain-opacity: 0.34;
  --grain-blend: overlay;

  color-scheme: dark;
}

/* 2. Base ---------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
  background: var(--page);
}

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow-x: clip;
}

/* Film grain over the whole site. A real tile, and it moves: eight
   positions a second, like print grain under a projector. */
body::after {
  content: "";
  position: fixed;
  inset: -100%;
  z-index: 1000;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("/assets/img/grain.png");
  background-size: 256px 256px;
  will-change: transform;
  animation: grain-shift 0.9s steps(8) infinite;
}

@keyframes grain-shift {
  0%   { transform: translate(0, 0); }
  12.5%  { transform: translate(-4%, -6%); }
  25%  { transform: translate(6%, 3%); }
  37.5%  { transform: translate(-7%, 8%); }
  50%  { transform: translate(8%, -3%); }
  62.5%  { transform: translate(-2%, 9%); }
  75%  { transform: translate(4%, -8%); }
  87.5%  { transform: translate(-9%, -1%); }
  100% { transform: translate(0, 0); }
}

::selection {
  background: var(--sage);
  color: #171a17;
}

h1, h2, h3, h4 {
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 0 0.5em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.5rem, 6.4vw, 5rem); letter-spacing: -0.03em; }
h2 { font-size: clamp(1.75rem, 3.2vw, 2.375rem); }
h3 { font-size: clamp(1.25rem, 2vw, 1.5rem); }

p {
  margin: 0 0 1em;
  max-width: var(--measure);
  text-wrap: pretty;
}

a {
  color: var(--green);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--green) 45%, transparent);
  transition: text-decoration-color 120ms ease, color 120ms ease;
}

a:hover {
  color: var(--moss);
  text-decoration-color: currentColor;
}

:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

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

[hidden] { display: none !important; }

ul, ol { padding-left: 1.2em; }

strong { font-weight: 600; }

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 0;
}

/* Mono labels: the legends on a mixing desk. */
.label,
.tag,
.meta,
.mono {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 500;
  line-height: 1.5;
}

.mono.lc { text-transform: none; letter-spacing: 0.02em; }

.tag {
  display: inline-block;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 0.2em 0.55em;
  color: var(--ink-2);
  background: var(--page);
  line-height: 1.4;
  font-size: 0.6875rem;
}

/* The one tag that says how it was made: a green hairline, nothing louder. */
.tag--ai {
  border-color: color-mix(in srgb, var(--green) 55%, var(--rule));
  color: var(--green);
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The one green word. Used once, on the home page. */
.the-word { color: var(--green); }

/* Small square: the green indicator used in nav, rocker, timeline. */
.sq {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 1.5px;
  background: var(--green);
  vertical-align: 1px;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 60;
  padding: 8px 12px;
  background: var(--page);
  border: 1px solid var(--ink);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  text-decoration: none;
}

.skip:focus { top: 12px; }

/* Icons: Iconoir, 1.5px stroke, monochrome, never coloured. */
.ic {
  width: 16px;
  height: 16px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: inline-block;
  vertical-align: -0.2em;
}

.ic--sm { width: 14px; height: 14px; }
.ic--md { width: 18px; height: 18px; }
.ic--lg { width: 20px; height: 20px; }

/* The one number with its unit and context. */
.number {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-weight: 500;
  margin: 0 0 6px;
  font-variant-numeric: tabular-nums;
}

.number small {
  display: block;
  font-size: 0.875rem;
  letter-spacing: 0;
  color: var(--ink-3);
  font-weight: 400;
  margin-top: 4px;
  line-height: 1.4;
}

/* 3. Layout -------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  border-top: 1px solid var(--rule);
  padding-block: clamp(52px, 7vw, 100px);
}

.section--tight { padding-block: clamp(36px, 5vw, 64px); }

.section-head {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px 40px;
  margin-bottom: clamp(28px, 4vw, 44px);
}

.section-head .label { margin: 0; }
.section-head h2 { margin: 0; }
.section-head p { margin: 0.5em 0 0; color: var(--ink-2); }

@media (min-width: 760px) {
  .section-head { grid-template-columns: 180px 1fr; align-items: start; }
  .section-head .label { padding-top: 0.55em; }
}

/* A heading row with controls on the right (expand all, etc). */
.head-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px 24px;
}

.head-row > .btn-row { flex: none; }

main { flex: 1; }

/* 4. Header -------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 1002; /* above the grain layer, so the wordmark can be too */
  background: color-mix(in srgb, var(--page) 94%, transparent);
  border-bottom: 1px solid var(--rule);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 60px;
}

.brand {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.brand:hover { color: var(--green); }

.brand .sq { width: 8px; height: 8px; }

.nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

.nav a {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  text-decoration: none;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  line-height: 1;
}

.nav a:hover { color: var(--ink); background: var(--surface); }
.nav a[aria-current="page"] { color: var(--ink); }
.nav a[aria-current="page"]::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: var(--green);
}

.nav .rocker { margin-left: 10px; }

@media (max-width: 560px) {
  .brand-text { display: none; }
  .nav a { padding: 8px 7px; font-size: 0.75rem; gap: 5px; }
}

/* 5. Pressable controls: the signature ---------------------------- */

.btn {
  --raise: 3px;
  --btn-bg: var(--surface);
  --btn-fg: var(--ink);
  --btn-edge: var(--ink);
  --dx: 0px;
  --dy: 0px;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 46px;
  padding: 0 20px;
  border: 1px solid var(--btn-edge);
  border-radius: var(--radius);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit;
  font-weight: 500;
  font-size: 1rem;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  box-shadow: var(--raise) var(--raise) 0 0 var(--btn-edge);
  transform: translate(var(--dx), var(--dy));
  transition:
    transform var(--press-out),
    box-shadow var(--press-out),
    background-color 120ms ease;
}

/* The label never fades: a link's hover colour must not reach a button. */
a.btn:hover,
a.btn:active,
a.btn:focus {
  color: var(--btn-fg);
}

/* Wind-up: lift a pixel before it can be pressed. */
@media (hover: hover) {
  .btn:hover {
    --dx: -1px;
    --dy: -1px;
    box-shadow: calc(var(--raise) + 1px) calc(var(--raise) + 1px) 0 0 var(--btn-edge);
    transition:
      transform var(--lift),
      box-shadow var(--lift),
      background-color 120ms ease;
  }
}

/* Press: instant. Seats flush into the page. Label stays fully legible. */
.btn:active,
.btn.is-pressed,
.btn[aria-pressed="true"] {
  --dx: var(--raise);
  --dy: var(--raise);
  box-shadow: 0 0 0 0 var(--btn-edge);
  color: var(--btn-fg);
  transition:
    transform var(--press-in),
    box-shadow var(--press-in),
    background-color 120ms ease;
}

.btn:focus-visible { outline-offset: 4px; }

/* A placeholder control: drawn, not yet wired. Sits flush, doesn't press. */
.btn[aria-disabled="true"] {
  --dx: 0px;
  --dy: 0px;
  cursor: default;
  color: var(--ink-3);
  border-color: var(--rule);
  box-shadow: none;
  transform: none;
}

.btn[aria-disabled="true"]:hover { --dx: 0px; --dy: 0px; box-shadow: none; }

/* A placeholder on a green face keeps a legible label. */
.btn--primary[aria-disabled="true"] {
  --btn-bg: color-mix(in srgb, var(--green) 82%, var(--surface));
  color: color-mix(in srgb, var(--green-ink) 82%, transparent);
}

/* Primary: green face, chrome edge. The only chrome outside the wordmark. */
.btn--primary {
  --btn-bg: var(--green);
  --btn-fg: var(--green-ink);
  --btn-edge: var(--ink);
  border-color: transparent;
  background:
    linear-gradient(var(--btn-bg), var(--btn-bg)) padding-box,
    linear-gradient(
      165deg,
      var(--chrome-sky-1) 0%,
      var(--chrome-sky-2) 22%,
      var(--chrome-horizon) 48%,
      var(--chrome-glint) 54%,
      var(--chrome-ground-2) 78%,
      var(--chrome-ground-3) 100%
    ) border-box;
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.16),
    var(--raise) var(--raise) 0 0 var(--btn-edge);
}

:root[data-theme="dark"] .btn--primary {
  --btn-edge: #050605;
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.28),
    var(--raise) var(--raise) 0 0 var(--btn-edge);
}

@media (hover: hover) {
  .btn--primary:hover {
    --btn-bg: var(--green-deep);
    box-shadow:
      inset 0 1px 0 0 rgba(255, 255, 255, 0.16),
      calc(var(--raise) + 1px) calc(var(--raise) + 1px) 0 0 var(--btn-edge);
  }
  :root[data-theme="dark"] .btn--primary:hover {
    --btn-bg: color-mix(in srgb, var(--green) 90%, white);
    box-shadow:
      inset 0 1px 0 0 rgba(255, 255, 255, 0.28),
      calc(var(--raise) + 1px) calc(var(--raise) + 1px) 0 0 var(--btn-edge);
  }
}

.btn--primary:active,
.btn--primary.is-pressed {
  --btn-bg: var(--green);
  --btn-fg: var(--green-ink);
  box-shadow:
    inset 0 1px 0 0 rgba(0, 0, 0, 0.18),
    0 0 0 0 var(--btn-edge);
}

:root[data-theme="dark"] .btn--primary:active,
:root[data-theme="dark"] .btn--primary.is-pressed {
  --btn-bg: var(--green-deep);
  box-shadow:
    inset 0 1px 0 0 rgba(0, 0, 0, 0.18),
    0 0 0 0 var(--btn-edge);
}

.btn--lg {
  min-height: 54px;
  padding: 0 26px;
  font-size: 1.0625rem;
}

.btn--sm {
  min-height: 36px;
  padding: 0 12px;
  font-size: 0.875rem;
  --raise: 2px;
}

.btn--mono {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The key: a thick, domed key-cap for the one CTA that matters. Same
   physics as every button, twice the travel, a top strip and a bottom
   shade so the face reads as a real cap, and an LED that lights when a
   pulse arrives or a hand hovers. */
.btn--key {
  --raise: 6px;
  min-height: 66px;
  padding: 0 34px 0 30px;
  gap: 16px;
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  border-width: 2px;
  border-radius: 8px;
  box-shadow:
    inset 0 2px 0 0 rgba(255, 255, 255, 0.26),
    inset 0 -4px 0 0 rgba(0, 0, 0, 0.24),
    var(--raise) var(--raise) 0 0 var(--btn-edge);
}

.btn--key .ic { width: 20px; height: 20px; }

@media (hover: hover) {
  .btn--key:hover {
    --dx: -2px;
    --dy: -2px;
    box-shadow:
      inset 0 2px 0 0 rgba(255, 255, 255, 0.3),
      inset 0 -4px 0 0 rgba(0, 0, 0, 0.24),
      calc(var(--raise) + 2px) calc(var(--raise) + 2px) 0 0 var(--btn-edge);
  }
}

.btn--key:active,
.btn--key.is-pressed {
  --dx: var(--raise);
  --dy: var(--raise);
  box-shadow:
    inset 0 3px 0 0 rgba(0, 0, 0, 0.26),
    inset 0 -1px 0 0 rgba(255, 255, 255, 0.12),
    0 0 0 0 var(--btn-edge);
}

:root[data-theme="dark"] .btn--key,
:root[data-theme="dark"] .btn--key:hover {
  box-shadow:
    inset 0 2px 0 0 rgba(255, 255, 255, 0.38),
    inset 0 -4px 0 0 rgba(0, 0, 0, 0.3),
    var(--raise) var(--raise) 0 0 var(--btn-edge);
}

:root[data-theme="dark"] .btn--key:active,
:root[data-theme="dark"] .btn--key.is-pressed {
  box-shadow:
    inset 0 3px 0 0 rgba(0, 0, 0, 0.3),
    inset 0 -1px 0 0 rgba(255, 255, 255, 0.12),
    0 0 0 0 var(--btn-edge);
}

/* Done: the key goes from green to chrome, the face lit like the wordmark,
   then cools back to green. */
.btn--key.is-done {
  --btn-fg: #171a17;
  color: #171a17;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
  background:
    linear-gradient(
      176deg,
      var(--chrome-sky-1) 0%,
      var(--chrome-sky-2) 34%,
      var(--chrome-sky-3) 60%,
      var(--chrome-ground-1) 66%,
      var(--chrome-glint) 71%,
      var(--chrome-ground-2) 86%,
      var(--chrome-ground-3) 100%
    ) padding-box,
    linear-gradient(165deg, var(--chrome-ground-3), var(--chrome-sky-1) 50%, var(--chrome-ground-3)) border-box;
  transition:
    transform var(--press-out),
    box-shadow var(--press-out),
    background 500ms ease,
    color 300ms ease;
}

.btn--key.is-done .led { color: var(--green); opacity: 1; box-shadow: 0 0 8px 1px var(--green); }

/* Cooling back: a slow fade of the chrome. */
.btn--key.is-cooling {
  transition:
    transform var(--press-out),
    box-shadow var(--press-out),
    background 1400ms ease,
    color 900ms ease;
}

.led {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 1.5px;
  background: currentColor;
  opacity: 0.45;
  flex: none;
  transition: opacity 160ms ease, box-shadow 160ms ease;
}

.btn:hover .led,
.btn.is-lit .led,
.btn.is-pressed .led,
.btn:active .led {
  opacity: 1;
  box-shadow: 0 0 8px 1px currentColor;
}

/* Icon-only button: square, the glyph centred. Always give it an aria-label. */
.btn--icon {
  width: 46px;
  padding: 0;
}

.btn--icon.btn--sm { width: 36px; }
.btn--icon .ic { width: 18px; height: 18px; }

.btn .arrow {
  display: inline-block;
  transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.btn .ic { transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1); }

@media (hover: hover) {
  .btn:hover .arrow { transform: translateX(3px); }
  .btn:hover .ic--go { transform: translateX(3px); }
  .btn:hover .ic--out { transform: translate(2px, -2px); }
  .btn:hover .ic--down { transform: translateY(2px); }
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 14px;
  align-items: center;
}

/* A quiet text link that sits beside a button: the email as a door. */
.quiet-link {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  text-decoration-color: color-mix(in srgb, var(--ink-2) 40%, transparent);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 2px;
}

.quiet-link:hover { color: var(--green); }

/* Rocker: a raised control with two or more cells; the "on" cell sits lower.
   Used for light/dark (sun / moon) and for EN/DE/FR on the CV page. */
.rocker {
  --raise: 2px;
  --dx: 0px;
  --dy: 0px;
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  background: var(--page);
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  box-shadow: var(--raise) var(--raise) 0 0 var(--ink);
  transform: translate(var(--dx), var(--dy));
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: transform var(--press-out), box-shadow var(--press-out);
}

@media (hover: hover) {
  .rocker:hover {
    --dx: -1px;
    --dy: -1px;
    box-shadow: calc(var(--raise) + 1px) calc(var(--raise) + 1px) 0 0 var(--ink);
    transition: transform var(--lift), box-shadow var(--lift);
  }
}

.rocker:active,
.rocker.is-pressed {
  --dx: var(--raise);
  --dy: var(--raise);
  box-shadow: 0 0 0 0 var(--ink);
  transition: transform var(--press-in), box-shadow var(--press-in);
}

/* A cell. Every cell has exactly the same box, on or off; only the
   fill, the inset and the LED change, so nothing shifts. */
.rocker > * {
  appearance: none;
  border: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 30px;
  min-width: 38px;
  padding: 0 10px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--ink-3);
  background: var(--page);
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

.rocker > * + * { border-left: 1px solid var(--rule); }

.rocker .ic { width: 14px; height: 14px; transition: transform 140ms ease; }

/* The LED: a small square that lights on the seated cell. */
.rocker > *::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 1px;
  flex: none;
  background: transparent;
  border: 1px solid var(--rule);
  transition: background-color 140ms ease, border-color 140ms ease;
}

.rocker > [data-on="true"],
.rocker > [aria-checked="true"] {
  color: var(--ink);
  background: var(--surface);
  box-shadow: inset 0 2px 0 0 var(--rule), inset 0 -1px 0 0 rgba(255, 255, 255, 0.35);
}

.rocker > [data-on="true"] > *,
.rocker > [aria-checked="true"] > * { transform: translateY(1px); }

.rocker > [data-on="true"]::before,
.rocker > [aria-checked="true"]::before {
  background: var(--green);
  border-color: var(--green);
  transform: translateY(1px);
}

:root[data-theme="dark"] .rocker > [data-on="true"],
:root[data-theme="dark"] .rocker > [aria-checked="true"] {
  box-shadow: inset 0 2px 0 0 #0d100d, inset 0 -1px 0 0 rgba(255, 255, 255, 0.06);
}

/* The theme rocker: icon only, no LED needed beside a glyph. */
.rocker--theme > *::before { display: none; }
.rocker--theme > * { min-width: 36px; padding: 0 9px; }

/* 6. Frames, bezels, duotone -------------------------------------- */

/* Every image sits in a thin frame. */
.frame {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 6px;
  overflow: hidden;
}

.frame-inner {
  position: relative;
  overflow: hidden;
  border-radius: calc(var(--radius) - 3px);
  background: var(--surface-2);
}

.frame img,
.frame video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Screenshot bezel: a consistent stone surround so mixed sources read as one set. */
.bezel {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 10px;
}

.bezel .bezel-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 0 2px 8px;
}

.bezel .bezel-bar .sq { background: var(--rule); width: 6px; height: 6px; }
.bezel .bezel-bar .sq + .sq { margin-left: 4px; }

.bezel .screen {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) - 2px);
  background: var(--page);
  aspect-ratio: 16 / 10;
}

.bezel .screen img,
.bezel .screen video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top left;
}

.bezel .bezel-cap {
  padding: 8px 2px 0;
  display: flex;
  justify-content: space-between;
  gap: 12px;
}

/* Thumbnail of a tool: a small bezel, same ratio as the big one, so a
   list of very different tools reads as one set. */
.thumb {
  display: block;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 4px;
  width: 100%;
  max-width: 132px;
}

.thumb .screen {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) - 3px);
  background: var(--page);
  aspect-ratio: 16 / 10;
}

.thumb .screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top left;
}

/* Darkroom duotone. The image is greyscale; shadows go to --duo-shadow,
   highlights to --duo-light. Both modes get their own pair of tokens. */
.duotone img,
.duotone video {
  filter: grayscale(1) contrast(1.08);
}

.duotone .frame-inner::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--duo-shadow);
  mix-blend-mode: screen;
}

.duotone .frame-inner::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background: var(--duo-light);
  mix-blend-mode: multiply;
}

/* 7. Grain gradient canvas (hero, closing band) -------------------- */

.grain-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.grain-field canvas {
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 900ms ease;
}

.grain-field.is-live canvas { opacity: 1; }

/* A still fallback in the same palette when WebGL is off. */
.grain-field::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 70% at 78% 30%, var(--sage-tint), transparent 70%),
    radial-gradient(50% 60% at 20% 90%, color-mix(in srgb, var(--green) 10%, transparent), transparent 70%);
}

.grain-field.is-live::before { opacity: 0; }

/* 8. Chrome wordmark ----------------------------------------------- */
/* Lit metal, not a gradient. The letters are SVG text with a lighting
   filter: a blurred alpha becomes a bevelled surface, a point light shades
   it and throws a specular highlight across the bevels and the face. The
   light IS the pointer: move it and the reflections move. Behind the face,
   six dark copies make the thickness, leaning away from the light. It sits
   above the grain layer so the metal stays clean. */

.wm { position: relative; z-index: 1001; }

.chrome-svg {
  --ex: 1;      /* extrusion direction, user units per step */
  --ey: 1;
  --rx: 0deg;   /* tilt */
  --ry: 0deg;
  --px: 0px;    /* press */
  --py: 0px;
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  transform: perspective(1100px) rotateX(var(--rx)) rotateY(var(--ry)) translate(var(--px), var(--py));
  transform-origin: 45% 60%;
  will-change: transform;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.chrome-svg:focus-visible { outline: 2px solid var(--green); outline-offset: 10px; border-radius: 4px; }

.chrome-svg text {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 176px;
  letter-spacing: -0.035em;
  font-optical-sizing: auto;
}

.chrome-svg .wm-face text {
  fill: url(#wm-ramp);
  stroke: var(--chrome-stroke);
  stroke-width: 1.2;
  paint-order: stroke fill;
}

/* The thickness: five solid copies, stepped behind the face, leaning away
   from the light. Plain fills, no filter, so they cost nothing. */
.chrome-svg .wm-ext { fill: var(--chrome-depth-2); }
.chrome-svg .wm-ext:nth-child(1) { transform: translate(calc(var(--ex) * 1.6px), calc(var(--ey) * 1.6px)); fill: var(--chrome-depth); }
.chrome-svg .wm-ext:nth-child(2) { transform: translate(calc(var(--ex) * 3.2px), calc(var(--ey) * 3.2px)); fill: var(--chrome-depth); }
.chrome-svg .wm-ext:nth-child(3) { transform: translate(calc(var(--ex) * 4.8px), calc(var(--ey) * 4.8px)); }
.chrome-svg .wm-ext:nth-child(4) { transform: translate(calc(var(--ex) * 6.4px), calc(var(--ey) * 6.4px)); }
.chrome-svg .wm-ext:nth-child(5) { transform: translate(calc(var(--ex) * 8px), calc(var(--ey) * 8px)); }

.chrome-svg .s1 { stop-color: var(--chrome-sky-1); }
.chrome-svg .s2 { stop-color: var(--chrome-sky-2); }
.chrome-svg .s3 { stop-color: var(--chrome-sky-3); }
.chrome-svg .s4 { stop-color: var(--chrome-horizon); }
.chrome-svg .s5 { stop-color: var(--chrome-glint); }
.chrome-svg .s6 { stop-color: var(--chrome-ground-1); }
.chrome-svg .s7 { stop-color: var(--chrome-ground-2); }
.chrome-svg .s8 { stop-color: var(--chrome-ground-3); }

/* It lands: a small settle from above on load. */
.chrome-svg.chrome-in {
  animation: chrome-in 900ms cubic-bezier(0.2, 1.3, 0.4, 1) both;
}

@keyframes chrome-in {
  from { opacity: 0; transform: perspective(1100px) rotateX(24deg) translateY(-16px); }
  to   { opacity: 1; transform: perspective(1100px) rotateX(var(--rx)) rotateY(var(--ry)) translate(var(--px), var(--py)); }
}

/* The status legend by the flow port: what the pipeline is doing. */
.flow-status {
  position: absolute;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  pointer-events: none;
  color: var(--ink-3);
  opacity: 0;
  transition: opacity 400ms ease, color 200ms ease;
}

.flow-status::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 1px;
  background: var(--rule);
  border: 1px solid var(--ink-3);
  transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

.hero.flow-in .flow-status { opacity: 1; }
.flow-status[data-state="running"] { color: var(--green); }
.flow-status[data-state="running"]::before,
.flow-status[data-state="done"]::before { background: var(--green); border-color: var(--green); }
.flow-status[data-state="running"]::before { box-shadow: 0 0 6px var(--green); }
.flow-status[data-state="done"] { color: var(--ink); }

/* The line only moves while the pipeline is running. */
.hero-flow .flow-line { animation-play-state: paused; }
.hero.is-running .hero-flow .flow-line { animation-play-state: running; }

/* 9. Diagrams: hairline boxes, mono labels, green flow lines --------- */

.diagram {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 6px;
}

.diagram svg {
  width: 100%;
  height: auto;
  border-radius: calc(var(--radius) - 3px);
  background: var(--page);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.diagram .box {
  fill: var(--page);
  stroke: var(--ink-2);
  stroke-width: 1;
}

.diagram .box--in { stroke: var(--ink-3); stroke-dasharray: 3 3; }
.diagram .box--out { stroke: var(--green); }
.diagram text { fill: var(--ink); }
.diagram .sub { fill: var(--ink-3); font-size: 9.5px; }

.diagram .flow,
.flow-line {
  fill: none;
  stroke: var(--green);
  stroke-width: 1.25;
  stroke-dasharray: 5 4;
  animation: flow 1.6s linear infinite;
}

.diagram .flow-head,
.flow-head { fill: var(--green); }

.diagram .flow--branch,
.flow-line--branch { stroke-dasharray: 2 3; }

/* Ports and nodes on a page-level flow (hero, about spine). */
.flow-port { fill: var(--page); stroke: var(--green); stroke-width: 1; }
.flow-node { fill: var(--green); }

@keyframes flow {
  to { stroke-dashoffset: -18; }
}

/* 10. Expanders: <details> that unfold ------------------------------ */

/* The expand control: a real pressable button drawn inside summary. */
.open-btn {
  --raise: 2px;
  min-height: 38px;
  padding: 0 12px;
  gap: 8px;
}

.open-btn .plus {
  display: inline-block;
  width: 10px;
  height: 10px;
  position: relative;
}

.open-btn .plus::before,
.open-btn .plus::after {
  content: "";
  position: absolute;
  background: currentColor;
  left: 0;
  top: 4px;
  width: 10px;
  height: 2px;
  transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.open-btn .plus::after { transform: rotate(90deg); }
[open] > summary .open-btn .plus::after { transform: rotate(0deg); }

/* While open, the control sits pressed in. */
[open] > summary .open-btn {
  --dx: var(--raise);
  --dy: var(--raise);
  box-shadow: 0 0 0 0 var(--btn-edge);
}

@media (hover: hover) {
  summary:hover .open-btn {
    --dx: -1px;
    --dy: -1px;
    box-shadow: calc(var(--raise) + 1px) calc(var(--raise) + 1px) 0 0 var(--btn-edge);
  }
  [open] > summary:hover .open-btn {
    --dx: var(--raise);
    --dy: var(--raise);
    box-shadow: 0 0 0 0 var(--btn-edge);
  }
}

summary:active .open-btn {
  --dx: var(--raise);
  --dy: var(--raise);
  box-shadow: 0 0 0 0 var(--btn-edge);
  transition: transform var(--press-in), box-shadow var(--press-in);
}

summary { list-style: none; }
summary::-webkit-details-marker { display: none; }

/* The opening: the body visibly unfolds rather than appearing. */
.unfold {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--open);
}

.unfold.is-closed { grid-template-rows: 0fr; }

/* The box opens first; the words arrive a beat later. */
.unfold > .unfold-inner {
  min-height: 0;
  overflow: hidden;
  transition: opacity 240ms ease 90ms, transform var(--open);
}

.unfold.is-closed > .unfold-inner {
  opacity: 0;
  transform: translateY(-6px);
  transition-delay: 0ms;
}

/* Expand all / collapse all: one toggle, label swaps with the state. */
[data-expand-all] .when-closed,
[data-expand-all] .when-open { display: inline-flex; align-items: center; gap: 8px; }
[data-expand-all] .when-open { display: none; }
[data-expand-all][aria-pressed="true"] .when-open { display: inline-flex; }
[data-expand-all][aria-pressed="true"] .when-closed { display: none; }

/* 11. Page head (inner pages) -------------------------------------- */

.page-head {
  position: relative;
  padding-block: clamp(36px, 5vw, 64px) clamp(24px, 4vw, 40px);
}

.page-head .kicker { margin: 0 0 14px; }
.page-head h1 { font-size: clamp(2.25rem, 5vw, 3.75rem); margin-bottom: 0.3em; }
.page-head .lede {
  font-size: clamp(1.125rem, 1.8vw, 1.375rem);
  line-height: 1.4;
  color: var(--ink-2);
  max-width: 46ch;
  margin-bottom: 0;
}

.page-head .btn-row { margin-top: 24px; }

/* 12. Footer ------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--rule);
  position: relative;
}

.site-footer .close {
  position: relative;
  padding-block: clamp(48px, 7vw, 96px);
}

.site-footer .close > .grain-field {
  -webkit-mask-image: linear-gradient(90deg, transparent 30%, rgba(0,0,0,0.6) 60%, #000 85%);
  mask-image: linear-gradient(90deg, transparent 30%, rgba(0,0,0,0.6) 60%, #000 85%);
}

@media (max-width: 879px) {
  .site-footer .close > .grain-field {
    -webkit-mask-image: linear-gradient(180deg, transparent 30%, #000 100%);
    mask-image: linear-gradient(180deg, transparent 30%, #000 100%);
  }
}

.site-footer .close .wrap {
  position: relative;
  z-index: 1;
}

.site-footer .close h2 {
  font-size: clamp(1.75rem, 3.6vw, 2.75rem);
  margin-bottom: 18px;
  max-width: 18ch;
}

.site-footer .close .email {
  font-size: clamp(1.25rem, 2.2vw, 1.625rem);
  font-weight: 500;
  margin: 0 0 8px;
  letter-spacing: -0.01em;
}

.site-footer .close .email a {
  color: var(--ink);
  text-decoration-color: color-mix(in srgb, var(--green) 55%, transparent);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.site-footer .close .email a:hover { color: var(--green); }
.site-footer .close .email .ic { width: 0.9em; height: 0.9em; }

.site-footer .close .line { margin: 0; }

.site-footer .colophon {
  border-top: 1px solid var(--rule);
  padding-block: 18px 26px;
}

.site-footer .colophon .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 24px;
}

.social {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  gap: 6px;
}

.social a {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  text-decoration: none;
}

.social a:hover { color: var(--ink); background: var(--surface); }
.social .ic { width: 18px; height: 18px; }

/* 13. Motion ------------------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 480ms cubic-bezier(0.22, 1, 0.36, 1), transform 480ms cubic-bezier(0.22, 1, 0.36, 1);
}

[data-reveal].in {
  opacity: 1;
  transform: none;
}

@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 200ms;
  animation-timing-function: ease;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  body::after { animation: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .btn, .rocker, .rocker > *, .btn .arrow, .btn .ic, .open-btn .plus::before, .open-btn .plus::after,
  .knob, .unfold, .unfold > .unfold-inner, .grain-field canvas {
    transition: none !important;
  }
  .diagram .flow, .flow-line { animation: none; }
  .chrome-svg, .chrome-svg.chrome-in { animation: none; transform: none; }
  .flow-status { transition: none; }
  .led { transition: none; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* Fallback: no JS means everything is visible and open state is native. */
.no-js [data-reveal] { opacity: 1; transform: none; }
.no-js .grain-field canvas { display: none; }
