@charset "utf-8";
/* OnlyGillette — the parts catalogue.
   One stylesheet for ~136 generated pages. No framework, no runtime, no build step
   on the server: what ships is this file and plain HTML.

   Conventions that matter, because a generator stamps them 136 times:
   - Mobile-first. min-width queries only, at 640 / 768 / 1024. No max-width query exists.
   - The index row breaks on a CONTAINER width (560px), never a viewport width: the same
     row partial is stamped into full-width category pages and into the narrower
     related-items block, and a viewport query would render it wrong in the narrow one.
   - Spacing, type, colour, motion and depth all come from the closed token sets below.
     No literal px anywhere except 1px hairlines. */

/* ---------------------------------------------------------------- fonts ---- */

/* One variable file carries the whole type system: weights 100-900 and the width axis
   62.5-125%, which is what gives display its expanded cut. Preloaded in the head. */
@font-face {
  font-family: "Archivo";
  src: url("/f/archivo-var.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-stretch: 62.5% 125%;
  font-display: swap;
}

/* Metric-matched fallback. font-display:swap guarantees a brief fallback render, and
   without these overrides every price column and index row would reflow when Archivo
   arrives. The four values are computed from Archivo's own tables (unitsPerEm 1000,
   ascent 878, descent 210, sxHeight 526) against Arial's — not estimated. */
@font-face {
  font-family: "Archivo Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  size-adjust: 101.44%;
  ascent-override: 86.56%;
  descent-override: 20.70%;
  line-gap-override: 0%;
}

/* ---------------------------------------------------------------- tokens --- */

:root {
  /* Colour. OKLCH is canonical. Contrast verified: ink/bg 17.8:1, muted/bg 6.5:1,
     bg-on-primary 12.3:1, ink-on-accent 11.5:1. */
  --bg: oklch(1 0 0);
  --surface: oklch(0.968 0.004 150);
  --ink: oklch(0.205 0.018 150);
  --muted: oklch(0.48 0.014 150);
  --primary: oklch(0.32 0.075 148);
  --primary-deep: oklch(0.255 0.068 148);
  --accent: oklch(0.86 0.115 78);
  --rule: oklch(0.9 0.006 150);
  /* On the green band the hairline has to be lighter than its ground, not darker. */
  --rule-on-primary: oklch(0.42 0.06 148);

  /* Spacing — exactly six steps, and these values are load-bearing: components below
     reference them by name (row padding md, panel padding lg). */
  --space-xs: 0.25rem;   /*  4px */
  --space-sm: 0.5rem;    /*  8px */
  --space-md: 1rem;      /* 16px */
  --space-lg: 1.5rem;    /* 24px */
  --space-xl: 2.5rem;    /* 40px */
  --space-xxl: 4.5rem;   /* 72px */

  /* Type. clamp() is for display and headline ONLY. Everything in an index row or a
     spec table stays on the fixed rem scale, or column register drifts with viewport
     width — the one failure a fitment table cannot have. */
  --text-display: clamp(2.75rem, 1.9rem + 3.6vw, 4.5rem);
  --text-headline: clamp(1.625rem, 1.3rem + 1.5vw, 2.5rem);
  --text-title: 1.3125rem;
  --text-body: 1.0625rem;
  --text-label: 0.8125rem;

  --radius-sm: 2px;
  --radius-md: 4px;

  /* Motion. Exit runs at 75% of enter, declared rather than computed. One easing:
     the CSS keyword ease-out is mushier than this curve, and a bare duration means
     `ease`, which is ease-in-out in disguise. */
  --dur-fast: 120ms;   --dur-fast-out: 90ms;
  --dur-base: 150ms;   --dur-base-out: 112ms;
  --dur-slow: 180ms;   --dur-slow-out: 135ms;
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);

  /* Exactly one shadow, and only things that genuinely float may use it. */
  --shadow-float: 0 8px 24px oklch(0.205 0.018 150 / 0.12);

  --z-dropdown: 100;
  --z-sticky: 200;
  --z-backdrop: 300;
  --z-modal: 400;
  --z-toast: 500;
  --z-tooltip: 600;

  --measure: 68ch;

  font-kerning: normal;
  color-scheme: light;
}

/* ---------------------------------------------------------------- base ----- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Archivo", "Archivo Fallback", Arial, sans-serif;
  font-size: var(--text-body);
  font-weight: 400;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
}

/* Three weights, no more. 400 body, 600 headline/title/label, 700 display. A fourth
   makes 600-vs-500 an invisible distinction and amplifies variable-font rendering
   differences across platforms. */
h1, h2, h3, h4 { margin: 0; font-weight: 600; text-wrap: balance; }
p { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

/* One focus ring sitewide. Deliberately NOT the amber accent: amber on white is ~1.4:1
   and would fail WCAG 2.2 AA for a focus indicator. primary-deep is 14.6:1. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus { outline: none; }
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--primary-deep);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* On the green band the same ring would disappear; invert it there. */
.band :where(a, button):focus-visible,
.site-header :where(a, button):focus-visible,
.site-footer :where(a, button):focus-visible { outline-color: var(--bg); }

.skip-link {
  position: absolute; left: var(--space-md); top: -4rem;
  z-index: var(--z-toast);
  background: var(--bg); color: var(--ink);
  padding: var(--space-sm) var(--space-md);
  transition: top var(--dur-base) var(--ease-out-quart);
}
.skip-link:focus { top: var(--space-md); }

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

/* Numbers are compared down a column, so they must line up. Ligatures off on codes:
   model strings like "Trac-II" otherwise pick up unwanted treatment. */
.num, .price, .stock, .pack, .sku, .fits code {
  font-variant-numeric: tabular-nums lining-nums;
}
.sku, .model-code { font-variant-ligatures: none; }

/* The one legitimate uppercase here: table column heads and stock badges. This is not
   the banned decorative eyebrow — it is a data label. */
.colhead {
  font-size: var(--text-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--muted);
}

/* ---------------------------------------------------------------- layout --- */

.page {
  width: 100%;
  max-width: 78rem;
  margin-inline: auto;
  /* max() so the fallback is a real token, never 0, when env() is unsupported. */
  padding-inline: max(var(--space-md), env(safe-area-inset-left), env(safe-area-inset-right));
}
@media (min-width: 768px) { .page { padding-inline: max(var(--space-xl), env(safe-area-inset-left), env(safe-area-inset-right)); } }

.prose { max-width: var(--measure); text-wrap: pretty; }
/* One line-height unit between paragraphs, and space only — never space plus indent. */
.prose > p + p { margin-top: 1.6em; }

.section { padding-block: var(--space-xl); }
@media (min-width: 768px) { .section { padding-block: var(--space-xxl); } }
.section + .section { border-top: 1px solid var(--rule); }

.section__head { margin-bottom: var(--space-lg); }
.section__head h2 {
  font-size: var(--text-headline);
  font-stretch: 112%;
  letter-spacing: -0.02em;
  line-height: 1.12;
  /* Optical alignment: only where a heading sits directly on a hairline, because the
     letterform's side bearing makes geometrically flush text read as indented. */
  margin-left: -0.045em;
}

/* ---------------------------------------------------------------- header --- */

/* The masthead is the deeper green and the hero band the brighter one. Both being
   --primary merged them into a single green mass and the hero stopped reading as a
   deliberate drenched fold; a tonal step separates them without a border. */
.site-header {
  background: var(--primary-deep);
  color: var(--bg);
  padding-top: max(var(--space-md), env(safe-area-inset-top));
  padding-bottom: var(--space-md);
}
.site-header a { text-decoration: none; }

.masthead { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-md) var(--space-lg); }

.logo {
  font-size: 1.5rem; font-weight: 700; font-stretch: 118%;
  letter-spacing: -0.02em; line-height: 1;
  margin: 0;
}
@media (min-width: 768px) { .logo { font-size: 1.75rem; } }

/* Redundant with the hero on the one page that has a hero, and on a phone it was
   costing a line of a header that already ran to 630px before any content. */
.masthead__tag { display: none; }
@media (min-width: 640px) {
  .masthead__tag {
    display: block;
    font-size: var(--text-label);
    color: oklch(0.86 0.03 148);
    margin-left: auto;
  }
}

.site-search { display: flex; gap: var(--space-sm); width: 100%; margin-top: var(--space-md); }
@media (min-width: 768px) { .site-search { width: auto; max-width: 24rem; margin-top: 0; } }
.site-search input {
  flex: 1 1 auto; min-width: 0;
  font: inherit; font-size: var(--text-body);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--rule-on-primary);
  border-radius: var(--radius-sm);
  background: var(--bg); color: var(--ink);
}
/* Placeholders carry the same 4.5:1 as body text — the light-grey default is the single
   most common contrast failure in a search field. */
.site-search input::placeholder { color: var(--muted); opacity: 1; }

/* 12 categories degrade by reflow, not by a drawer or a bottom bar: both are app chrome
   that fights the paper-catalogue register, and neither works without JS. */
.site-nav { margin-top: var(--space-md); }
/* Twelve systems wrap to four lines on a phone. Rather than hide them behind a JS
   drawer — which would put the primary task behind a tap — the strip scrolls
   horizontally below 640px and wraps normally above it. Nothing is hidden, and the
   header drops from four lines to one. */
.site-nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; gap: var(--space-xs) var(--space-md);
  font-size: var(--text-label); font-weight: 600;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
  /* bleed to the page edge so the strip reads as scrollable, not as clipped */
  margin-inline: calc(-1 * var(--space-md));
  padding-inline: var(--space-md);
  scroll-padding-inline-start: var(--space-md);
}
.site-nav a { white-space: nowrap; }
@media (min-width: 640px) {
  .site-nav ul { flex-wrap: wrap; overflow-x: visible; margin-inline: 0; padding-inline: 0; }
}
.site-nav a { padding-block: var(--space-xs); display: inline-block; color: oklch(0.9 0.02 148); }
@media (hover: hover) { .site-nav a:hover { color: var(--bg); } }
.site-nav a[aria-current="page"] { color: var(--bg); box-shadow: inset 0 -2px 0 var(--accent); }

.breadcrumb {
  font-size: var(--text-label); color: var(--muted);
  padding-block: var(--space-md);
  border-bottom: 1px solid var(--rule);
}
.breadcrumb ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.breadcrumb li + li::before { content: "/"; margin-right: var(--space-sm); color: var(--rule); }

/* ---------------------------------------------------------------- band ----- */

/* The one drenched surface. Colour strategy is Committed, so green owns a whole fold
   here and then recedes to header, footer and actions everywhere else. */
.band {
  background: var(--primary);
  color: var(--bg);
  padding-block: var(--space-xxl);
}
.band h1 {
  font-size: var(--text-display);
  font-weight: 700; font-stretch: 118%;
  letter-spacing: -0.03em; line-height: 1.02;
  max-width: 18ch;
  margin-left: -0.045em;
}
.band__lede {
  margin-top: var(--space-lg);
  max-width: 52ch;
  font-size: 1.1875rem;
  /* Light type on a dark ground reads lighter; it needs more room than 1.6. */
  line-height: 1.7;
  color: oklch(0.93 0.02 148);
}
.band__actions { margin-top: var(--space-xl); display: flex; flex-wrap: wrap; gap: var(--space-md); }


/* Specimen plates in the hero. Presented as pinned white plates on the green, the way a
   parts catalogue shows the actual part — not as cards: no shadow, no soft radius, a
   caption line rather than a heading-plus-body block. */
.band__grid { display: grid; gap: var(--space-xl); align-items: center; }
@media (min-width: 1024px) { .band__grid { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--space-xxl); } }

.band__plates { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-md); align-items: stretch; }
.plate {
  display: grid;
  /* image, then a flexible caption, then the note pinned to the bottom edge, so the
     three plates share one baseline no matter how the system names wrap. */
  grid-template-rows: auto 1fr auto;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--bg);
  border-radius: var(--radius-sm);
  text-decoration: none; color: var(--ink);
  transition: transform var(--dur-fast) var(--ease-out-quart);
}
.plate img { width: 100%; height: auto; aspect-ratio: 1; object-fit: contain; }
.plate__name { font-size: var(--text-label); font-weight: 600; line-height: 1.25; }
.plate__note {
  font-size: var(--text-label); color: var(--muted);
  border-top: 1px solid var(--rule); padding-top: var(--space-xs);
}
@media (hover: hover) { .plate:hover .plate__name { text-decoration: underline; text-underline-offset: 2px; } }

/* ---------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
  padding: 0.875rem 1.75rem;
  font: inherit; font-size: var(--text-label); font-weight: 600;
  letter-spacing: 0.01em;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  text-decoration: none; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart),
              transform var(--dur-fast) var(--ease-out-quart);
}
.btn--primary { background: var(--primary); color: var(--bg); }
.btn--secondary { background: var(--bg); color: var(--ink); border-color: var(--ink); }
.btn--on-band { background: var(--bg); color: var(--primary); }
.btn--ghost { background: transparent; color: inherit; border-color: currentColor; }

/* Hover is colour only. A row or button that lifts or grows breaks the column register
   a fitment table exists to preserve. */
@media (hover: hover) {
  .btn--primary:hover { background: var(--primary-deep); }
  .btn--secondary:hover { background: var(--surface); }
  .btn--on-band:hover { background: oklch(0.93 0.02 148); }
  .btn--ghost:hover { background: oklch(1 0 0 / 0.1); }
}
.btn:active { transform: scale(0.98); }

/* ---------------------------------------------------------------- rows ----- */

/* The catalogue is an index, not a grid of cards. Cards are the explicit anti-reference.
   Numeric columns get fixed ch widths so the register holds across all 12 category
   pages; the name cell takes the remaining space and truncates rather than shoving the
   numbers out of alignment. */
.rows { container-type: inline-size; }
.rows__head { display: none; }

.row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  grid-template-areas:
    "thumb name"
    "thumb meta";
  column-gap: var(--space-md);
  row-gap: var(--space-xs);
  align-items: center;
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--rule);
  transition: background-color var(--dur-fast) var(--ease-out-quart);
}
.row__thumb { grid-area: thumb; }
.row__thumb { aspect-ratio: 1; }
.row__thumb img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
.row__noimg { width: 100%; aspect-ratio: 1; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-sm); }
.row__name { grid-area: name; font-size: var(--text-body); font-weight: 600; line-height: 1.35; }
.row__name a { text-decoration: none; }
@media (hover: hover) { .row__name a:hover { text-decoration: underline; text-underline-offset: 2px; } }

/* Fitment sits tight under the name and price is pushed away from that cluster: the
   hierarchy is spatial, not only typographic. If both sat equidistant, proximity would
   contradict the ranking and the squint test would resolve to price. */
.row__meta { grid-area: meta; display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-md); align-items: baseline; }
.row__fits { font-size: var(--text-label); color: var(--muted); }
.row__fits b { color: var(--ink); font-weight: 600; }
.row__pack { font-size: var(--text-label); color: var(--muted); }
.row__price { font-size: var(--text-body); font-weight: 600; }
.row__stock { font-size: var(--text-label); }

@container (min-width: 560px) {
  .rows__head {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr) 12ch 9ch 10ch;
    column-gap: var(--space-lg);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--ink);
  }
  .rows__head span:nth-child(n+3) { text-align: right; }
  .row {
    grid-template-columns: 64px minmax(0, 1fr) 12ch 9ch 10ch;
    grid-template-areas: "thumb name fits price stock";
    column-gap: var(--space-lg);
    align-items: center;
  }
  .row__meta { display: contents; }
  .row__fits { grid-area: fits; text-align: right; }
  .row__pack { display: none; }
  .row__price { grid-area: price; text-align: right; }
  .row__stock { grid-area: stock; text-align: right; }
}

/* Exactly one focusable anchor per row — the product name. The whole row lights from it.
   A second overlay link would double the keyboard path through a 46-item catalogue. */
@media (hover: hover) { .row:hover { background: var(--surface); } }
.row:has(a:focus-visible) { background: var(--surface); outline: 2px solid var(--primary-deep); outline-offset: -2px; }
.row:has(a:focus-visible) a:focus-visible { outline: none; }

/* Touch: expand the hit area with a pseudo-element rather than padding, which would
   change the grid cell height and break the column register across 46 rows. */
.row__name a { position: relative; display: inline-block; }
.row__name a::before { content: ""; position: absolute; inset: -10px -4px; }
@media (pointer: coarse) { .row__name a::before { inset: -14px -8px; } }


/* Sort controls. These previously inherited .site-nav, whose colours are tuned for the
   dark masthead — on white that measured 1.43:1, a hard WCAG failure on every category
   page. They get their own colours here, and the active option is marked by weight and
   an amber underline rather than by colour alone. */
.sortbar {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--rule);
  font-size: var(--text-label);
}
.sortbar__label { color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; }
.sortbar ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-md); }
.sortbar a { color: var(--muted); text-decoration: none; padding-block: var(--space-xs); display: inline-block; }
@media (hover: hover) { .sortbar a:hover { color: var(--ink); } }
.sortbar a[aria-current] { color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent); }
@media (pointer: coarse) { .sortbar a { min-height: 44px; display: flex; align-items: center; } }

/* The column header carries the word; repeating it in all 46 cells is noise. */
@container (min-width: 560px) { .row__fitslabel { display: none; } }

/* ---------------------------------------------------------------- stamp ---- */

/* Availability reads as an ink stamp on a catalogue page, not as a badge. Dark text on
   the amber — white on it would collapse to ~1.4:1. */
.stamp {
  display: inline-block;
  padding: 0.1875rem var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--ink);
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.stamp--out { background: transparent; color: var(--muted); border: 1px solid var(--rule); }

/* ---------------------------------------------------------------- product -- */

.product { display: grid; gap: var(--space-xl); padding-block: var(--space-xl); }
@media (min-width: 768px) { .product { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: var(--space-xxl); align-items: start; } }

/* The surviving product photography tops out at 320px. Upscaling it into a full-width
   hero would just be a blurry hero, so the figure is a catalogue plate instead: the
   image sits at its native size on a ruled panel, which is what a parts catalogue does
   anyway. */
.product__figure {
  margin: 0;
  display: grid;
  place-items: center;
  padding: var(--space-xl);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
}
.product__figure img { width: auto; max-width: 100%; height: auto; mix-blend-mode: multiply; }

.product__title {
  font-size: var(--text-headline);
  font-stretch: 112%; letter-spacing: -0.02em; line-height: 1.12;
  margin-left: -0.045em;
}

/* Fitment outranks price, so it comes first and it is the loudest fact on the page. */
.fitment {
  margin-top: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  background: var(--surface);
  border-left: none;                 /* never a side stripe */
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
}
.fitment__label { font-size: var(--text-label); color: var(--muted); }
.fitment__value { font-size: var(--text-title); font-weight: 600; margin-top: var(--space-xs); }

.product__price { font-size: 2rem; font-weight: 600; margin-top: var(--space-lg); font-variant-numeric: tabular-nums lining-nums; }
.product__meta { margin-top: var(--space-sm); color: var(--muted); font-size: var(--text-label); display: flex; flex-wrap: wrap; gap: var(--space-md); }
.product__actions { margin-top: var(--space-lg); display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: center; }

.spec { width: 100%; border-collapse: collapse; margin-top: var(--space-lg); font-size: var(--text-body); }
.spec th, .spec td { text-align: left; padding: var(--space-sm) 0; border-bottom: 1px solid var(--rule); vertical-align: baseline; }
.spec th { font-weight: 600; color: var(--muted); font-size: var(--text-label); width: 12rem; }
.spec td { font-variant-numeric: tabular-nums lining-nums; }

/* ---------------------------------------------------------------- notes ---- */

.note {
  padding: var(--space-lg);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
}
.note h2, .note h3 { font-size: var(--text-title); }
.note p { margin-top: var(--space-sm); color: var(--muted); }
.note p:first-of-type { margin-top: var(--space-xs); }

/* Empty state. Sensor Excel has no surviving listings; saying so plainly beats padding
   the page with substitutes. */
.empty { padding-block: var(--space-xl); }
.empty h2 { font-size: var(--text-title); }
.empty p { margin-top: var(--space-sm); color: var(--muted); max-width: 52ch; }
.empty .btn { margin-top: var(--space-lg); }

/* ---------------------------------------------------------------- systems -- */

/* The entry point: a plain list of razor systems. People recognise their own razor by
   name, so this is links, not a filter widget — it works without JS and it indexes. */
.systems { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--rule); border-block: 1px solid var(--rule); }
@media (min-width: 640px) { .systems { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .systems { grid-template-columns: repeat(3, 1fr); } }
.systems li { background: var(--bg); }
.systems a {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md);
  min-height: 44px;
  padding: var(--space-md);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out-quart);
}
.systems a > span:first-child { font-weight: 600; }
.systems a > span:last-child { font-size: var(--text-label); color: var(--muted); font-variant-numeric: tabular-nums; }
@media (hover: hover) { .systems a:hover { background: var(--surface); } }

/* ---------------------------------------------------------------- forms ---- */

.field { display: block; margin-bottom: var(--space-md); }
.field > span { display: block; font-size: var(--text-label); font-weight: 600; margin-bottom: var(--space-xs); }
.field input, .field textarea, .field select {
  width: 100%; font: inherit; font-size: var(--text-body);
  padding: 0.75rem var(--space-md);
  min-height: 44px;
  color: var(--ink); background: var(--bg);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}
.field input::placeholder, .field textarea::placeholder { color: var(--muted); opacity: 1; }
.field input:focus-visible, .field textarea:focus-visible { border-color: var(--primary); }
/* A permanent, non-collapsing slot: an error appearing must not reflow the form. */
.field-err { min-block-size: 1.25rem; font-size: var(--text-label); color: oklch(0.45 0.16 27); margin-top: var(--space-xs); }

/* ---------------------------------------------------------------- footer --- */

.site-footer {
  background: var(--primary);
  color: oklch(0.9 0.02 148);
  padding-block: var(--space-xl);
  padding-bottom: max(var(--space-xl), env(safe-area-inset-bottom));
  margin-top: var(--space-xxl);
  font-size: var(--text-label);
}
.site-footer a { color: var(--bg); }
.site-footer__grid { display: grid; gap: var(--space-lg); }
@media (min-width: 640px) { .site-footer__grid { grid-template-columns: repeat(3, 1fr); } }
.site-footer h2 { font-size: var(--text-label); color: var(--bg); margin-bottom: var(--space-sm); letter-spacing: 0.06em; text-transform: uppercase; }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-xs); }
.site-footer li a, .breadcrumb a { display: inline-block; padding-block: var(--space-xs); }
.site-footer__legal { margin-top: var(--space-xl); padding-top: var(--space-md); border-top: 1px solid var(--rule-on-primary); }

/* ---------------------------------------------------------------- popover -- */

/* Native popover: light-dismiss, Escape and top-layer stacking with no JS. */
[popover] {
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  box-shadow: var(--shadow-float);
  max-width: min(28rem, calc(100vw - 2 * var(--space-md)));
}
[popover]::backdrop { background: oklch(0.205 0.018 150 / 0.35); }
/* Anchor positioning is not everywhere yet, and a JS-free site has no reposition
   fallback — pin it predictably where it is unsupported. */
@supports not (anchor-name: --a) {
  [popover] { position: fixed; inset: auto var(--space-md) var(--space-md) auto; }
}

/* ---------------------------------------------------------------- print ---- */

/* A parts catalogue gets printed and carried to the bench. Drop the chrome, keep the
   index, expand the links. */
@media print {
  .site-header, .site-footer, .site-search, .site-nav, .band__actions, .skip-link { display: none; }
  .band { background: none; color: var(--ink); padding-block: var(--space-lg); }
  .band__lede { color: var(--ink); }
  .row { break-inside: avoid; }
  .page { max-width: none; padding: 0; }
  main a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #555; }
}

/* ---------------------------------------------------------------- motion --- */

/* Blanket reset, deliberately last so a component added later cannot escape it. It kills
   durations only: touching opacity or visibility here could strand a popover invisible
   but still focusable. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
