/* Urverk — Nordic precision-mechanism. Scandinavian minimalism meets fine
   watchmaking: generous whitespace, a precise modular grid, hairline rules like
   watch-face indices, one brass accent, and one place per page where the
   mechanism comes alive. The eight palette tokens are the only hex literals in
   this file; every other colour is derived via var()/color-mix() so the palette
   guard holds. Light is the committed default; a dark theme is built from the
   same tokens under prefers-color-scheme. */

/* Self-hosted fonts — no third-party request, no render-blocking CDN, and a
   strict font-src 'self' CSP. Only the latin subset is committed (the site is
   English). Space Grotesk is the engineered display face; Inter carries body
   and UI. unicode-range copied from the Google latin @font-face blocks so glyph
   coverage is identical. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/space-grotesk-latin-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../assets/fonts/space-grotesk-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/inter-latin-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/inter-latin-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/inter-latin-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light; /* native controls / scrollbars track the theme (t-020) */
  /* --- the eight palette tokens (the only hex literals) --------------------- */
  --graphite:      #0f1113; /* dark ground / mechanism */
  --graphite-2:    #171a1d; /* raised graphite panel */
  --snow:          #f6f4ef; /* light page ground */
  --paper:         #e7e2d8; /* raised panel on light */
  --ink:           #1c1e22; /* text on snow */
  --brass:         #b98a3e; /* the one accent — indices, rules, the mechanism */
  --brass-hi:      #d8b877; /* brass highlight — jewels, hover */
  --steel:         #8b9198; /* cool grey — labels, secondary text */

  /* --- semantic tokens (light, the committed default) ----------------------- */
  --ground:  var(--snow);
  --surface: var(--paper);
  --fg:      var(--ink);
  --accent:  var(--brass);
  --accent-hi: var(--brass-hi);
  /* Brass as *text* on the page ground: full brass is only 2.8:1 on snow, so
     darken it toward ink to clear WCAG AA (5.6:1). The dark theme restores full
     brass, which already passes on graphite (6:1). Used for accent-coloured copy
     that sits on --ground, e.g. form-field error messages. */
  --accent-text: color-mix(in srgb, var(--brass) 58%, var(--ink));

  --rule:       color-mix(in srgb, var(--ink) 13%, transparent);
  --rule-brass: color-mix(in srgb, var(--brass) 50%, transparent);
  /* 64% ink keeps the muted labels clearly quieter than body copy while
     clearing WCAG AA (4.86:1 on snow); 58% read 4.0:1 and failed. */
  --label:      color-mix(in srgb, var(--ink) 64%, var(--snow));
  --body-soft:  color-mix(in srgb, var(--ink) 90%, var(--snow));

  /* The mechanism grounds — always dark, in both themes. Reassigned a touch
     lighter under dark so they still lift off the graphite page. */
  --movement:   var(--graphite);
  --movement-2: var(--graphite-2);
  --on-dark:    var(--snow);
  --on-dark-soft: color-mix(in srgb, var(--snow) 78%, var(--graphite));
  --on-dark-faint: color-mix(in srgb, var(--snow) 60%, var(--graphite));
  --rule-dark:  color-mix(in srgb, var(--snow) 15%, transparent);

  --font-display: "Space Grotesk", "SF Pro Display", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;

  /* Vertical rhythm and type scale — the whitespace is the design. */
  --measure: 66ch;
  --step-0: 1.0625rem;
  --step-1: clamp(1.15rem, 1.05rem + 0.5vw, 1.35rem);
  --step-2: clamp(1.35rem, 1.2rem + 0.9vw, 1.7rem);
  --step-3: clamp(1.7rem, 1.4rem + 1.6vw, 2.35rem);
  --step-4: clamp(2.3rem, 1.75rem + 2.9vw, 3.5rem);
  --step-5: clamp(2.9rem, 2rem + 4.6vw, 4.6rem);
  --step-6: clamp(3.4rem, 2.3rem + 6vw, 6rem);

  /* --- The movement: one frequency, everything subdivides it ---------------
     A watch runs at a fixed beat; so does this site. --beat is the escapement
     half-period every duration below is a whole multiple of, so every
     transition and animation is in phase — a shared cadence, not thirty
     unrelated milliseconds. Two easings only, both mechanical: decisive
     travel, and an arrival that stops dead. Never an overshoot — this brand
     ticks, it does not wobble. Reduced-motion collapses all of it (see the
     global reduce reset at the foot of the file). */
  --beat: 130ms;
  --t-1: var(--beat);                    /* 130ms  — detents: colour, hairline, border */
  --t-2: calc(var(--beat) * 2);          /* 260ms  — sweeps: nav + link underlines */
  --t-3: calc(var(--beat) * 3);          /* 390ms  — winds: button fill, glyph turn */
  --t-4: calc(var(--beat) * 4);          /* 520ms  — draws: brass index ticks */
  --t-5: calc(var(--beat) * 5);          /* 650ms  — settles: reveals, headings */
  --t-wind: calc(var(--beat) * 7);       /* 910ms  — the wordmark winding up */
  --tick-period: calc(var(--beat) * 8);  /* 1040ms — the escapement: caret + spinner + orbit */

  --ease-mech: cubic-bezier(0.22, 0.85, 0.24, 1);   /* general mechanical travel */
  --ease-settle: cubic-bezier(0.16, 1, 0.3, 1);     /* arrival that stops dead, no overshoot */

  /* --- Hero band + Astrolabium face (theme-adaptive, t-049) ----------------
     The hero is no longer a permanently dark band: in light theme it is a
     DAYLIGHT face on the light page ground (ink hands/indices, brass bezel,
     steel hairlines); in dark theme it stays a graphite NIGHT face. These
     defaults are the DAY values; the two dark blocks below override them to the
     NIGHT values (kept byte-identical between the media query and the attribute
     selector). Only the eight tokens appear as hex; every face colour is a
     var()/color-mix() of them, so the palette guard holds in both themes. */
  --hero-band:        var(--ground);
  --hero-band-fg:     var(--fg);
  --hero-band-soft:   var(--body-soft);
  --hero-band-faint:  var(--label);
  --hero-band-accent: var(--accent-text);
  --hero-band-rule:   var(--rule);

  --face-line:        color-mix(in srgb, var(--ink) 62%, transparent);
  --face-line-soft:   color-mix(in srgb, var(--ink) 30%, transparent);
  --face-brass:       var(--brass);
  --face-brass-soft:  color-mix(in srgb, var(--brass) 42%, transparent);
  --face-hand:        var(--ink);
  --face-hand-edge:   color-mix(in srgb, var(--snow) 85%, var(--paper));
  --face-sun:         var(--brass);
  --face-sun-ring:    var(--ink);
  --face-moon-lit:    color-mix(in srgb, var(--snow) 60%, var(--steel));
  --face-moon-dark:   var(--ink);
  --face-moon-rim:    var(--brass);
  --face-text:        var(--ink);
  --face-window:      var(--ground);
  --face-gear:        color-mix(in srgb, var(--ink) 50%, transparent);
  --face-gear-hi:     color-mix(in srgb, var(--brass) 72%, var(--ink));
  --face-gear-opacity: 0.28;
}

/* Dark theme — rebuilt from the same tokens. The mechanism grounds lift a step
   so the dark sections still separate from the now-graphite page.

   Two entry points, one token set (t-020). The media query paints dark when the
   OS asks for it AND the visitor has not pinned light; the attribute selector
   paints dark when the visitor pins it, whatever the OS says. A media query
   cannot join an attribute selector in one selector list, so the declarations
   are duplicated on purpose — keep the two blocks byte-identical. On <html>,
   data-theme unset means "follow the system", data-theme="light" pins light even
   under an OS dark preference (theme.js), and data-theme="dark" pins dark. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ground:  var(--graphite);
    --surface: var(--graphite-2);
    --fg:      var(--snow);
    --accent-text: var(--brass);  /* full brass reads 6:1 on graphite */
    --rule:       color-mix(in srgb, var(--snow) 14%, transparent);
    --rule-brass: color-mix(in srgb, var(--brass) 55%, transparent);
    --label:      color-mix(in srgb, var(--snow) 62%, var(--graphite));
    --body-soft:  color-mix(in srgb, var(--snow) 90%, var(--graphite));

    --movement:   var(--graphite-2);
    --movement-2: color-mix(in srgb, var(--graphite-2) 82%, var(--snow));
    --on-dark-soft: color-mix(in srgb, var(--snow) 80%, var(--graphite-2));
    --on-dark-faint: color-mix(in srgb, var(--snow) 62%, var(--graphite-2));

    /* NIGHT face (t-049) — keep byte-identical with the [data-theme="dark"] block. */
    --hero-band:        var(--graphite);
    --hero-band-fg:     var(--snow);
    --hero-band-soft:   color-mix(in srgb, var(--snow) 78%, var(--graphite));
    --hero-band-faint:  color-mix(in srgb, var(--snow) 60%, var(--graphite));
    --hero-band-accent: var(--brass-hi);
    --hero-band-rule:   var(--rule-dark);

    --face-line:        color-mix(in srgb, var(--snow) 55%, transparent);
    --face-line-soft:   color-mix(in srgb, var(--steel) 55%, transparent);
    --face-brass:       var(--brass);
    --face-brass-soft:  color-mix(in srgb, var(--brass) 40%, transparent);
    --face-hand:        var(--snow);
    --face-hand-edge:   var(--graphite);
    --face-sun:         var(--brass);
    --face-sun-ring:    var(--brass-hi);
    --face-moon-lit:    color-mix(in srgb, var(--snow) 85%, var(--steel));
    --face-moon-dark:   var(--graphite);
    --face-moon-rim:    color-mix(in srgb, var(--brass) 55%, transparent);
    --face-text:        var(--snow);
    --face-window:      var(--graphite);
    --face-gear:        color-mix(in srgb, var(--brass) 62%, transparent);
    --face-gear-hi:     var(--brass-hi);
    --face-gear-opacity: 0.42;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --ground:  var(--graphite);
  --surface: var(--graphite-2);
  --fg:      var(--snow);
  --accent-text: var(--brass);  /* full brass reads 6:1 on graphite */
  --rule:       color-mix(in srgb, var(--snow) 14%, transparent);
  --rule-brass: color-mix(in srgb, var(--brass) 55%, transparent);
  --label:      color-mix(in srgb, var(--snow) 62%, var(--graphite));
  --body-soft:  color-mix(in srgb, var(--snow) 90%, var(--graphite));

  --movement:   var(--graphite-2);
  --movement-2: color-mix(in srgb, var(--graphite-2) 82%, var(--snow));
  --on-dark-soft: color-mix(in srgb, var(--snow) 80%, var(--graphite-2));
  --on-dark-faint: color-mix(in srgb, var(--snow) 62%, var(--graphite-2));

  /* NIGHT face (t-049) — keep byte-identical with the media-query block above. */
  --hero-band:        var(--graphite);
  --hero-band-fg:     var(--snow);
  --hero-band-soft:   color-mix(in srgb, var(--snow) 78%, var(--graphite));
  --hero-band-faint:  color-mix(in srgb, var(--snow) 60%, var(--graphite));
  --hero-band-accent: var(--brass-hi);
  --hero-band-rule:   var(--rule-dark);

  --face-line:        color-mix(in srgb, var(--snow) 55%, transparent);
  --face-line-soft:   color-mix(in srgb, var(--steel) 55%, transparent);
  --face-brass:       var(--brass);
  --face-brass-soft:  color-mix(in srgb, var(--brass) 40%, transparent);
  --face-hand:        var(--snow);
  --face-hand-edge:   var(--graphite);
  --face-sun:         var(--brass);
  --face-sun-ring:    var(--brass-hi);
  --face-moon-lit:    color-mix(in srgb, var(--snow) 85%, var(--steel));
  --face-moon-dark:   var(--graphite);
  --face-moon-rim:    color-mix(in srgb, var(--brass) 55%, transparent);
  --face-text:        var(--snow);
  --face-window:      var(--graphite);
  --face-gear:        color-mix(in srgb, var(--brass) 62%, transparent);
  --face-gear-hi:     var(--brass-hi);
  --face-gear-opacity: 0.42;
}

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

/* Root font-size ramp (t-095): the whole site is rem/ch-based, so ramping the
   root from 100% at <=1920px to 118% at >=3000px scales the entire composition
   uniformly on wide monitors — the watch cap, every type step, the wrap measures,
   the flap tiles, and the h1's rem clamp ceiling all grow together, so a 4K
   viewport no longer reads as a small centred island. Two constraints keep it
   safe: em/rem media queries resolve against the 16px default (not this rule),
   so no breakpoint shifts as the ramp climbs; and browser zoom shrinks the CSS-px
   viewport, so zooming naturally backs the ramp off. */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  font-size: clamp(100%, calc(68% + 0.26667vw), 118%);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 400;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

main {
  flex: 1 0 auto;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.015em;
  margin: 0;
  text-wrap: balance;
}

p {
  margin: 0 0 1.15em;
  max-width: var(--measure);
}

h2 + p {
  margin-top: 0.75em;
}

a {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--rule-brass);
  transition: border-color var(--t-1) var(--ease-mech), color var(--t-1) var(--ease-mech);
}

a:hover {
  border-bottom-color: var(--accent);
}

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

::selection {
  background: color-mix(in srgb, var(--brass) 30%, transparent);
  color: inherit;
}

/* Underline-growth: a resting hairline that a solid brass rule extends over on
   hover, like a watch hand sweeping to the mark. */
.link-grow {
  position: relative;
  display: inline-block;
  color: var(--fg);
  border-bottom: 1px solid var(--rule-brass);
}

.link-grow::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 1px;
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-2) var(--ease-settle);
}

.link-grow:hover::after,
.link-grow:focus-visible::after {
  transform: scaleX(1);
}

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

/* --- Layout scaffolding ------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: 74rem;
  margin-inline: auto;
  padding-inline: clamp(1.5rem, 5vw, 4rem);
}

.section {
  padding-block: clamp(4rem, 9vw, 7.5rem);
}

/* Eyebrow: a data label, carried by a short brass index tick — the watch-face
   vocabulary that recurs across the site. */
.eyebrow {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-family: var(--font-body);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--label);
  margin: 0 0 1.5rem;
}

.eyebrow::before {
  content: "";
  width: 1.4rem;
  height: 1px;
  background: var(--accent);
  flex: none;
}

.lede {
  font-size: var(--step-2);
  line-height: 1.36;
  max-width: 34ch;
  color: var(--body-soft);
}

/* --- Skip link ---------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: 1rem;
  top: -3.5rem;
  z-index: 20;
  background: var(--movement);
  color: var(--on-dark);
  padding: 0.6rem 1rem;
  border: 0;
  transition: top var(--t-1) var(--ease-mech);
}

.skip-link:focus {
  top: 1rem;
}

/* --- Header / nav ------------------------------------------------------- */

.site-header {
  border-bottom: 1px solid var(--rule);
  background: var(--ground);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1.25rem;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  border: 0;
  color: var(--fg);
}

/* The wordmark mark — a "U" whose counter is an escape-wheel silhouette. The
   escape wheel indexes to its mark once on first load, then rests: the whole
   instrument coming alive, on every page, in phase with the movement beat.
   Gated on html.js-enter (tick.js, loaded in <head> before first paint) so a
   no-JS visitor gets the static mark with nothing hidden. */
.brand-mark {
  width: 1.55rem;
  height: 1.55rem;
  display: block;
  flex: none;
  color: var(--accent);
}

html.js-enter .brand-mark .mark-wheel {
  transform-box: fill-box;
  transform-origin: center;
}

@media (prefers-reduced-motion: no-preference) {
  html.js-enter .brand-mark .mark-wheel {
    animation: mark-tick var(--t-wind) var(--ease-mech) 1 both;
  }
}

@keyframes mark-tick {
  0%   { transform: rotate(-38deg); opacity: 0; }
  60%  { opacity: 1; }
  100% { transform: rotate(0deg); opacity: 1; }
}

.brand .brand-name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.16rem;
  letter-spacing: 0.02em;
}

/* Contact promoted to the top level (t-077): the primary nav is a single
   Contact chip beside the theme toggle — the wordmark is the way home, so the
   Home link went with the list. Styled in the hero CTA's language (mono,
   uppercase, brass rule) so header and hero speak with one voice; fills brass
   on hover/focus like the CTA. WCAG target size via the chip's min-height. */
.site-nav {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  /* The single spacer between the two header clusters (t-096): the flat wrap is
     [menu · brand · motto] on the left, [contact chip · theme toggle] on the
     right, and margin-left:auto pushes this nav (and the toggle after it) hard
     right. Robust when .head-motto hides on mobile — the old brand{margin-right:
     auto} would have shoved the motto right instead of anchoring the right group. */
  margin-left: auto;
}

/* (t-096) the standalone Services nav link was removed from the header — that
   surface now lives in the section menu as "What we build" — so its rule block
   is gone; Contact stays the single emphasized right-cluster action. */
.nav-contact {
  display: inline-flex;
  align-items: center;
  min-height: 2.5rem;
  padding: 0 0.95rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-text);
  border: 1px solid var(--rule-brass);
  border-radius: 4px;
  white-space: nowrap;
  transition: color var(--t-2) var(--ease-mech), background-color var(--t-2) var(--ease-mech),
    border-color var(--t-2) var(--ease-mech);
}

.nav-contact:hover,
.nav-contact:focus-visible {
  background: var(--accent);
  color: var(--graphite);
  border-color: var(--accent);
}

/* Ring clears the brass fill in both themes (mirrors .hero-cta's focus fix). */
.nav-contact:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

/* On the contact page itself the chip reads as location, not a repeat action. */
.nav-contact[aria-current="page"] {
  border-color: var(--accent);
  color: var(--fg);
}

/* --- Header section menu (t-096) ---------------------------------------------
   The section menu is a native <details>/<summary> disclosure so it works with
   JS off — the summary toggles the panel; menu.js only enhances it (close on
   outside click / Escape / link activation). It sits LEFT of the brand (founder
   placement). The summary is styled in the .nav-contact chip language so the
   header speaks with one voice. */
.site-menu {
  position: relative;
  display: flex;
  align-items: center;
}
/* Round icon-button (t-097, supersedes the t-096 MENU text chip): the disclosure
   summary now speaks .theme-toggle's language — a 2.5rem brass-ringed circle
   carrying the three-bar hamburger glyph, its "Menu" label visually hidden. */
.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--rule-brass);
  border-radius: 50%;
  cursor: pointer;
  /* Suppress the default disclosure marker in both engines. */
  list-style: none;
  transition: color var(--t-2) var(--ease-mech), background-color var(--t-2) var(--ease-mech),
    border-color var(--t-2) var(--ease-mech);
}
.menu-toggle::-webkit-details-marker { display: none; }
.menu-toggle:hover,
.menu-toggle:focus-visible {
  border-color: var(--accent);
  color: var(--accent-hi);
}
/* Open state fills brass, so the toggle reads as active while its panel is down. */
.site-menu[open] .menu-toggle {
  background: var(--accent);
  color: var(--graphite);
  border-color: var(--accent);
}
/* Ring clears the brass fill in both themes (mirrors .theme-toggle / .nav-contact). */
.menu-toggle:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}
/* The hamburger glyph — matches .theme-glyph's box so the two round header icons
   read at the same weight. */
.menu-glyph {
  width: 1.15rem;
  height: 1.15rem;
  display: block;
}

/* The dropdown panel: absolutely positioned under the toggle, above the hero
   (z-index 30). Soft elevation via a color-mix graphite shadow — no raw hex. */
.menu-panel {
  position: absolute;
  top: calc(100% + 0.6rem);
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 15rem;
  padding: 0.5rem 0;
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: 4px;
  box-shadow: 0 8px 24px color-mix(in srgb, var(--graphite) 18%, transparent);
}
.menu-panel a {
  display: block;
  padding: 0.55rem 1.1rem;
  color: var(--fg);
  border-bottom: 0;
  border-left: 2px solid transparent;
  transition: background-color var(--t-2) var(--ease-mech), color var(--t-2) var(--ease-mech),
    border-color var(--t-2) var(--ease-mech);
}
.menu-panel a:hover,
.menu-panel a:focus-visible {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--fg);
}
/* The current section reads as location: accent text and a left brass rule. */
.menu-panel a[aria-current="page"] {
  color: var(--accent-text);
  border-left-color: var(--accent);
}

/* --- Header motto (t-096) ----------------------------------------------------
   The split-flap motto, moved out of the hero and beside the brand. flap.js picks
   up every .solari-board, so no JS change is needed. Static "Som ett urverk /
   like clockwork" text is the whole feature with JS off. The footer's motto rules
   are footer-scoped, so the header composes its own on the light header ground,
   mirroring the hero motto's accent-text + label pairing. Hidden below 48rem so
   the mobile header stays clean. */
.head-motto {
  margin: 0;
  display: flex;
  align-items: center;
}
.head-motto .detail-motto {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--accent-text);
  letter-spacing: 0.02em;
  margin-right: 0.5rem;
}
.head-motto .detail-gloss {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--label);
}
.head-motto .flap-tile { font-size: 0.72rem; }
@media (max-width: 47.999rem) {
  .head-motto { display: none; }
}

/* --- Theme toggle (top-right; brass in both themes) --------------------- */

/* Narrow header (t-088; menu added t-096, menu now a round icon t-097): the row
   carries the menu icon, brand, motto, contact chip, and the theme toggle. The
   motto hides below 48rem (see .head-motto) so the mobile header stays clean;
   tighten the contact chip metrics under 30rem so the icon, brand, chip, and
   toggle hold one line at 375px (the menu + theme icons keep their fixed 2.5rem
   circle). The right cluster is anchored by .site-nav's margin-left:auto, so no
   brand-side spacer is needed here. */
@media (max-width: 30rem) {
  .site-header .wrap { gap: 0.75rem; }
  .site-nav { gap: 0.55rem; }
  .nav-contact { font-size: 0.72rem; letter-spacing: 0.1em; }
  .nav-contact { padding: 0 0.7rem; }
}

/* Hidden until theme.js wires it (html.js-theme), so a no-JS visitor never meets
   a control that would do nothing — the page still follows prefers-color-scheme. */
.theme-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--rule-brass);
  border-radius: 50%;
  cursor: pointer;
  transition: border-color var(--t-1) var(--ease-mech), color var(--t-1) var(--ease-mech);
}

html.js-theme .theme-toggle {
  display: inline-flex;
}

.theme-toggle:hover {
  border-color: var(--accent);
  color: var(--accent-hi);
}

/* A half-moon crescent, brass stroke. It rotates a half-turn when dark is
   engaged — waxing to waning; transform-only, so reduced-motion simply snaps it. */
.theme-glyph {
  width: 1.15rem;
  height: 1.15rem;
  display: block;
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--t-3) var(--ease-settle);
}

.theme-toggle[aria-pressed="true"] .theme-glyph {
  transform: rotate(180deg);
}

.theme-crescent {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* (t-077) The header no longer wraps on mobile: with the nav reduced to the
   single Contact chip, brand + chip + toggle share one row at every width. */

/* --- Hero: the movement (the mechanism comes alive here) ---------------- */

/* --- Hero: agent-forward (t-061) ----------------------------------------
   Left column states the offer; the right column is an always-dark agent
   panel — a code-block surface that reads dark in both themes, rotating
   through four illustrative sample agents (inbox/support/outbound/
   reporting). The panel is authored complete for JS off (a finished inbox
   run); agents.js winds it into rotation, exactly as the old dial was drawn
   static and wound by watch.js. Every panel colour is a var()/color-mix()
   of the eight tokens — no raw hex, no green — so the palette guard holds. */
.hero {
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
  /* Tighter top than bottom (t-072 round-4): the split-flap sits close under the
     navbar so the top of the page reads composed, not sparse. */
  padding-block: clamp(0.9rem, 2.2vw, 1.5rem) clamp(1.75rem, 4vw, 3.25rem);
}

/* The hero theatre (t-084 round-2 founder ruling): one centred column that
   fills the first viewport. The pocket watch is the sole first-paint element;
   the hero-reveal copy below it is server-rendered but held back (see the
   theatre reveal further down) until the visitor opens the case or scrolls. The
   stage subtracts the header's rendered height so the watch lands centred in
   the space under the navbar — the header is padding-block 1.25rem (2.5rem
   top+bottom) + the ~1.55rem brand row + a 1px rule ≈ 4.1rem, so 4.5rem carries
   a small safety margin and the fold never scrolls. 100vh first as the fallback;
   100svh wins where supported so a mobile URL bar can't push the fold. On very
   tall viewports the stage stops stretching at 70rem (t-095), so the proof band
   peeks above the fold instead of the theatre centring in a ~2000px void; the cap
   never bites at laptop heights — a 1440x900 or 1080p stage computes well under
   70rem, so it engages only on ~1440p-tall-plus viewports. */
.hero-theatre {
  padding-block: 0;
}

.hero-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Gap cap trimmed 2.5rem -> 2rem (t-084 round-3b) — real height between the
     watch and the copy, shaved to lift the revealed desktop CTA above the fold. */
  gap: clamp(1.5rem, 4vh, 2rem);
  min-height: min(calc(100vh - 4.5rem), 70rem);
  min-height: min(calc(100svh - 4.5rem), 70rem);
}

/* The hero tease (t-096 founder ruling — supersedes the t-087 ungated value
   line). A short, BOLD hook painted before the case opens: display face, heavy
   weight, so the first viewport lands a punch instead of a caption. The value
   copy the old line carried moved into the reveal (the subhead); the teaser only
   has to make a motionless visitor lean in — five words need no measure clamp.
   Centred by the stage's text-align. No animation: legible with JS off and under
   reduced motion. */
.hero-tease {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.4rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--fg);
  text-wrap: balance;
}

/* The watch — the sole first-paint element — centred and sized by the
   flip-scene's own max-width. The reserved chip room is gone (t-097 removed the
   "Open the case" button); a small cushion below the case keeps the watch off the
   reveal stack. */
.hero-watch {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  padding-bottom: 1.25rem;
}

/* The held-back copy: a centred stack under the watch (motto, tagline, subhead,
   CTA). Its layout box is reserved even while hidden, so revealing it shifts
   nothing. */
.hero-reveal {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.8rem, 2vh, 1.25rem);
  width: 100%;
}

/* The split-flap motto board moved OUT of the hero and INTO the site header
   (t-096, see .head-motto) — the reveal stack now opens straight on the tagline
   h1, so the motto's old hero rules that lived here are gone. */

/* The size/weight live on the <h1> itself, not the .hero-head wrapper: the
   global h1,h2,h3 rule sets no font-size (so the UA h1{font-size:2em} would
   double a wrapper value) and pins font-weight:500. Targeting the h1 directly
   fixes both — matches the reference mockup's ~64px bold two-line headline. */
.hero-head h1 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.2rem, 5.2vw, 4rem);
  line-height: 1.03;
  letter-spacing: -0.015em;
  color: var(--fg);
}

.hero-head .em { color: var(--accent-text); }

/* Centred in the hero-reveal stack (t-084 round-2): the .hero-body wrapper was
   dropped, so the subhead is a direct hero-reveal child and centres itself with
   margin-inline auto within its measure. */
.hero-sub {
  margin: 0;
  margin-inline: auto;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--body-soft);
  max-width: 34ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  /* The value-prop paragraph that used to sit above this was re-homed (t-072
     round-4), so the actions sit closer under the headline. */
  margin-top: clamp(0.9rem, 2vw, 1.4rem);
}

.hero-actions .button { margin: 0; }

/* Monospace CTA (t-072 round-5): the primary hero action rendered like a CLI
   command — monospace, uppercase, brass. Inverts to a filled brass chip on
   hover/focus. (The round-4 decorative [ ] bracket pseudo-elements were removed;
   the accessible name was always just "Deploy Agent".) */
.hero-cta {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: 0.72rem 1.15rem;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-text);
  background: transparent;
  border: 1px solid var(--rule-brass);
  border-radius: 4px;
  white-space: nowrap;
  transition: color var(--t-2) var(--ease-mech), background-color var(--t-2) var(--ease-mech),
    border-color var(--t-2) var(--ease-mech);
}
.hero-cta:hover,
.hero-cta:focus-visible {
  background: var(--accent);
  color: var(--graphite);
  border-color: var(--accent);
}
/* Distinct keyboard focus ring: on focus the CTA fills brass, so a brass outline
   read as merely a thicker border (round-4 nit). The ring uses the band
   foreground — ink on the day hero, snow on the night hero — so it clears the
   brass chip in both themes, offset so it reads as a ring, not an edge. */
.hero-cta:focus-visible {
  outline: 2px solid var(--hero-band-fg);
  outline-offset: 3px;
}

/* The hero mention-code line (t-097): a quiet mono note under the hero CTA, not a
   banner. The code itself lifts to accent-text with a touch more tracking so it
   reads as the thing to quote. */
.hero-offer {
  margin: 0.9rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  color: var(--body-soft);
  text-align: center;
}
.hero-offer b {
  color: var(--accent-text);
  font-weight: 600;
  letter-spacing: 0.1em;
}

/* Filled brass primary; inverts to outline on hover (the base .button is the
   reverse — outline that fills on hover — so the modifier flips both states). */
.button.button-primary {
  background: var(--accent);
  color: var(--graphite);
}
.button.button-primary::after { display: none; }
.button.button-primary:hover {
  background: transparent;
  color: var(--fg);
}

.button.button-ghost { border-color: var(--rule-brass); }  /* visible outline in both themes (was --rule, near-invisible on light) */
.button.button-ghost::after { display: none; }
.button.button-ghost:hover {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}
.button.button-ghost .arrow {
  color: var(--accent);
  transition: transform var(--t-1) var(--ease-mech);
}
.button.button-ghost:hover .arrow { transform: translateX(4px); }

/* Entrance (t-084 round-2/3): gated on html.js-enter (tick.js) so JS-off is
   fully static. The WATCH fades in on first paint — it is the sole first-paint
   element; the hero-reveal copy is held back by the theatre reveal below. This
   entrance is OPACITY-ONLY on purpose (round-3): the watch's TRANSFORM is owned
   by the theatre choreography below (the pre-reveal drop + the reveal glide), so
   the old translateY(10px) entrance was retargeted to opacity — two animations
   must not fight over transform on the same element. */
@media (prefers-reduced-motion: no-preference) {
  html.js-enter .hero-watch {
    opacity: 0;
    animation: hero-rise var(--t-5) var(--ease-settle) forwards;
  }
}
@keyframes hero-rise { to { opacity: 1; } }

/* The theatre reveal (t-084 round-2). With JS armed (html.js-flip, set by
   flip.js) the hero-reveal copy is held back — invisible AND out of the tab
   order (visibility:hidden takes it off both), while keeping its reserved
   layout box so the watch sits at a stable centre and nothing shifts on reveal.
   flip.js flips html.hero-revealed on the first case-open or the first scroll,
   one way (closing the case never re-hides it). JS off (html lacks js-flip):
   the whole reveal is visible and the case is open — the unchanged no-JS
   contract. */
html.js-flip:not(.hero-revealed) .hero-reveal {
  visibility: hidden;
}
html.js-flip:not(.hero-revealed) .hero-reveal > * {
  opacity: 0;
  transform: translateY(14px);
}
/* Watch choreography (t-084 round-3): pre-reveal the watch alone holds the
   stage, so it drops to optical centre over the space the held-back copy
   reserves; the reveal glides it back up as the copy rises — one continuous
   motion. Not motion-gated (under reduced motion the watch simply sits at these
   dropped positions and snaps back on reveal); only the glide transition below
   is gated. Tuned empirically to land the dial at optical centre at first paint:
   desktop 10rem (round-3b: trimmed from 10.5rem — the height cuts above the
   watch, see the flip-scene/gap changes, raise the copy block ~20px so the
   optical-centre target drops ~10px); at <=64rem the paint-only transform means
   the reserved-copy geometry differs, so 10.5rem lands the 375-wide dial centre
   at ~334 (5.5rem undershot to ~254). */
html.js-flip:not(.hero-revealed) .hero-watch { transform: translateY(10rem); }
@media (max-width: 63.999rem) {
  html.js-flip:not(.hero-revealed) .hero-watch { transform: translateY(10.5rem); }
}
/* Motion only: the children rise in sequence on the --beat once revealed, and
   the watch glides on the same curve/duration so the two read as one motion in
   BOTH directions (the transition lives on the .hero-watch base). Reduced motion
   carries no transition, so the class flip shows the copy and snaps the watch at
   once with no travel. */
@media (prefers-reduced-motion: no-preference) {
  .hero-reveal > * {
    transition: opacity var(--t-5) var(--ease-settle),
      transform var(--t-5) var(--ease-settle);
  }
  .hero-watch { transition: transform var(--t-5) var(--ease-settle); }
  /* Re-staggered (t-096): the motto left the top of this stack for the header, so
     the h1 now takes the 0-beat slot and the sub/actions follow one beat apart. */
  html.hero-revealed .hero-head { transition-delay: 0s; }
  html.hero-revealed .hero-sub { transition-delay: var(--beat); }
  html.hero-revealed .hero-actions { transition-delay: calc(var(--beat) * 2); }
}

/* --- Pillars ------------------------------------------------------------ */

.pillars {
  display: grid;
  gap: clamp(2.25rem, 5vw, 3.5rem);
}

@media (min-width: 52rem) {
  .pillars {
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(2rem, 4vw, 3.5rem);
  }
}

/* Home teases; /services carries the full version (t-088). One quiet caption
   line under the pillars grid points to the full catalog — no animation. */
.pillars-more {
  margin: 2.25rem 0 0;
  font-size: 0.9rem;
  color: var(--label);
}

/* The line is one whole link, so the muted caption color must land on the <a>
   itself — the global link rule would otherwise paint it full ink. */
.pillars-more a {
  color: var(--label);
}

.pillars-more a:hover,
.pillars-more a:focus-visible {
  color: var(--fg);
}

/* Each pillar hangs from a hairline index rule with a brass tick — a watch
   face's marker, reused as structure. */
.pillar {
  position: relative;
  border-top: 1px solid var(--rule);
  padding-top: 1.5rem;
}

.pillar::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 2.5rem;
  height: 2px;
  background: var(--accent);
}

.pillar h3 {
  font-size: var(--step-2);
  margin-bottom: 0.7rem;
}

.pillar p {
  margin-bottom: 0;
  color: var(--body-soft);
}

/* --- Demonstrations card grid (t-017) ----------------------------------- */

.demos-grid {
  display: grid;
  gap: clamp(1.75rem, 4vw, 2.5rem) clamp(1.5rem, 4vw, 2.5rem);
  margin-top: clamp(2rem, 5vw, 3rem);
}

@media (min-width: 40rem) {
  .demos-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 60rem) {
  .demos-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Each card hangs from a hairline index rule with a brass tick — the watch-face
   vocabulary the pillars use. */
.demo-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  border-top: 1px solid var(--rule);
  padding-top: 1.4rem;
}

.demo-card::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 2.5rem;
  height: 2px;
  background: var(--accent);
}

/* The persistent honesty chip — a brass-tinted badge on every card (t-017). */
.demo-chip {
  align-self: flex-start;
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--accent-text);
  background: color-mix(in srgb, var(--brass) 10%, transparent);
  border: 1px solid var(--rule-brass);
  border-radius: 4px;
  padding: 0.3rem 0.6rem;
}

.demo-context {
  margin: 0;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--label);
}

.demo-flag {
  margin-left: 0.5rem;
  font-weight: 600;
  color: var(--accent-text);
}

.demo-title {
  margin: 0;
  font-size: var(--step-1);
}

.demo-desc {
  margin: 0;
  color: var(--body-soft);
}

.demo-tags {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.2rem 0 0;
  padding: 0;
}

.demo-tags li {
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--label);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 0.22rem 0.55rem;
}

/* --- Homepage demonstrations band (t-081) ----------------------------------
   The homepage demos reuse the existing .demo-card / .demo-card::before base
   (border-top + brass tick — the same watch-face vocabulary as .pillar). New
   here: the .demos grid mirrors .pillars exactly (stack → 3-col at 52rem, like
   the local .demos-grid precedent), a mono honesty chip (.demo-tag) distinct
   from the t-017 body-font .demo-chip, the lede, and h3/p sizing that matches
   .pillar without margins — .demo-card is a flex column whose gap already
   spaces its children, so a margin-bottom would double the gap. */
.demos {
  display: grid;
  gap: clamp(2.25rem, 5vw, 3.5rem);
}

@media (min-width: 52rem) {
  .demos {
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(2rem, 4vw, 3.5rem);
  }
}

.demos-lede {
  margin: 1rem 0 2.25rem;
  color: var(--label);
  max-width: 60ch;
}

.demo-card h3 {
  font-size: var(--step-2);
}

.demo-card p {
  margin-bottom: 0;
  color: var(--body-soft);
}

/* The demonstration label, made unmissable — a mono chip (FTC honesty). The
   card is a flex column, so align-self keeps the chip from stretching full
   width, matching the t-017 .demo-chip treatment. */
.demo-tag {
  align-self: flex-start;
  display: inline-flex;
  margin-bottom: 0.75rem;
  padding: 0.25rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-text);
  border: 1px solid var(--rule-brass);
  border-radius: 3px;
}

/* The per-card link into the full demonstration report (t-090 /work page). The
   card is a flex column whose gap already spaces its children; margin-top:auto
   pins the link to the card's bottom so the links align across a grid row of
   uneven-length descriptions. Quiet by default in the label color (like
   .pillars-more), brightening to full ink on hover/focus. */
.demo-link {
  margin-top: auto;
  align-self: flex-start;
  font-size: 0.85rem;
  color: var(--label);
}

.demo-link:hover,
.demo-link:focus-visible {
  color: var(--fg);
}

/* The terms as a lean ledger (t-081 round-2): the bordered trio component
   belongs to the proof run — rendered as rule-separated contract rows here so
   the two dark bands don't read as the same card grid twice. */
.terms-list { margin: 0; padding: 0; list-style: none; max-width: 46rem; }
.terms-list li {
  display: grid;
  grid-template-columns: minmax(9rem, 12rem) 1fr;
  gap: 1.5rem;
  padding-block: 1.15rem;
  border-top: 1px solid var(--rule-dark);
}
.terms-list li:last-child { border-bottom: 1px solid var(--rule-dark); }
.term-name {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-hi);
  padding-top: 0.2rem;
}
.term-desc { color: var(--on-dark-soft); }
@media (max-width: 40rem) {
  .terms-list li { grid-template-columns: 1fr; gap: 0.35rem; }
}

/* --- Closing CTA band (t-084) ----------------------------------------------
   A centred final prompt after the terms: the display h2, a soft lede, and the
   same mono hero CTA. On the default light ground (not a proof-run dark band),
   quiet and consistent with the rest of the page. .hero-cta is inline-level, so
   text-align centres it. */
.cta-band {
  text-align: center;
}
.cta-band h2 {
  font-size: var(--step-3);
}
.cta-lede {
  color: var(--label);
  margin: 0.75rem 0 1.5rem;
}

/* --- Proof-run (mechanism ground) ------------------------------------------- */

.proof-run {
  background: var(--movement);
  color: var(--on-dark);
}

.proof-run .eyebrow {
  color: var(--accent-hi);
}

.proof-run .eyebrow::before {
  background: var(--accent-hi);
}

/* h1 joins h2 here (t-096): the open-movement and terms bands moved to their own
   pages (/movement, /terms) where their heading is the page <h1>, not the home
   <h2> — the same band styling must cover both tags. */
.proof-run h1,
.proof-run h2 {
  font-size: var(--step-3);
  max-width: 22ch;
  margin-bottom: 1.5rem;
  color: var(--on-dark);
}

.proof-run p {
  color: var(--on-dark-soft);
  max-width: 62ch;
}

.proof-run .runs {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--rule-dark);
  border: 1px solid var(--rule-dark);
}

@media (min-width: 40rem) {
  .proof-run .runs {
    grid-template-columns: 1fr 1fr;
  }

  /* The services "wrapper" panel holds exactly three terms — a 3-col grid at the
     same breakpoint fills the row cleanly instead of leaving an empty quadrant. */
  .proof-run .runs-trio {
    grid-template-columns: repeat(3, 1fr);
  }
}

.proof-run .runs li {
  background: var(--movement);
  padding: 1.3rem 1.4rem;
}

.proof-run .runs .run-name {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--accent-hi);
  margin-bottom: 0.35rem;
}

.proof-run .runs .run-desc {
  color: var(--on-dark-soft);
  font-size: 0.95rem;
}

/* The proof-run band is always dark, so the quiet .pillars-more link needs the
   band's own on-dark muted token — the page-level --label assumes a light
   ground and under-renders here in light theme (t-089 design review, AA). */
.proof-run .pillars-more a {
  color: var(--on-dark-soft);
}

.proof-run .pillars-more a:hover,
.proof-run .pillars-more a:focus-visible {
  color: var(--on-dark);
}

.proof-run .proof-run-stamp {
  margin: clamp(1.5rem, 4vw, 2rem) 0 0;
  font-size: 0.9rem;
  color: var(--on-dark-soft);
  font-variant-numeric: tabular-nums;
}

.proof-run .proof-run-stamp strong {
  color: var(--accent-hi);
  letter-spacing: 0.08em;
}

.proof-run .proof-run-stamp a {
  color: var(--accent-hi);
  border-bottom-color: transparent;
}

.proof-run .proof-run-stamp a:hover,
.proof-run .proof-run-stamp a:focus-visible {
  border-bottom-color: var(--accent-hi);
}

/* --- The open movement: case-flip proof section (t-072) ----------------- */
/* The proof-run section carries a watch that turns over in 3D — a closed
   complication dial on the front, the exposed gear train (the exhibition
   caseback) on the back. The stage always sits on the dark mechanism ground
   (.proof-run), so the face tokens are pinned to their NIGHT values in BOTH
   themes here (snow hands, brass gears, steel hairlines) — the same always-dark
   treatment the agent panel uses. Every value is token-derived; the palette
   guard holds. Transform/opacity/clip-path only. */
.open-movement {
  --face-line:        color-mix(in srgb, var(--snow) 55%, transparent);
  --face-line-soft:   color-mix(in srgb, var(--steel) 55%, transparent);
  --face-brass:       var(--brass);
  --face-brass-soft:  color-mix(in srgb, var(--brass) 40%, transparent);
  --face-hand:        var(--snow);
  --face-hand-edge:   var(--graphite);
  --face-sun:         var(--brass);
  --face-gear:        color-mix(in srgb, var(--brass) 66%, transparent);
  --face-gear-hi:     var(--brass-hi);
}

.open-movement .om-lede {
  font-size: var(--step-1);
  max-width: 60ch;
}

.open-movement .om-runs {
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
}

.open-movement .om-report {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  font-size: 0.95rem;
}

.open-movement .om-report b {
  color: var(--accent-hi);
}

.flip-scene {
  position: relative;
  width: 100%;
  /* Raised 30rem -> 33rem (t-084 round-2) so the watch commands the hero
     theatre viewport, then trimmed to 31.5rem (round-3b): at 3:2 the ~24px
     narrower case is ~16px shorter, real layout height that lifts the revealed
     CTA back above the desktop fold. The <=30rem mobile cap (16rem) below is
     unchanged. */
  max-width: 31.5rem;
}

.flip-card {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 2;
}

/* The two layers of the hunter case: the movement seated in the case body, and
   the complication dial as a hinged lid over it. Both fill the stage; the lid
   pivots on its LEFT edge. */
.case-body,
.case-lid {
  position: absolute;
  inset: 0;
}

/* Base + no-JS + reduced-motion default: the case is already open — the
   movement is the static face (the copy is about visible gears), fully
   readable, no 3D. The lid is out of the picture until JS arms the hinge. */
.case-lid { display: none; }
.case-body { display: block; }

/* The lid has two faces stacked in the lid's own 3D space: the dial (front) and
   the engraved cuvette (back, pre-rotated 180°). Both hide their own back side,
   so as the lid swings PAST 90° the dial turns away and the inside face turns to
   the viewer — nothing goes edge-on and disappears. Inert until js-flip arms the
   hinge (the whole lid is display:none with JS off). */
.lid-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
/* (t-076) The inside of the lid is the engraved cuvette, not the old mirrored
   dial: the 180° pre-rotation nets out against the open swing, so the engraving
   is authored upright and reads upright on the standing-open lid. Styles live
   in the watch-furniture block below. */
.lid-back { transform: rotateY(180deg); }

/* Visible hinge barrel joining the lid to the case on the left edge (t-072
   round-4). It sits on the case rim at 9 o'clock — the axis the lid pivots on —
   so the open lid and the case body never read as two floating pieces. A brass
   cylinder with two knuckle divisions; token colours only, so the palette guard
   holds. Shown in every state (JS-off, reduced motion, and the live swing). */
.case-hinge {
  position: absolute;
  left: calc(18.3% - 5px);
  top: 35%;
  bottom: 35%;
  width: 10px;
  border-radius: 5px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--brass) 48%, var(--graphite)),
    var(--brass-hi) 48%,
    color-mix(in srgb, var(--brass) 54%, var(--graphite))
  );
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--graphite) 55%, transparent),
    0 3px 7px -3px color-mix(in srgb, var(--graphite) 70%, transparent);
  z-index: 4;
}
.case-hinge::before,
.case-hinge::after {
  content: "";
  position: absolute;
  left: 1px;
  right: 1px;
  height: 1px;
  background: color-mix(in srgb, var(--graphite) 60%, transparent);
}
.case-hinge::before { top: 34%; }
.case-hinge::after { bottom: 34%; }

.clockwork-face {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--face-brass);
  overflow: visible;
}

/* JS + motion-OK: the real hunter-case opening. The lid is hinged on the case's
   left rim (transform-origin ~18.3%, where the visible bezel meets the hinge
   barrel) and swings open toward the viewer, stopping past 90° so it stands open
   as a raking panel. backface-visibility:hidden keeps exactly one lid face
   showing — the front dial when closed, the engraved cuvette when open. The movement
   sits a hair behind so the lid reads as ON it, not coplanar. */
@media (prefers-reduced-motion: no-preference) {
  html.js-flip .flip-scene { perspective: 1500px; perspective-origin: 50% 44%; }
  html.js-flip .flip-card { transform-style: preserve-3d; }
  html.js-flip .case-body { transform: translateZ(-2px); }
  html.js-flip .case-lid {
    display: block;
    transform-style: preserve-3d;
    transform-origin: 18.3% center;
    transform: rotateY(0deg);
    transition: transform calc(var(--beat) * 7) var(--ease-settle);
  }
  /* Past 90°: the lid stands open leaning back, its engraved-cuvette inside face
     turned to the viewer, the dial (front) facing away. */
  html.js-flip .flip-card.is-open .case-lid { transform: rotateY(-110deg); }
  /* Hover/focus peek (t-097): the closed lid cracks a few degrees under the cursor
     or keyboard focus — a preview of the swing, the "you can open this" cue now
     that the watch itself is the control. Lives here so it is suppressed under
     reduced motion (the reduce block below stays travel-free; state changes there
     remain instant). */
  html.js-flip .flip-card:not(.is-open):hover .case-lid,
  html.js-flip .flip-card:not(.is-open):focus-visible .case-lid {
    transform: rotateY(-16deg);
  }
}

/* JS + reduced motion: the same hinge, but no travel — the global reduce reset
   drops the transition, so JS opens it to the standing-lid state instantly. */
@media (prefers-reduced-motion: reduce) {
  html.js-flip .flip-scene { perspective: 1500px; }
  html.js-flip .flip-card { transform-style: preserve-3d; }
  html.js-flip .case-body { transform: translateZ(-2px); }
  html.js-flip .case-lid {
    display: block;
    transform-style: preserve-3d;
    transform-origin: 18.3% center;
    transform: rotateY(0deg);
  }
  html.js-flip .flip-card.is-open .case-lid { transform: rotateY(-110deg); }
}

/* The case is a click target (t-078) — signal it. */
html.js-flip .flip-card { cursor: pointer; }

/* Keyboard focus ring on the card-as-button (t-097): the case is now the
   open/close control, so a focus-visible ring makes the affordance reachable for
   keyboard users. */
html.js-flip .flip-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 8px;
}

/* --- Watch furniture: real complication dial + exhibition caseback (t-072) -
   Both the lid and the body share viewBox 0 0 600 400, case centre (300,200)
   and an identical brass bezel band, so lid + body read as one physical object.
   The lid is a cream complication dial (Roman numerals, moon/sun/date/day); the
   body is the movement — barrel, meshing going train, escapement + balance,
   jewels — clipped to the case circle so nothing spills the case. Every
   fill/stroke is token-derived; the palette guard holds. The section sits on the
   dark mechanism ground, so the cream dial reads identically light/dark. */

/* Shared bezel band — the substantial round case rim, drawn on both faces. */
.bezel-band { fill: var(--brass); }
.bezel-groove {
  fill: none;
  stroke: color-mix(in srgb, var(--graphite) 60%, transparent);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
}

/* ---- Front: the complication dial ------------------------------------- */
.dial { fill: var(--paper); }
.guilloche-line {
  stroke: color-mix(in srgb, var(--ink) 24%, transparent);
  stroke-width: 0.5;
}
.dial-guilloche { fill: url(#guilloche); opacity: 0.55; }
.dial-flange {
  fill: none;
  stroke: color-mix(in srgb, var(--ink) 14%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.chap-min {
  fill: none;
  stroke: color-mix(in srgb, var(--ink) 42%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.chap-hour {
  fill: none;
  stroke: var(--brass);
  stroke-width: 2.4;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.rn {
  fill: var(--ink);
  font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  /* Smaller, finer numerals set near the chapter ring — a refined classic dial
     (t-072), not the earlier oversized bold ones. */
  font-size: 21px;
  font-weight: 400;
  letter-spacing: 0;
  text-anchor: middle;
  dominant-baseline: central;
}

/* Subdials — cream faces, brass rings, tiny mono labels. */
.subdial-face { fill: color-mix(in srgb, var(--paper) 80%, var(--snow)); }
.subdial-ring {
  fill: none;
  stroke: var(--brass);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
}
.sub-label {
  fill: color-mix(in srgb, var(--ink) 58%, var(--paper));
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.14em;
  text-anchor: middle;
}
/* Moonphase: night sky, a half-lit moon, two stars. */
.moon-sky { fill: var(--graphite); }
.moon-lit { fill: color-mix(in srgb, var(--snow) 76%, var(--steel)); }
.moon-dark { fill: var(--graphite); }
.moon-star { fill: var(--brass-hi); }
/* Sun / landscape: pale sky, brass sun, ridged foreground. */
.sun-sky { fill: color-mix(in srgb, var(--snow) 82%, var(--steel)); }
.sun-body { fill: var(--brass); }
.sun-ground { fill: color-mix(in srgb, var(--ink) 58%, var(--paper)); }
.sun-hill { fill: color-mix(in srgb, var(--brass) 42%, var(--ink)); }
/* Framed date aperture. */
.date-window { fill: var(--snow); }
.date-frame {
  fill: none;
  stroke: var(--brass);
  stroke-width: 1.8;
  vector-effect: non-scaling-stroke;
}
.date-num {
  fill: var(--ink);
  font-family: "Iowan Old Style", Palatino, Georgia, serif;
  font-size: 20px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
}
/* Day / season subdial. */
.day-day {
  fill: var(--ink);
  font-family: "Iowan Old Style", Palatino, Georgia, serif;
  font-size: 13px;
  font-weight: 700;
  text-anchor: middle;
  dominant-baseline: central;
}
.day-season {
  fill: color-mix(in srgb, var(--ink) 58%, var(--paper));
  font-family: var(--font-mono);
  font-size: 7px;
  letter-spacing: 0.12em;
  text-anchor: middle;
  dominant-baseline: central;
}

/* Hands: filled dauphine hour/minute in ink with a snow edge; a fine brass-hi
   seconds needle with a counterweight (the reference's red maps to brass-hi). */
.h-hour,
.h-min {
  fill: var(--ink);
  stroke: color-mix(in srgb, var(--snow) 72%, var(--paper));
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.h-sec-arm {
  stroke: var(--brass-hi);
  stroke-width: 2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.h-sec-cw { fill: var(--brass-hi); }
.dial-cap {
  fill: var(--brass);
  stroke: color-mix(in srgb, var(--snow) 60%, var(--paper));
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.dial-cap-hi { fill: var(--brass-hi); }

/* ---- Inside of the lid: the engraved cuvette (t-076) ------------------- */
/* A hunter case's inner lid is where the maker engraves. Polished old-gold
   ground, engine-turned like the dial, two engraved border rings, the motto
   arced over the engraved brand mark with its gloss beneath — so the open case
   reads the studio's promise where a maker would sign. Replaces the mirrored-
   dial lid back (t-072 round-4), which repeated the front face and carried no
   message. Decorative (the SVG is aria-hidden); every colour token-derived,
   and the low engraving contrast is deliberate — it is an engraving, not copy;
   the readable text lives in the proof-run list below the stage. */
.cuv-ground { fill: color-mix(in srgb, var(--brass) 62%, var(--graphite)); }
.cuv-engine-line {
  stroke: color-mix(in srgb, var(--graphite) 38%, transparent);
  stroke-width: 0.5;
}
.cuv-engine-fill { fill: url(#cuv-engine); opacity: 0.5; }
.cuv-ring {
  fill: none;
  stroke: color-mix(in srgb, var(--graphite) 48%, transparent);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
}
.cuv-ring-fine { stroke-width: 0.8; }
.cuv-arc {
  fill: color-mix(in srgb, var(--graphite) 86%, var(--brass));
  font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  font-size: 27px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-anchor: middle;
}
.cuv-gloss {
  fill: color-mix(in srgb, var(--graphite) 66%, var(--brass));
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.34em;
  text-anchor: middle;
}
.cuv-mark {
  fill: none;
  stroke: color-mix(in srgb, var(--graphite) 86%, var(--brass));
}

/* ---- Back: the exhibition caseback (movement, clipped to the case) ----- */
.movement-ground { fill: color-mix(in srgb, var(--graphite) 90%, var(--snow)); }
.mainspring {
  fill: none;
  stroke: color-mix(in srgb, var(--brass) 42%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.barrel-arbor { fill: var(--brass-hi); }

.gear-teeth {
  fill: none;
  stroke: color-mix(in srgb, var(--brass) 66%, transparent);
  stroke-width: 1.3;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.gear-pitch {
  fill: none;
  stroke: color-mix(in srgb, var(--brass) 30%, transparent);
  stroke-width: 0.6;
  vector-effect: non-scaling-stroke;
}
.gear-hub {
  fill: none;
  stroke: color-mix(in srgb, var(--brass) 66%, transparent);
  stroke-width: 1.1;
  vector-effect: non-scaling-stroke;
}
.gear-spokes {
  fill: none;
  stroke: color-mix(in srgb, var(--brass-hi) 80%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.gear-jewel { fill: var(--brass-hi); }

.balance-ring,
.balance-ring2 {
  fill: none;
  stroke: color-mix(in srgb, var(--brass) 66%, transparent);
  stroke-width: 1.3;
  vector-effect: non-scaling-stroke;
}
.balance-arm {
  fill: none;
  stroke: color-mix(in srgb, var(--brass-hi) 78%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.balance-screw { fill: var(--brass); }
.balance-jewel { fill: var(--brass-hi); }
.pallet-fork {
  fill: none;
  stroke: color-mix(in srgb, var(--brass-hi) 80%, transparent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.pallet-jewel { fill: var(--brass-hi); }

/* Jewels sit at the pivots, anchoring the going train on the open movement.
   (The translucent brass bridge plates were removed — founder ruling t-072:
   they read as distracting yellow overlays on the gears.) */
.pivot-jewel { fill: var(--brass-hi); }
.pivot-jewel-ring {
  fill: none;
  stroke: var(--brass);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}
.case-wall {
  fill: none;
  stroke: var(--brass);
  stroke-width: 3;
  vector-effect: non-scaling-stroke;
}
.case-wall-inner {
  fill: none;
  stroke: color-mix(in srgb, var(--steel) 40%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Per-gear periods, proportional to tooth count so meshed wheels stay meshed
   (tooth-passing rate teeth/dur is equal across the train: 44/12.32 = 22/6.16 =
   26/7.28 = 18/5.04 = 15/4.20). A deliberate exception to the --beat grid — the
   mesh ratio governs, not the escapement beat. Set in CSS, never inline
   (style-src 'self'). Gears spin only under JS + motion, while the caseback
   faces you. */
.gear-b { --dur: 12.32s; }
.gear-c { --dur: 6.16s; }
.gear-t { --dur: 7.28s; }
.gear-f { --dur: 5.04s; }
.gear-e { --dur: 4.20s; }
.gear-spin {
  transform-box: fill-box;
  transform-origin: center;
}
.balance-spin {
  transform-box: fill-box;
  transform-origin: center;
}
@media (prefers-reduced-motion: no-preference) {
  /* Declared paused so gears idle while the caseback is hidden behind the front
     dial; the open state runs them. */
  html.js-flip .gear-spin {
    animation: gear-rotate var(--dur, 8s) linear infinite;
    animation-play-state: paused;
  }
  html.js-flip .gear.ccw .gear-spin { animation-direction: reverse; }
  html.js-flip .balance-spin {
    animation: balance-osc 0.9s linear infinite;
    animation-play-state: paused;
  }
  html.js-flip .flip-card.is-open .gear-spin,
  html.js-flip .flip-card.is-open .balance-spin { animation-play-state: running; }
}
@keyframes gear-rotate {
  to { transform: rotate(360deg); }
}
/* The balance BEATS — long holds, a fast snap between limits: an escapement
   tick, never a quartz glide. */
@keyframes balance-osc {
  0%,  44% { transform: rotate(-15deg); }
  50%, 94% { transform: rotate(15deg); }
  100%     { transform: rotate(-15deg); }
}

/* --- Balance bridge: a steel bridge over the balance (t-072 r5) ------------- */
.brg-plate {
  fill: color-mix(in srgb, var(--graphite) 58%, var(--steel));
  stroke: color-mix(in srgb, var(--steel) 60%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.brg-screw {
  fill: var(--steel);
  stroke: color-mix(in srgb, var(--graphite) 55%, transparent);
  stroke-width: 0.8;
  vector-effect: non-scaling-stroke;
}
.brg-screw-slot {
  fill: none;
  stroke: color-mix(in srgb, var(--graphite) 70%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Proof-check callouts (t-078): the bridge engravings moved off the plates —
   the cap jewels fought the lettering — into a spec-sheet annotation layer on
   the movement's right margin: hairline leaders from TESTS/EVALS/SECURITY to
   the parts they stand for. Names only, never readouts. (t-084) recoloured to
   theme-aware tokens: the callouts now overhang the themed hero ground (the
   watch moved into the hero), not the old dark movement band, so the fixed
   light-steel fill read at ~1.5:1 on the light hero. --label flips with the
   theme, so both themes stay legible. */
.callout-line {
  fill: none;
  stroke: color-mix(in srgb, var(--label) 75%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.callout-dot { fill: var(--label); }
.callout-label {
  fill: var(--label);
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.14em;
  text-anchor: end;
  dominant-baseline: central;
}
/* Closed case: the callouts wait for the open state; the fade starts as the
   lid clears the movement. With JS off (case always open) they just show. */
html.js-flip .clockwork-back .callouts { opacity: 0; }
html.js-flip .flip-card.is-open .callouts { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  html.js-flip .clockwork-back .callouts { transition: opacity calc(var(--beat) * 3) var(--ease-mech); }
  html.js-flip .flip-card.is-open .callouts { transition-delay: calc(var(--beat) * 5); }
}

/* The separate "Open the case" affordance-button rule family was removed (t-097
   founder ruling, superseding the t-096 persistent chip and the t-084 CLICK ME
   arrow): the watch itself is the open/close control now — see the card cursor,
   the hover peek, and the card focus ring above. */

@media (max-width: 30rem) {
  /* The lid hinges on the left and opens past 90°, so on a narrow screen it
     needs room to its left. Keep the case compact but a touch larger than round-4
     (14rem) so the movement stays legible on a phone; opening the lid a few more
     degrees foreshortens the standing lid-back so both still fit. */
  .flip-scene { max-width: 16rem; }
  html.js-flip .flip-card.is-open .case-lid { transform: rotateY(-108deg); }
  .rn { font-size: 23px; }
}

/* --- Split-flap motto (Solari board, t-072) ----------------------------- */
/* The footer motto flips like a departure board between "SOM ETT URVERK" and
   "LIKE CLOCKWORK". Server-rendered static text is the whole feature with JS
   off / reduced motion; flap.js swaps in tiles that flip on the --beat. */
.solari-board {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
}
.flap-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0.94em;
  height: 1.55em;
  padding: 0 0.08em;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1;
  color: var(--accent-hi);
  background: color-mix(in srgb, var(--graphite) 78%, var(--snow));
  /* Was var(--ap-rule); inlined when the agent-panel token block was removed
     (t-084) — the flap tiles were its one surviving consumer. */
  border: 1px solid color-mix(in srgb, var(--snow) 14%, transparent);
  border-radius: 2px;
  transform-origin: center;
  backface-visibility: hidden;
}
.flap-tile.is-blank {
  /* Same footprint as a lettered tile so every column is a fixed width: the
     board is one stable grid and its right edge never shifts between phrases
     (which carry different space counts). Only the surface goes invisible. */
  background: transparent;
  border-color: transparent;
}
/* Footer flap board (t-078): the motto rides the same split-flap tiles as the
   hero board, cut smaller so the footer stays shallow. */
.site-footer .flap-tile { font-size: 0.68rem; }
/* (t-096) the t-095 wide-viewport hero flap-tile step was removed with the hero
   motto board — the board now rides in the header (.head-motto), which carries
   its own tile sizing. */
@media (prefers-reduced-motion: no-preference) {
  .flap-tile.flap { animation: flap-turn var(--t-2) var(--ease-mech); }
}
@keyframes flap-turn {
  0%   { transform: rotateX(0deg); }
  49%  { transform: rotateX(-90deg); }
  51%  { transform: rotateX(90deg); }
  100% { transform: rotateX(0deg); }
}

/* --- Pricing ------------------------------------------------------------ */

.engagement h2 {
  font-size: var(--step-3);
  margin-bottom: 0.75rem;
}

.engagement .intro {
  color: var(--label);
  max-width: 48ch;
  margin-bottom: 3rem;
}

.terms {
  margin-top: 2.25rem;
  padding: 0;
  list-style: none;
  color: var(--label);
  max-width: 58ch;
}

.terms li {
  padding-left: 1.4rem;
  position: relative;
  margin-bottom: 0.6rem;
}

.terms li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 0.8rem;
  height: 1px;
  background: var(--accent);
}

/* --- CTA ---------------------------------------------------------------- */

.cta {
  border-top: 1px solid var(--rule);
}

.cta h2 {
  font-size: var(--step-3);
  margin-bottom: 1rem;
  max-width: 18ch;
}

.cta p {
  color: var(--label);
  margin-bottom: 2rem;
}

/* The pill button "winds" on hover: a brass fill sweeps in and the button
   settles up a hair. */
.button {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--step-1);
  color: var(--fg);
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 0.8rem 1.7rem;
  cursor: pointer;
  transition: color var(--t-2) var(--ease-mech), background-color var(--t-2) var(--ease-mech),
    transform var(--t-1) var(--ease-mech);
}

.button::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  border: 1px solid currentColor;
  opacity: 0.6;
  transition: transform var(--t-3) var(--ease-settle);
}

.button:hover {
  background: var(--accent);
  color: var(--graphite);
  transform: translateY(-1px);
}

.button:hover::after {
  transform: rotate(180deg) scale(1.15);
}

/* --- Contact: one centered column (t-080) ------------------------------
   Founder ruling t-080 collapsed /contact to a single narrow, centered column:
   eyebrow, one H1, one plain supporting line, the email link, the form, the
   "Your data" note. The t-079 layout read as two competing display headings
   with a dead band between them; the second heading is now a visually-hidden
   form label and the page-index is dropped here (it stays on /services). The
   column governs width — the form no longer carries its own max-width — and
   .contact-col centers the block; the form itself resets to text-align:left so
   labels and inputs read normally inside the centered column. -------------- */

/* The centered contact column (t-080). Narrow measure, centered as a unit; the
   text inside centers except the form, which resets to left below. */
.contact-col {
  max-width: 30rem;
  margin-inline: auto;
  text-align: center;
}

/* Center the eyebrow's brass-tick + word as a unit (t-080). The base .eyebrow
   is an inline-flex row; making it a centered flex row keeps the dash+label
   together and centered under the H1. */
.contact-page .eyebrow {
  display: flex;
  justify-content: center;
}

.contact-page h1 {
  font-size: var(--step-4);
  line-height: 1.04;
  margin: 0 0 1rem;
}

/* The one plain supporting line (t-080) — flat, centered, a comfortable measure. */
.contact-lede {
  margin: 0 auto;
  max-width: 40ch;
  color: var(--body-soft);
  line-height: 1.55;
}

/* The success state (t-094). Once the form is replaced, the confirmation IS
   the page's message — full ink and a step up in size, not a caption under the
   email line. contact-form.js hides the intro lede at the same moment (it
   carries the same one-business-day promise, which would otherwise read twice). */
.contact-sent {
  margin: 2.5rem auto 0;
  max-width: 40ch;
  font-size: var(--step-1);
  line-height: 1.4;
  color: var(--fg);
}

/* The form is progressive enhancement (t-080): hidden until contact-form.js
   flags the document — a no-JS visitor gets the email line above instead. The
   centered column governs width, so the form drops its old max-width and only
   resets alignment (labels/inputs read left) and centers itself in the column. */
.contact-form {
  display: none;
  margin: 2.5rem auto 0;
  text-align: left;
  width: 100%;
}
html.js-contact .contact-form { display: block; }

.contact-form .field {
  max-width: 52ch;
  margin-bottom: 1.5rem;
}

.contact-form label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--label);
  margin-bottom: 0.5rem;
}

/* Optional-field flag beside a label (t-079) — lowercase as authored, a quiet
   aside so the label still reads first. */
.field-opt {
  font-size: 0.72rem;
  color: var(--label);
  letter-spacing: 0.04em;
  margin-left: 0.3rem;
}

.contact-form input,
.contact-form textarea {
  display: block;
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.5;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0.7rem 0.85rem;
  transition: border-color var(--t-1) var(--ease-mech);
}

.contact-form input:hover,
.contact-form textarea:hover {
  border-color: var(--rule-brass);
}

.contact-form input:focus,
.contact-form textarea:focus {
  border-color: var(--accent);
}

.contact-form textarea {
  min-height: 4.5rem;
  resize: vertical;
}

/* Per-field hint under an input (t-079) — the phone/message asides. */
.field-hint {
  margin: 0.4rem 0 0;
  font-size: 0.82rem;
  color: var(--label);
}

.contact-form .field-error {
  margin: 0.4rem 0 0;
  font-size: 0.9rem;
  color: var(--accent-text);
}

.contact-form button {
  margin-top: 0.5rem;
}

/* Quiet note under the compose button (t-079): the form only composes a mailto
   in the visitor's own mail app — it never sends. Muted so it reads as a note,
   not a second CTA. */
.contact-form-fallback {
  margin: 0.85rem 0 0;
  font-size: 0.9rem;
  color: var(--label);
}

/* The email link (t-080) — the always-visible, no-JS contact path, sitting
   above the JS-gated form in the centered column. */
.contact-email {
  margin: 1.4rem 0 0;
  font-size: var(--step-1);
}

.contact-email a {
  font-family: var(--font-display);
}

/* The contact mention-code offer note (t-097): one quiet line in the site's rule
   language — a 0.8rem brass left rule (as .terms li::before draws it) marking the
   offer where the intro flows into the form. Sits as a centered unit in the
   centered contact column; no border box, no background. */
.offer-note {
  position: relative;
  display: inline-block;
  margin: 1.4rem 0 0;
  padding-left: 1.4rem;
  text-align: left;
  font-size: 0.95rem;
  color: var(--label);
}
.offer-note::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 0.8rem;
  height: 1px;
  background: var(--accent);
}
.offer-note b { color: var(--accent-text); }

/* "Your data" note (t-080) — the gate-pinned data posture, one quiet line
   under a hairline rule that closes the column, centered within its measure. */
.contact-data-note {
  margin-top: 2.5rem;
  margin-inline: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.82rem;
  color: var(--label);
  max-width: 44ch;
}

/* --- Footer (mechanism ground) — the quiet closing path to contact (t-077; height trimmed t-078) --
   The footer must not dead-end: wordmark + motto on the left, the contact
   prompt + studio email on the right, the copyright alone on the final
   baseline. The footer nav was removed (t-077) — the header carries Contact
   and the wordmark is the way home, so the echo column read as clutter. Two
   blocks on wide screens, a clean stack on a phone; same movement/on-dark/
   accent tokens, so it composes in both themes at 1440 and 375. */
.site-footer {
  flex-shrink: 0;
  background: var(--movement);
  color: var(--on-dark-soft);
  border-top: 1px solid var(--rule-dark);
}

.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.4rem 3rem;
  padding-block: clamp(1.6rem, 3vw, 2.4rem);
  font-size: 0.9rem;
}

/* Left block: wordmark stacked over the motto. */
.site-footer .foot-lead {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.site-footer .foot-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  border: 0;
}

.site-footer .foot-brand .brand-mark {
  width: 1.35rem;
  height: 1.35rem;
  color: var(--accent);
}

.site-footer .foot-name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.05rem;
  color: var(--on-dark);
  letter-spacing: 0.02em;
}

.site-footer .foot-motto {
  margin: 0;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--on-dark-faint);
}

.site-footer .foot-motto .detail-motto {
  font-family: var(--font-display);
  color: var(--accent-hi);
  margin-right: 0.5rem;
}

/* Closing contact path: the prompt keeps a visitor who reached the bottom from
   dead-ending now that the pilot CTA is gone; the email is the direct affordance. */
.site-footer .foot-contact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}

.site-footer .foot-prompt {
  margin: 0;
  color: var(--on-dark-soft);
  text-wrap: balance;
}

.site-footer .foot-link {
  font-size: 1rem;
}

/* Links (nav + email) share the footer's quiet brass underline-on-hover. */
.site-footer a:not(.foot-brand) {
  display: inline-flex;
  align-items: center;
  min-height: 1.5rem;
  color: var(--accent-hi);
  border-bottom: 0;
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--t-1) var(--ease-mech), color var(--t-1) var(--ease-mech);
}

.site-footer a:not(.foot-brand):hover,
.site-footer a:not(.foot-brand):focus-visible {
  text-decoration-color: var(--accent-hi);
}

/* Copyright sits alone on the final baseline, faint, spanning the row above a
   hairline rule. */
.site-footer .foot-note {
  flex-basis: 100%;
  order: 1;
  margin: 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--rule-dark);
  color: var(--on-dark-faint);
  font-size: 0.78rem;
}

@media (max-width: 39.999rem) {
  /* On a phone the three blocks stack into one clean column (space-between has
     nothing to spread), and the copyright rule spans the full width above it. */
  .site-footer .wrap {
    flex-direction: column;
    gap: 1.25rem;
  }
  .site-footer .foot-note {
    align-self: stretch;
    padding-top: 1rem;
  }
  .site-footer a:not(.foot-brand) { min-height: 2.75rem; }
}

/* --- Interior page intro ------------------------------------------------ */

.page-intro h1 {
  font-size: var(--step-4);
  line-height: 1.04;
  margin-bottom: 1.25rem;
  max-width: 18ch;
}

.page-intro .lede {
  color: var(--body-soft);
  max-width: 38ch;
}

/* Centered intro variant (t-084): terminus pages (404, matching contact's
   centered column) center the intro as one unit. */
.intro-centered { text-align: center; }
.intro-centered .eyebrow { justify-content: center; }
.intro-centered h1,
.intro-centered .lede { margin-inline: auto; }

/* Interior page-intro density (t-070). At mobile the intro flows as a plain
   stack and the decorative index is hidden — nothing is absolutely positioned,
   so nothing can overlap the heading at narrow widths. At desktop the heading
   column is balanced by a quiet right-side index scale: the watch-face tick
   vocabulary that already carries the .eyebrow and the hero. The scale is
   aria-hidden and purely decorative (no JS, no motion) — it carries nothing a
   reader would miss — and the band's vertical padding is tightened so the
   heading is not marooned at the top of an empty section. */
.page-index {
  display: none;
}

@media (min-width: 60rem) {
  .page-intro {
    display: grid;
    grid-template-columns: minmax(0, 42ch) 1fr;
    grid-template-areas:
      "eyebrow index"
      "title   index"
      "lede    index";
    align-items: start;
    column-gap: clamp(2rem, 6vw, 5rem);
    padding-block: clamp(3.25rem, 6vw, 5.5rem);
  }

  .page-intro > .eyebrow { grid-area: eyebrow; }
  .page-intro > h1       { grid-area: title; }
  .page-intro > .lede    { grid-area: lede; }

  .page-index {
    grid-area: index;
    justify-self: end;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1.1rem;
    width: 100%;
    max-width: 15rem;
    min-height: 11rem;
  }

  /* A running-head folio, mirroring the eyebrow across the band. */
  .page-index-tag {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    letter-spacing: 0.26em;
    text-transform: uppercase;
    color: var(--label);
  }

  /* Watch-face index scale: a brass hairline spine with evenly spaced ticks,
     drawn entirely from the palette tokens — no asset, no request, no motion. */
  .page-index-scale {
    flex: 1 1 auto;
    width: 100%;
    min-height: 9rem;
    border-right: 1px solid var(--rule-brass);
    background-image: linear-gradient(to bottom, var(--rule) 1px, transparent 1px);
    background-size: 1.4rem 2.4rem;
    background-position: right top;
    background-repeat: repeat-y;
  }

  /* On load, the index draws itself in — a clip-path wipe down the spine so the
     brass hairline and its ticks appear top-to-bottom, like a hand sweeping
     down the scale, settling just after the heading has landed. Desktop only
     (the scale is hidden below 60rem), gated on html.js-enter, and purely
     decorative (the group is aria-hidden) — a no-JS visitor gets it static.
     Reduced motion is handled by the global reset, which lands it visible. */
  @media (prefers-reduced-motion: no-preference) {
    html.js-enter .page-index {
      animation: index-scale-draw var(--t-wind) var(--ease-settle) calc(var(--beat) * 2) both;
    }
  }
}

@keyframes index-scale-draw {
  from { clip-path: inset(0 0 100% 0); opacity: 0; }
  to   { clip-path: inset(0 0 0 0);   opacity: 1; }
}

/* --- Journal ------------------------------------------------------------ */

.journal-list {
  padding-block-start: clamp(1rem, 3vw, 2rem);
}

.piece-provenance {
  margin-top: clamp(2rem, 5vw, 3rem);
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--label);
}

.piece-index {
  list-style: none;
  margin: 0;
  padding: 0;
}

.piece-entry {
  position: relative;
  border-top: 1px solid var(--rule);
  padding-block: clamp(1.5rem, 4vw, 2rem);
}

.piece-entry::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 2.5rem;
  height: 2px;
  background: var(--accent);
}

.piece-meta {
  margin: 0 0 0.35rem;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}

.piece-title {
  margin: 0 0 0.5rem;
  font-size: var(--step-2);
  line-height: 1.2;
}

.piece-title a {
  border-bottom: 0;
}

.piece-title a:hover {
  color: var(--accent);
}

.piece-summary {
  margin: 0;
  max-width: 62ch;
  color: var(--label);
}

.piece h1 {
  font-size: var(--step-4);
  line-height: 1.08;
}

.piece h2 {
  font-size: var(--step-2);
  margin-top: 2.5rem;
}

.piece-header {
  margin-bottom: 2.5rem;
}

.piece-byline {
  font-size: 0.85rem;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}

.piece code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.86em;
  padding: 0.1em 0.35em;
  background: var(--surface);
  border-radius: 3px;
}

.piece ul {
  list-style: none;
  margin: 1rem 0;
  padding: 0;
}

.piece ul li {
  padding-left: 1.4rem;
  position: relative;
  margin-bottom: 0.6rem;
}

.piece ul li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 0.8rem;
  height: 1px;
  background: var(--accent);
}

/* --- Numbered method (prose, not cards) --------------------------------- */

.method {
  list-style: none;
  counter-reset: step;
  margin: 2.5rem 0 0;
  padding: 0;
  max-width: var(--measure);
  display: grid;
  gap: clamp(1.75rem, 4vw, 2.5rem);
}

.method > li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: 0 1.25rem;
  align-items: baseline;
}

/* The step number reads as a watch index: monospaced tabular, brass. */
.method > li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  line-height: 1.7;
}

.method p {
  margin-bottom: 0;
  color: var(--body-soft);
}

/* --- Delivery ledger table ---------------------------------------------- */

.ledger-wrap {
  overflow-x: auto;
  margin-top: 2.5rem;
}

.ledger {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  text-align: left;
  font-variant-numeric: tabular-nums;
}

.ledger th:nth-child(1) { width: 20%; }
.ledger th:nth-child(2) { width: 24%; }
.ledger th:nth-child(3) { width: 19%; }
.ledger th:nth-child(4) { width: 16%; }
.ledger th:nth-child(5) { width: 21%; }

.ledger caption {
  text-align: left;
  color: var(--label);
  margin-bottom: 1.5rem;
  max-width: 58ch;
}

.ledger th {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--label);
  border-bottom: 1px solid var(--rule-brass);
  padding: 0 1.25rem 0.75rem 0;
  vertical-align: bottom;
  white-space: nowrap;
}

.ledger td {
  padding: 1.75rem 1.25rem 1.75rem 0;
  border-bottom: 1px solid var(--rule);
  color: var(--body-soft);
}

.ledger .ledger-empty {
  font-style: italic;
  max-width: 60ch;
  color: var(--label);
}

@media (max-width: 39.999rem) {
  .ledger {
    table-layout: auto;
  }
  .ledger th {
    white-space: normal;
  }
  .ledger:has(.ledger-empty) thead {
    display: none;
  }
}

/* --- Assay client view (a structural artifact, not an app mock) --------- */

.proof-run-view-section h2 {
  font-size: var(--step-3);
  margin-bottom: 0.9rem;
  max-width: 18ch;
}

.proof-run-view-section .view-intro {
  color: var(--label);
  max-width: 54ch;
  margin-bottom: 0;
}

.proof-run-view {
  position: relative;
  margin: clamp(2.75rem, 6vw, 4rem) 0 0;
  padding: clamp(1.75rem, 4vw, 2.75rem);
  background: var(--movement);
  color: var(--on-dark);
}

/* The index rule crosses the ground -> movement boundary at the panel's top,
   binding the intro to the artifact. */
.proof-run-view-joint {
  position: absolute;
  top: 0;
  left: clamp(1.75rem, 4vw, 2.75rem);
  right: clamp(1.75rem, 4vw, 2.75rem);
  width: auto;
  height: auto;
  max-height: 1.6rem;
  margin: 0;
  transform: translateY(-52%);
}

.proof-run-view-grid {
  display: grid;
  gap: 1px;
  background: var(--rule-dark);
  border: 1px solid var(--rule-dark);
}

@media (min-width: 40rem) {
  .proof-run-view-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.view-panel {
  background: var(--movement);
  padding: 1.3rem 1.4rem;
}

.view-panel .panel-label {
  font-family: var(--font-body);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-hi);
  margin: 0 0 0.95rem;
}

.view-panel .panel-states {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.55rem;
}

.view-panel .panel-states li {
  position: relative;
  padding-left: 1.2rem;
  max-width: 34ch;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--on-dark-soft);
}

.view-panel .panel-states li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.6rem;
  height: 1px;
  background: var(--accent);
}

.proof-run-view-caption {
  margin-top: 1.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--on-dark-faint);
  font-variant-numeric: tabular-nums;
}

.proof-run-view-caption a {
  color: var(--accent-hi);
  border-bottom-color: transparent;
}

.proof-run-view-caption a:hover,
.proof-run-view-caption a:focus-visible {
  border-bottom-color: var(--accent-hi);
}

/* --- Entrance: the lead heading winds up (t-023) ------------------------ */

/* One mechanical "comes alive" moment per interior page: the lead eyebrow's
   brass index tick draws to its mark, then the heading (and its lede/byline)
   settle up into place — like a hand sweeping to the hour and stopping dead.
   Mechanical ease (var(--ease-mech), no overshoot — the brand ticks, it does not
   boing); transform/opacity only. index keeps the hero movement as its one
   moment and has no .page-intro, so it is untouched here. The lead CTA sits
   lower on every page, so its first render is never gated on this animation.

   Double-gated exactly like the scroll reveals: the hidden start state exists
   ONLY under html.js-enter (set by tick.js) AND no-preference, so a no-JS or
   reduced-motion visitor gets the fully-visible static heading with nothing
   hidden that JS cannot restore. */
@media (prefers-reduced-motion: no-preference) {
  html.js-enter :is(.page-intro, .piece-header) > .eyebrow::before {
    transform-origin: left center;
    animation: enter-tick var(--t-4) var(--ease-settle) both;
  }

  html.js-enter :is(.page-intro, .piece-header) h1 {
    animation: enter-settle var(--t-5) var(--ease-settle) calc(var(--beat) * 0.5) both;
  }

  html.js-enter .page-intro > .lede,
  html.js-enter .piece-header > .piece-byline {
    animation: enter-settle var(--t-5) var(--ease-settle) var(--beat) both;
  }
}

@keyframes enter-tick {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes enter-settle {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --- Motion: scroll reveals -------------------------------------------- */

/* Double-gated: content is fully visible by default; the hidden state exists
   ONLY inside both guards, so any browser without view() timelines (or with
   reduced motion) renders the complete static page with nothing hidden. */
/* One vocabulary at every scale: a brass index tick draws to its mark, then the
   content settles up into place behind it — the same "hand sweeping to the
   hour, stopping dead" the interior headings do on load, now scroll-linked.
   Each drawable rides its OWN view() timeline, so section eyebrows and the
   hairline ticks under each pillar/card index in one at a time as they scroll
   past, a natural stagger with no JS and no per-element delays. Double-gated
   exactly like before: the hidden start state exists ONLY inside no-preference
   + supports(view()), so any browser without scroll timelines (or with reduced
   motion) renders the complete static page with nothing hidden. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .reveal {
      animation: reveal-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 28%;
    }

    @keyframes reveal-rise {
      from {
        opacity: 0;
        transform: translateY(14px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    /* The section's own brass tick draws first (a tight early range), so the
       mark is set before the copy behind it finishes settling. */
    .reveal .eyebrow::before {
      transform-origin: left center;
      animation: index-draw linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }

    /* The hairline index tick under each pillar / card / journal entry draws on
       its own entry, so a grid of them indexes in as you scroll, not all at once. */
    .reveal :is(.pillar, .demo-card, .piece-entry)::before {
      transform-origin: left center;
      animation: index-draw linear both;
      animation-timeline: view();
      animation-range: entry 5% entry 45%;
    }
  }
}

@keyframes index-draw {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* --- Progressive enhancement (JS reveal fallback) ----------------------- */

/* Fallback for browsers without scroll-driven animations. Only under
   html.js-reveal (set by reveal.js exactly when it commits to observing) and
   only where view() is unsupported, so the CSS-only path stays primary and the
   no-JS / reduced-motion render is fully visible. */
@media (prefers-reduced-motion: no-preference) {
  @supports not (animation-timeline: view()) {
    html.js-reveal .reveal {
      opacity: 0;
      transform: translateY(14px);
      transition: opacity var(--t-5) var(--ease-settle), transform var(--t-5) var(--ease-settle);
    }

    html.js-reveal .reveal.revealed {
      opacity: 1;
      transform: none;
    }

    /* Same index-tick vocabulary in the fallback: the marks draw when the
       section reveals (observed together here, not per-element — the scroll
       stagger above is the primary path). */
    html.js-reveal .reveal .eyebrow::before,
    html.js-reveal .reveal :is(.pillar, .demo-card, .piece-entry)::before {
      transform-origin: left center;
      transform: scaleX(0);
      transition: transform var(--t-4) var(--ease-settle);
    }

    html.js-reveal .reveal.revealed .eyebrow::before,
    html.js-reveal .reveal.revealed :is(.pillar, .demo-card, .piece-entry)::before {
      transform: scaleX(1);
    }
  }
}

/* --- Cross-document view transitions ------------------------------------ */

@view-transition {
  navigation: auto;
}

.brand-name {
  view-transition-name: wordmark;
}

main {
  view-transition-name: page-main;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* --- Print -------------------------------------------------------------- */

@media print {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    background: transparent !important;
    color: var(--ink) !important;
    box-shadow: none !important;
  }

  .skip-link,
  .site-nav,
  .hero-art,
  .proof-run-view-joint {
    display: none !important;
  }

  .reveal {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  main a[href]:not(.button):not(.hero-cta)::after {
    content: " (" attr(href) ")";
    font-size: 0.82em;
    word-break: break-word;
  }
}
