/* ==========================================================================
   BASE — reset, faces, element defaults, layout primitives, shared utilities.
   Every value is a token. Reasoning: docs/art-direction.md
   ========================================================================== */

/* --------------------------------------------------------------------------
   FACES
   Both families self-hosted variable WOFF2. font-display: swap.
   The swap is metrically neutral: each fallback family named in the token
   stacks is declared from a local() source with size-adjust and metric
   overrides MEASURED in-browser, so the fallback fills the same box.
   Chivo ascent 0.940 / descent 0.250 / zero line gap.
   Martian Mono ascent 1.000 / descent 0.200 / zero line gap.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: Chivo;
  src: url("../assets/fonts/chivo-latin-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Martian Mono";
  src: url("../assets/fonts/martian-mono-latin-var.woff2") format("woff2");
  font-weight: 100 800;
  font-stretch: 75% 112.5%;
  font-style: normal;
  font-display: swap;
}

/* body copy — Chivo 400/500 vs Arial regular */
@font-face {
  font-family: "Chivo Fallback";
  src: local("Arial"), local("Liberation Sans"), local("Helvetica Neue");
  font-weight: 100 500;
  size-adjust: 106.97%;
  ascent-override: 87.88%;
  descent-override: 23.37%;
  line-gap-override: 0%;
}
/* controls — Chivo 600 vs Arial Bold. local() resolves a FACE, so a bold band
   must name the bold face: local("Arial") inside a 601+ band returns regular
   and the browser will not synthesise over it. */
@font-face {
  font-family: "Chivo Fallback";
  src: local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold");
  font-weight: 501 650;
  size-adjust: 101.17%;
  ascent-override: 92.91%;
  descent-override: 24.71%;
  line-gap-override: 0%;
}
/* headings — Chivo 700 vs Arial Bold */
@font-face {
  font-family: "Chivo Fallback";
  src: local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold");
  font-weight: 651 800;
  size-adjust: 101.69%;
  ascent-override: 92.44%;
  descent-override: 24.58%;
  line-gap-override: 0%;
}
/* wordmark — Chivo 900 vs Arial Black, which is narrower and much taller */
@font-face {
  font-family: "Chivo Fallback";
  src: local("Arial Black"), local("Arial-Black"), local("Liberation Sans Bold");
  font-weight: 801 900;
  size-adjust: 88.58%;
  ascent-override: 106.12%;
  descent-override: 28.22%;
  line-gap-override: 0%;
}
/* mono — measured against Consolas. Advance does not vary with weight. */
@font-face {
  font-family: "Martian Mono Fallback";
  src: local("Consolas"), local("DejaVu Sans Mono"), local("Liberation Mono"), local("Menlo");
  font-weight: 100 800;
  size-adjust: 127.32%;
  ascent-override: 78.54%;
  descent-override: 15.71%;
  line-gap-override: 0%;
}

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

html {
  /* THE ONE DECLARATION THAT MAKES UA CHROME AGREE ACROSS PLATFORMS.
     Everything the page does not draw itself — the scrollbar, the select
     popup, the text caret, the spellcheck underline, the autofill wash, the
     canvas behind the document before CSS arrives, the date and file pickers
     — is painted by the platform from this keyword. Without it: light
     scrollbars beside a near-black page on Windows in all three engines, a
     white select list in Firefox, a pale-blue Chrome autofill block over the
     dark booking fields, and a light native bezel on every iOS control.
     One keyword, and none of that has to be re-styled per engine. */
  color-scheme: dark;
  /* iOS inflates type on orientation change unless told not to; the
     unprefixed property is what Chrome and the newer WebKit read. Neither
     disables user zoom — that is maximum-scale, which is not set. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-h) + var(--space-md));
}

/* opt in rather than opt out: a reduced-motion reset scoped differently from
   the state it undoes is the failure recorded in docs/lessons.md */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin-block: 0;
  margin-inline: auto;
  max-width: var(--width-page);
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--weight-regular);
  line-height: var(--leading-body);
  overflow-x: hidden;

  /* TYPE RENDERING — a deliberate position, not a default.
     macOS renders light-on-dark type with stem darkening and looks a weight
     heavier than the same page on Windows, which is the difference the client
     sees. Two properties can be set and only these two:
     - -webkit-font-smoothing: antialiased turns that off in Safari and Chrome
       on macOS and in every iOS browser. It is a no-op on Windows and Linux,
       where Chrome and Edge use DirectWrite and ignore it.
     - -moz-osx-font-smoothing: grayscale is the same switch for Firefox on
       macOS. Without it Firefox is the odd one out ON THE SAME MAC, which is
       a divergence we can actually close; the macOS-versus-Windows one is not
       reachable from CSS and is accepted.
     text-rendering stays at its initial `auto`, stated here so nobody adds
     optimizeLegibility later: it forces the full shaping path, and on the
     variable Chivo face it changes measured line breaks between engines —
     the opposite of what this pass is for. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: auto;

  /* iOS paints a translucent grey block over any element it decides was
     tapped, which lands as a rectangle over the pill radius on every accent
     button and over the chamfer on every card. Removed page-wide because
     every control here already answers a press with its own :active
     transform; nothing loses its feedback. */
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

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

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; margin: 0; }
button { background: none; border: 0; cursor: pointer; }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-inset);
}

::selection { background: var(--surface-accent); color: var(--text-on-accent); }

/* --------------------------------------------------------------------------
   GRAIN — one fixed layer over the whole page, no HTTP request
   -------------------------------------------------------------------------- */
.grain {
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  background-image: var(--grain-image);
  background-size: var(--grain-size) var(--grain-size);
  opacity: var(--grain-opacity);
}

/* --------------------------------------------------------------------------
   LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
/* The gutter is also the safe-area guard. head.html ships viewport-fit=cover,
   so on a notched iPhone in LANDSCAPE the page runs under the sensor housing
   and the inline insets are ~44px; in portrait they are 0 and this resolves
   back to --gutter exactly. Written as max() rather than an addition so the
   measure is unchanged wherever there is no inset — which is every desktop.
   env() with a 0px fallback also covers a browser that knows the function but
   not the keyword. */
.wrap {
  width: 100%;
  max-width: var(--width-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-inline: max(var(--gutter), env(safe-area-inset-left, 0px)) max(var(--gutter), env(safe-area-inset-right, 0px));
}
.wrap--wide { max-width: var(--width-wide); }
.wrap--prose { max-width: var(--width-measure); }

.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gap);
}

/* FULL BLEED. 100vw includes the classic scrollbar on Windows and Linux but
   100% does not, so `width: 100vw` on a centred box overhangs the viewport by
   half a scrollbar at each edge — and above --width-page, where <body> stops
   growing, it overhangs by half the difference. Both are clamped here rather
   than left to an ancestor's overflow to hide:
   - the reach is capped at --width-page, so the wide case is exact;
   - the scrollbar residue (<=8px a side, 0 on macOS overlay scrollbars and 0
     on iOS) cannot be removed in CSS, because no unit reports the viewport
     minus its scrollbar. Where the bleeding element is already a child of a
     full-width block — .workband, .refusal__band — the vw maths is deleted
     instead and `width: auto` is exact everywhere. Prefer that. */
.bleed {
  width: auto;
  margin-inline: calc(50% - min(50vw, var(--width-page) / 2));
}

.stack > * + * { margin-block-start: var(--space-md); }

.section { padding-block: var(--space-section); }
.section--tight { padding-block: var(--space-section-tight); }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   SHARED TYPE COMPONENTS
   -------------------------------------------------------------------------- */
/* Section coordinate: [ 04 / SERVICES ]. The brackets are DRAWN, not typed —
   typed brackets in Martian Mono are heavy, their sidebearings fix the internal
   spacing, and they land in the accessible name as punctuation. */
.eyebrow {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding-inline: calc(var(--bracket-arm) + var(--bracket-gap));
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}
.eyebrow::before,
.eyebrow::after {
  content: "";
  position: absolute;
  inset-block: 0;
  width: var(--bracket-arm);
  border-block: var(--bracket-width) solid var(--bracket-color);
}
.eyebrow::before {
  inset-inline-start: 0;
  border-inline-start: var(--bracket-width) solid var(--bracket-color);
}
.eyebrow::after {
  inset-inline-end: 0;
  border-inline-end: var(--bracket-width) solid var(--bracket-color);
}
.eyebrow__idx { color: var(--text-accent); }

/* All section heads are left-aligned. Only the hero is centred: a mix of the
   two across fourteen sections reads as an accident, and centred-everything is
   the shared tell with both reference models. */
.sec-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
  text-align: start;
  max-width: var(--width-measure);
  margin-block-end: var(--space-stack);
}
/* the section boundary, graduated */
.sec-head::before {
  content: "";
  align-self: stretch;
  height: var(--tick-rule-h);
  background: var(--tick-rule);
}

.sec-title {
  font-size: var(--text-h2);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-display);
}

/* ACCENT GRAMMAR, page-wide: accent INK may only touch a number, a unit, a
   tick or a marker. Accent FILL is unrestricted. So the spec line sets in
   muted ink and only its figures are accented. */
.sec-spec {
  margin-block-start: calc(var(--spec-line-gap) - var(--space-sm));
  font-family: var(--font-mono);
  font-size: var(--text-spec);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  color: var(--text-muted);
}
.sec-spec b { color: var(--text-accent); font-weight: var(--weight-medium); }

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

.sec-lead {
  font-size: var(--text-lead);
  line-height: var(--leading-body);
  color: var(--text-muted);
}

/* µ (U+00B5) uppercases to Greek capital Mu, so text-transform turns "µm"
   into "MM" — a corrupted reading on a page about readings. Every unit inside
   an uppercased string carries this. */
.u { text-transform: none; }

.mono {
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-mono-tight);
  font-feature-settings: "tnum" 1;
}

/* --------------------------------------------------------------------------
   BUTTONS — one hover mechanism, a skewed sheen, compact calibration
   -------------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--control-h);
  padding-inline: var(--control-pad-x);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-body-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  overflow: hidden;
  transition:
    transform var(--dur-fast) var(--ease-lift),
    background-color var(--dur-fast) var(--ease-state),
    border-color var(--dur-fast) var(--ease-state),
    box-shadow var(--dur-fast) var(--ease-lift);
}
.btn::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(var(--sheen-width-compact) * -1);
  width: var(--sheen-width-compact);
  background: var(--sheen-color);
  transform: skewX(var(--sheen-skew)) translate3d(0, 0, 0);
  transition: transform var(--dur-sheen-compact) var(--ease-state);
  pointer-events: none;
}
.btn:hover::after { transform: skewX(var(--sheen-skew)) translate3d(400%, 0, 0); }

.btn--lg { min-height: var(--control-h-lg); padding-inline: var(--control-pad-x-lg); }

.btn--primary { background: var(--surface-accent); color: var(--text-on-accent); }
.btn--primary:hover {
  background: var(--surface-accent-hover);
  transform: translate3d(0, var(--lift-button), 0);
  box-shadow: var(--shadow-accent);
}
.btn--primary:active { transform: translate3d(0, 0, 0); box-shadow: var(--shadow-none); }

.btn--ghost {
  border: var(--border-width-hair) solid var(--border-soft);
  color: var(--text-primary);
}
.btn--ghost:hover {
  border-color: var(--border-accent);
  color: var(--text-primary);
  transform: translate3d(0, var(--lift-button), 0);
}
.btn--ghost:active { transform: translate3d(0, 0, 0); }

.btn--dark { background: var(--surface-invert-ink); color: var(--text-primary); }
.btn--dark:hover {
  background: var(--surface-raised-hover);
  transform: translate3d(0, var(--lift-button), 0);
}
.btn--dark:active { transform: translate3d(0, 0, 0); }

/* pill CTA at the foot of a card */
.pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: var(--control-h-sm);
  padding-inline: var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--surface-accent-wash);
  border: var(--border-width-hair) solid var(--border-accent-soft);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  transition:
    background-color var(--dur-fast) var(--ease-state),
    color var(--dur-fast) var(--ease-state),
    transform var(--dur-fast) var(--ease-lift);
}
.pill:hover {
  background: var(--surface-accent);
  color: var(--text-on-accent);
  transform: translate3d(0, var(--lift-button), 0);
}
.pill:active { transform: translate3d(0, 0, 0); }

/* --------------------------------------------------------------------------
   ICONS — inline sprite, stroked, painted from currentColor
   -------------------------------------------------------------------------- */
.icon {
  width: var(--icon-lg);
  height: var(--icon-lg);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--border-width-medium);
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}
.icon--sm { width: var(--icon-md); height: var(--icon-md); }
.icon--xs { width: var(--icon-sm); height: var(--icon-sm); }
.icon--fill { fill: currentColor; stroke: none; }

/* THE MARK — the depth gauge, from #i-mark. Two inks, not three: the frame and
   the pale graduations take currentColor like every other icon here, and only
   the reading takes its own, so a parent can move the reading alone.
   --mark-read is set here rather than inside the symbol because a <use> shadow
   tree is closed to document selectors — an inherited custom property is the
   only way to paint into it, and the only way a hover can reach it.
   Miter joins: the 45-degree chamfer is the whole point of the frame, and the
   round join .icon sets for the stroked pictograms sands it off. */
.icon--mark {
  --mark-read: var(--text-accent);
  width: var(--icon-md);
  height: var(--icon-md);
  color: var(--text-secondary);
  stroke-linejoin: miter;
}

.icon-circle {
  display: grid;
  place-items: center;
  width: var(--icon-circle);
  height: var(--icon-circle);
  border-radius: var(--radius-round);
  background: var(--surface-accent-wash);
  border: var(--border-width-hair) solid var(--border-accent-soft);
  color: var(--text-accent);
  flex: none;
}

/* --------------------------------------------------------------------------
   MEDIA — every photo box reserves its ratio, so CLS stays 0
   -------------------------------------------------------------------------- */
.media {
  position: relative;
  overflow: hidden;
  background: var(--surface-media);
}
/* <picture> is the direct child, so the image is addressed through it and
   taken out of flow: the box is sized by its reserved ratio, never by the
   intrinsic height of the photograph. */
.media picture { display: contents; }
.media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-entrance);
}

/* SPECIMEN TREATMENT — no photograph ships plain.
   ::after carries the tint AND the inset bezel. The bezel is drawn as an
   outline with a negative offset rather than a border on a second element:
   it stays a sharp square inside the rounded, chamfered card, and it does not
   ride along when the image scales on hover. */
.media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-scrim);
  background: var(--overlay-media);
  outline: var(--specimen-frame-width) solid var(--specimen-frame);
  outline-offset: calc(var(--specimen-inset) * -1);
  transition: background-color var(--dur-fast) var(--ease-state);
  pointer-events: none;
}
.media--lg::after { outline-offset: calc(var(--specimen-inset-lg) * -1); }

/* the specular band: narrow and bright, the way a light actually crosses
   paint. Third calibration of the one sheen mechanism. */
.media::before {
  content: "";
  position: absolute;
  z-index: var(--z-content);
  inset-block: calc(var(--space-lg) * -1);
  /* parked four band-widths out: the skew displaces the band horizontally by
     tan(18deg) x half the height, which on a 900px hero pushed a one-width
     offset back into view as a static streak. */
  inset-inline-start: calc(var(--sheen-width-specular) * -4);
  width: var(--sheen-width-specular);
  background: var(--sheen-color-specular);
  transform: skewX(var(--sheen-skew)) translate3d(0, 0, 0);
  transition: transform var(--dur-sheen-broad) var(--ease-state);
  pointer-events: none;
}
.media:hover::before { transform: skewX(var(--sheen-skew)) translate3d(1200%, 0, 0); }

/* SPEC CHIP — bottom-left, aligned to the bezel. Carries a true value: a
   reading where one exists, otherwise a real figure from the price table.
   Never an invented measurement. */
.chip {
  position: absolute;
  z-index: var(--z-content);
  inset-block-end: var(--chip-offset);
  inset-inline-start: var(--chip-offset);
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  min-height: var(--chip-min-h);
  padding: var(--chip-pad-block) var(--chip-pad-inline);
  border-radius: var(--radius-sharp);
  background: var(--chip-bg);
  border: var(--border-width-hair) solid var(--chip-border);
  color: var(--chip-text);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-mono-label);
  text-transform: uppercase;
  /* A chip is a reading, so it is never truncated. It sits inside a box that
     is both overflow-hidden and clip-path-chamfered, so anything that does not
     fit is silently cut — which is exactly how "FROM $1,850" shipped as
     "FROM $1". Wrapping, not nowrap, is the only safe default. */
  white-space: normal;
  max-width: calc(100% - var(--chip-offset) * 2);
}
.chip b { color: var(--chip-value); font-weight: var(--weight-medium); }
.chip--quiet b { color: var(--text-accent-quiet); }
.chip--lg { inset-block-end: var(--specimen-inset-lg); inset-inline-start: var(--specimen-inset-lg); }

/* --------------------------------------------------------------------------
   THE CHAMFER — the shape signature. Top-right, every block, never a control.
   clip-path removes box-shadow and clips both drop-shadow and the focus
   outline, so: no cast shadow on the dark ground (a black cast shadow on the
   near-black ground was never doing the work), and focusable chamfered
   elements ring with --focus-ring-inset.
   The cut eats the border along the diagonal; --chamfer-edge is the
   background layer that draws the missing hairline back.
   -------------------------------------------------------------------------- */
.chamfer { clip-path: var(--chamfer-clip); }
.chamfer--lg { --chamfer: var(--chamfer-lg); }
.chamfer--sm { --chamfer: var(--chamfer-sm); }

/* --------------------------------------------------------------------------
   TICK RULE — one construction, four inks. Horizontal only.
   -------------------------------------------------------------------------- */
.rule {
  height: var(--tick-rule-h);
  background: var(--tick-rule);
}

/* --------------------------------------------------------------------------
   REVEALS
   The hidden state scopes under .motion-ready — a class added only once the
   motion library has actually loaded. Never under .js: .js is set before first
   paint and would hide this content forever on a blocked CDN.
   The reduced-motion reset is scoped exactly like the state it undoes, because
   a media query adds no specificity.
   -------------------------------------------------------------------------- */
.motion-ready [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift), 0);
}
@media (prefers-reduced-motion: reduce) {
  .motion-ready [data-reveal] { opacity: 1; transform: none; }
}
