/* site.css — component layer. Depends on tokens.css.
 *
 * Three rules do most of the work of not looking like a generic store:
 *   1. depth is a hairline or an inset lip, never a blurred drop shadow
 *   2. nothing is heavier than 600, and body text is 500
 *   3. radii stop at 4px
 * Everything numeric is set in mono, so a spec table reads like the machine's DRO.
 */

/* ============================================================ webfont
 *
 * Space Grotesk, self-hosted. SIL Open Font License 1.1 — the licence text ships beside
 * the files in assets/fonts/OFL.txt, which is the whole of the compliance obligation.
 *
 * NOT loaded from the Google Fonts CDN. That would be a third-party request on every
 * page: an extra DNS lookup and TLS handshake in the critical path, every visitor's IP
 * handed to Google, and a font-src entry in the CSP. Self-hosting is same-origin, cached
 * with the rest of the site, and leaks nothing.
 *
 * One file per unicode-range covers BOTH weights because Space Grotesk is a variable
 * font — Google serves the same woff2 for 500 and 700, and the browser interpolates.
 *
 * latin-ext is declared but costs nothing on a normal page: unicode-range means the
 * browser fetches a file only when it actually has to render a glyph from that range.
 * It exists so a rupee sign or an accented name in a heading renders in the right face
 * instead of falling back mid-word.
 *
 * font-display: swap — text paints immediately in the fallback and reflows when the
 * face arrives. The alternative, invisible text for up to three seconds, is a worse
 * failure on a spec sheet someone is trying to read.
 */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk-latin.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: 500 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
                 U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================ base */

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

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

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--page-fg);
  font-family: var(--font-sans);
  font-size: var(--t-base);
  font-weight: var(--w-normal);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--w-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
}
h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-xl); }
h3 { font-size: var(--t-lg); font-weight: var(--w-medium); }
h4 { font-size: var(--t-base); font-weight: var(--w-medium); }

p { margin: 0 0 var(--s-4); }

a { color: var(--info); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

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

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

.wrap      { max-width: var(--w-content); margin-inline: auto; padding-inline: var(--s-5); }
.wrap-wide { max-width: var(--w-wide);    margin-inline: auto; padding-inline: var(--s-5); }

/* The document column — policy pages, contact, application notes, and the individual
 * tool pages to come. Anything read start to finish rather than scanned.
 *
 * This rule was MISSING while page.php used the class, so every editorial page rendered
 * with no max-width and no auto margin: the heading and lede ran the full width of the
 * monitor while the body text sat glued to the left edge with no padding at all.
 *
 * THE CONTAINER IS NOW THE SAME WIDTH AS EVERY OTHER PAGE, and only the reading column
 * inside it is narrow. Capping the container at one prose measure made these pages
 * noticeably narrower than the rest of the site: the masthead, the footer and every
 * product page are --w-wide, so /contact/ sat in a column about half that, with its
 * heading starting well inside the logo above it. Consistent edges matter more than a
 * perfectly centred block — the page reads as the same site now, and the body text is
 * still held to a measure by .prose. */
.wrap-narrow {
  max-width: var(--w-wide);
  margin-inline: auto;
  padding-inline: var(--s-5);
}
/* The reading column. --w-doc is derived from --measure plus the gutters, so the heading,
 * the lede and the body share one set of edges rather than stepping in and out. */
/* NO INNER CAPS. Every element on a document page flows to the container width — the
 * heading, the lede, the paragraphs and the tables all share one set of edges.
 *
 * This replaced three different widths on one page. The G-code reference had its heading
 * at --w-doc, its paragraphs at a 68ch measure and its tables at --w-content, so the
 * intro read as a narrow column tucked into the left of a wide page while the tables
 * beside it ran nearly twice as far.
 *
 * The reading-measure argument is real and it loses here on purpose. A 1400px line is
 * around 150 characters, which is past what is comfortable for long prose — Terms of Sale
 * is the page that pays for this. It was an explicit call: one width everywhere beats a
 * page whose parts disagree about where the edge is. Put the cap back on
 * `.wrap-narrow > .prose > :is(p, ul, ol)` if that trade ever looks wrong. */
/* `.wrap-narrow > .prose` is spelled out rather than left to `.wrap-narrow > *`. Both
 * would match, but `> *` ties with the plain `.prose` rule further down the file on
 * specificity and loses on source order — so the container stayed capped at the measure
 * and only the lede, which has its own rule here, actually widened. Naming the class
 * wins outright and does not depend on which rule comes first. */
.wrap-narrow > *,
.wrap-narrow > .prose,
.wrap-narrow > .prose > * { max-width: none; }
.wrap-narrow .lede { max-width: none; }
/* The measure applies to TEXT, not to everything.
 *
 * Capping .prose itself held its tables to 68ch too, and a reference table — the G-code
 * page is nine columns of codes and meanings — is unreadable squeezed into a reading
 * column. A child cannot exceed its parent's content box, so the cap has to go on the
 * text-level children and leave tables to take the width they need. */

.prose     { max-width: var(--measure); }
.stack > * + * { margin-top: var(--s-4); }

/* ============================================================ enquiry list */

.nav__enq { display: inline-flex; align-items: center; gap: var(--s-2); }
.nav__enqn {
  min-width: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--brand); color: var(--brand-on);
  font-size: var(--t-xs); line-height: 18px; text-align: center;
}

.enq { display: grid; gap: var(--s-5); }
/* Name, SKU, quantity, remove. The name takes the slack because it is the only column
   whose length varies; the other three are as wide as their contents and no wider. */
.enq__row {
  display: grid; grid-template-columns: 1fr auto auto auto;
  gap: var(--s-4); align-items: center;
  padding: var(--s-3) 0; border-bottom: var(--hair);
}
.enq__name { font-weight: var(--w-medium); }
.enq__sku  { color: var(--page-muted); font-size: var(--t-sm); }
.enq__qty  { display: flex; align-items: center; gap: var(--s-2); }
.enq__step { padding: 2px 10px; line-height: 1.4; }
.enq__n    { min-width: 2ch; text-align: center; }
.enq__drop { padding: 2px 10px; font-size: var(--t-sm); color: var(--page-muted); }
.enq__foot { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.enq__note { font-size: var(--t-sm); margin: 0; }
/* Clearing the list is destructive and rarely wanted, so it sits below the note,
   small and quiet, rather than competing with the two real actions. */
.enq__clearrow { margin: var(--s-5) 0 0; }
.enq__noscript { padding: var(--s-5); }

@media (max-width: 560px) {
  /* Four columns will not fit. Name and SKU stack on the left, the controls sit under
     them rather than being squeezed to an untappable width. */
  .enq__row { grid-template-columns: 1fr auto; row-gap: var(--s-2); }
  .enq__sku { grid-column: 1; }
  .enq__qty { grid-column: 1; }
  .enq__drop { grid-column: 2; grid-row: 1; align-self: start; }
}

/* Block separation inside a single column.
 *
 * These exist because a strict Content-Security-Policy cannot allow `style-src
 * 'unsafe-inline'`, and every one of these was previously an inline style="margin-top:…"
 * attribute in a template. Three named steps rather than a scale of utilities: the site
 * only ever needed these three, and a full spacing scale in the markup is how a
 * stylesheet stops being the place where layout decisions live. */
.gap-sm { margin-top: var(--s-3); }
.gap-md { margin-top: var(--s-6); }
.gap-lg { margin-top: var(--s-7); }

/* Uppercase, widely tracked, scarlet. Used above every section title and on
 * spec-group headers — it is the single most identifying mark on the site. */
.eyebrow {
  font-size: var(--t-xs);
  font-family: var(--font-display);
  font-weight: var(--w-medium);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  /* Scarlet, not the accent. The eyebrow is a masthead device, not a control, and the
     accent now means "you can click this". Keeping it scarlet is what stops the brand
     colour disappearing from the page entirely once the logo is the only place it lives.
     One line to change if it should follow the accent instead: use var(--brand). */
  color: var(--brand-2);
  margin: 0 0 var(--s-2);
}
.eyebrow--muted { color: var(--page-muted); }

.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.muted { color: var(--page-muted); }
/* A lede is body copy at a larger size, so it obeys the prose measure wherever it lands.
 * Without the max-width it took the width of its container — 1400px on the home and tools
 * pages, roughly twice a readable line. Inside .wrap-narrow the container is already one
 * measure, so this changes nothing there. */
.lede  { font-size: var(--t-md); line-height: var(--lh-snug); color: var(--page-muted);
         max-width: var(--measure); }

.rule { border: 0; border-top: var(--hair); margin: var(--s-7) 0; }

.section { padding-block: var(--s-8); }
.section + .section { border-top: var(--hair); }

.section-head { display: flex; align-items: baseline; justify-content: space-between;
                gap: var(--s-4); margin-bottom: var(--s-6); flex-wrap: wrap; }

/* ============================================================ header */

.topbar {
  font-size: var(--t-xs);
  background: var(--page-sunk);
  border-bottom: var(--hair);
  color: var(--page-muted);
}
.topbar__in { display: flex; gap: var(--s-5); align-items: center;
              justify-content: space-between; min-height: 34px; flex-wrap: wrap; }
.topbar a { color: inherit; }

.header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--page-bg) 92%, transparent);
  backdrop-filter: saturate(150%) blur(8px);
  border-bottom: var(--hair);
}
.header__in { display: flex; align-items: center; gap: var(--s-6); min-height: var(--nav-h); }

.brand { display: flex; align-items: baseline; gap: var(--s-2);
         font-weight: var(--w-display); letter-spacing: -0.02em;
         font-size: var(--t-md); color: var(--page-fg); }
.brand:hover { text-decoration: none; }
.brand__mark { width: 22px; height: 22px; align-self: center; }
.brand__tag { font-size: var(--t-xs); font-weight: var(--w-normal);
              letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
              color: var(--page-muted); }

.nav { display: flex; gap: var(--s-5); margin-inline-start: auto; align-items: center; }
.nav a {
  color: var(--page-fg);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  padding-block: var(--s-2);
  border-bottom: 2px solid transparent;
}
.nav a:hover { text-decoration: none; border-bottom-color: var(--page-hair); }
.nav a[aria-current] { border-bottom-color: var(--brand); color: var(--brand); }

/* Mobile navigation.
 * A <details> disclosure, so it needs no JavaScript and keeps the client JS floor at
 * zero on every page. Hidden above 900px where .nav takes over; .nav hides below it. */
.menu { position: relative; margin-inline-start: auto; display: none; }
.menu > summary {
  list-style: none; cursor: pointer;
  display: grid; place-items: center; width: 38px; height: 38px;
  border: var(--hair); border-radius: var(--r-md);
  background: var(--page-raised); box-shadow: var(--lip);
}
.menu > summary::-webkit-details-marker { display: none; }
.menu__panel {
  position: absolute; inset-inline-end: 0; top: calc(100% + var(--s-2));
  min-width: 220px; z-index: 30;
  background: var(--page-raised); border: var(--hair); border-radius: var(--r-md);
  box-shadow: var(--lip); padding: var(--s-2);
  display: grid;
}
.menu__panel a {
  color: var(--page-fg); font-size: var(--t-sm); font-weight: var(--w-medium);
  padding: var(--s-3); border-radius: var(--r-sm);
}
.menu__panel a:hover { background: var(--page-sunk); text-decoration: none; }
.menu__panel a[aria-current] { color: var(--brand); }

.searchbox {
  display: flex; align-items: center; gap: var(--s-2);
  border: var(--hair); border-radius: var(--r-md);
  background: var(--page-raised); box-shadow: var(--lip);
  padding: 5px var(--s-3); min-width: 210px;
}
.searchbox input { border: 0; background: none; color: inherit; font: inherit;
                   font-size: var(--t-sm); width: 100%; outline: none; }

.icon-btn { display: grid; place-items: center; width: 32px; height: 32px;
            border: var(--hair); border-radius: var(--r-md);
            background: var(--page-raised); box-shadow: var(--lip);
            color: var(--page-fg); cursor: pointer; }

/* ============================================================ buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  font: inherit; font-size: var(--t-sm); font-weight: var(--w-medium);
  padding: 10px var(--s-5);
  border: 1px solid transparent; border-radius: var(--r-md);
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn--primary   { background: var(--brand); color: var(--brand-on); box-shadow: var(--lip); }
.btn--primary:hover { background: var(--brand-ink); }
.btn--ghost     { background: var(--page-raised); color: var(--page-fg);
                  border-color: var(--page-hair); box-shadow: var(--lip); }
.btn--ghost:hover { border-color: var(--page-muted); }
.btn--wa        { background: #1f8f4d; color: #fff; box-shadow: var(--lip); }
.btn--wa:hover  { background: #197a41; }
.btn--block     { width: 100%; }
.btn--lg        { padding: 13px var(--s-6); font-size: var(--t-base); }

/* ============================================================ badges */

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--t-xs); font-weight: var(--w-medium);
  letter-spacing: 0.04em; text-transform: uppercase;
  padding: 3px 7px; border-radius: var(--r-sm);
  border: 1px solid currentColor; color: var(--page-muted);
}
.badge--stock { color: var(--ok); }
.badge--mto   { color: var(--warn); }
.badge--own   { color: var(--brand); background: var(--brand-wash); border-color: transparent; }
.badge::before { content: ""; width: 5px; height: 5px; border-radius: 50%;
                 background: currentColor; }
.badge--own::before { display: none; }

/* ============================================================ hero */

.hero { border-bottom: var(--hair); }
.hero__in { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: var(--s-8);
            align-items: center; padding-block: var(--s-9); }
.hero h1 { font-size: var(--t-3xl); margin-bottom: var(--s-4); }
.hero__cta { display: flex; gap: var(--s-4); flex-wrap: wrap; align-items: center;
             margin-top: var(--s-6); }

/* Two or three product photos beside the main button — plain <img> elements linking to
   their own product pages, no script involved. Square, small, and quiet: the point is a
   glance of "here is what's in that category", not a second hero image competing with
   the DRO panel for attention. */
.hero__preview { display: flex; gap: var(--s-2); }
.hero__preview a {
  /* var(--r) — no such token, so this radius silently did nothing until it was spotted. */
  display: block; width: 72px; height: 72px; border-radius: var(--r-sm);
  overflow: hidden; background: var(--media-bg); border: var(--hair);
  transition: border-color .15s ease;
}
.hero__preview a:hover, .hero__preview a:focus-visible { border-color: var(--brand); }
.hero__preview img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* The anodised panel. This is the product's real enclosure finish, used as the hero
 * surface — it does the job that product photography would, and we do not have
 * product photography. Etched legend, machined chamfer, no gloss. */
.panel {
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--anod-graphite) 92%, #fff) 0%,
              var(--anod-graphite) 42%,
              color-mix(in srgb, var(--anod-graphite) 88%, #000) 100%);
  border: 1px solid #0e1013;
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), inset 0 -1px 0 rgba(0,0,0,0.5);
  color: var(--anod-etch);
  padding: var(--s-6);
  font-family: var(--font-mono);
}
.panel__legend { font-size: var(--t-xs); letter-spacing: var(--ls-eyebrow);
                 text-transform: uppercase; color: var(--anod-etch-dim);
                 border-bottom: 1px solid rgba(255,255,255,0.08);
                 padding-bottom: var(--s-3); margin-bottom: var(--s-4);
                 display: flex; justify-content: space-between; }

/* DRO readout — mono, tabular, axis-coloured. Deliberately the same visual grammar
 * as the controller's own screen. */
.dro { display: grid; gap: var(--s-2); }
.dro__row { display: grid; grid-template-columns: 22px 1fr auto;
            align-items: baseline; gap: var(--s-3);
            font-size: var(--t-lg); font-variant-numeric: tabular-nums; }
.dro__axis { font-size: var(--t-sm); font-weight: var(--w-medium); }
.dro__row[data-axis="X"] .dro__axis { color: var(--axis-x); }
.dro__row[data-axis="Y"] .dro__axis { color: var(--axis-y); }
.dro__row[data-axis="Z"] .dro__axis { color: var(--axis-z); }
.dro__row[data-axis="A"] .dro__axis { color: var(--axis-a); }
.dro__val { text-align: right; }
.dro__unit { font-size: var(--t-sm); color: var(--anod-etch-dim); }

/* ============================================================ trust strip */

.trust { border-bottom: var(--hair); background: var(--page-sunk); }
.trust__in { display: grid; grid-template-columns: repeat(4, 1fr); }
.trust__item { padding: var(--s-5) var(--s-4); border-inline-start: var(--hair); }
.trust__item:first-child { border-inline-start: 0; }
.trust__k { font-size: var(--t-sm); font-weight: var(--w-medium); }
.trust__v { font-size: var(--t-xs); color: var(--page-muted); }

/* ============================================================ product card */

/* Flush hairline grid: cards share borders instead of floating apart. Reads as a
 * parts catalogue, which is what it is. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  border-top: var(--hair); border-inline-start: var(--hair);
}
.card {
  border-inline-end: var(--hair); border-bottom: var(--hair);
  padding: var(--s-5);
  display: flex; flex-direction: column; gap: var(--s-3);
  background: var(--page-bg);
  color: inherit;
}
.card:hover { background: var(--page-raised); text-decoration: none; }
.card__media { aspect-ratio: 4 / 3; background: var(--media-bg);
               border: var(--hair); border-radius: var(--r-sm);
               display: grid; place-items: center; overflow: hidden; }
.card__media img { width: 100%; height: 100%; object-fit: contain; }
.card__name { font-size: var(--t-base); font-weight: var(--w-medium);
              line-height: var(--lh-snug); }
.card__sku  { font-size: var(--t-xs); color: var(--page-muted); }
.card__spec { font-size: var(--t-xs); color: var(--page-muted);
              display: flex; gap: var(--s-3); flex-wrap: wrap; margin-top: auto; }
.card__spec b { font-weight: var(--w-medium); color: var(--page-fg);
                font-family: var(--font-mono); }
.card__foot { display: flex; align-items: baseline; justify-content: space-between;
              gap: var(--s-3); padding-top: var(--s-3); border-top: var(--hair); }
.card__price { font-family: var(--font-mono); font-size: var(--t-md);
               font-weight: var(--w-medium); }
.card__price--ask { font-family: var(--font-sans); font-size: var(--t-sm);
                    color: var(--page-muted); }

/* ============================================================ category card */

/* Compact thumb + name + count. Same flush hairline grid as the product cards, so a
 * category list and a product list read as the same object at different zoom levels. */
.cats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  border-top: var(--hair); border-inline-start: var(--hair);
}
.catcard {
  border-inline-end: var(--hair); border-bottom: var(--hair);
  padding: var(--s-4);
  display: grid; grid-template-columns: 58px 1fr; gap: var(--s-4);
  align-items: center; color: inherit;
}
.catcard:hover { background: var(--page-raised); text-decoration: none; }
.catcard__img { aspect-ratio: 1; background: var(--media-bg); border: var(--hair);
                border-radius: var(--r-sm); overflow: hidden;
                display: grid; place-items: center; }
.catcard__img img { width: 100%; height: 100%; object-fit: contain; }
.catcard__n { font-weight: var(--w-medium); font-size: var(--t-sm);
              line-height: var(--lh-snug); }
.catcard__c { font-family: var(--font-mono); font-size: var(--t-xs);
              color: var(--page-muted); }

/* ============================================================ breadcrumbs */

.crumbs { font-size: var(--t-xs); color: var(--page-muted);
          padding-block: var(--s-4); display: flex; gap: var(--s-2); flex-wrap: wrap; }
.crumbs a { color: inherit; }
.crumbs span[aria-hidden] { opacity: 0.5; }

/* ============================================================ product page */

/* Content takes the slack; the buybox is as wide as it needs to be and no wider.
 *
 * It used to be minmax(0, 1.15fr) / minmax(300px, 0.85fr), which handed the buybox 40% of
 * the page — 547px at a 1400px layout, for a price and three buttons — and squeezed the
 * gallery, description and spec table into 741px. A proportional column for fixed content
 * gets more generous the wider the screen, which is exactly backwards. */
.pdp { display: grid; grid-template-columns: minmax(0, 1fr) 360px;
       gap: var(--s-8); align-items: start; padding-bottom: var(--s-8); }

/* Everything in the product column fills the product column.
 *
 * Three separate rules cap text at --measure (68ch) so long-form pages read properly:
 * .prose, .lede, and the FAQ answer body. All three are correct on a full-width page and
 * all three are wrong here, because this column is ALREADY narrower than the page — so
 * the cap applied a second, smaller limit and left the summary, the description and every
 * answer stopping short while the gallery and the spec table beside them filled the
 * column. One block short of its neighbours reads as a bug; three of them reads as a
 * broken page.
 *
 * Listed together rather than overridden one at a time, so the next capped rule added
 * anywhere is a one-line change here instead of another report that something does not
 * flow to full width. Inside a column narrower than the page, the column is the measure.
 *
 * Long-form pages keep every cap — see .wrap-narrow, where the container is one measure
 * wide and none of this changes anything. */
.pdp .prose,
.pdp .lede,
.pdp .faq details > div { max-width: none; }

/* Clickable gallery, zero JavaScript. A hidden radio group plus <label for> is what makes
 * the thumbnails work: clicking a thumbnail checks its radio, and the positional
 * :nth-child rules below show the matching slide and highlight the matching thumb. The
 * only script a product page can load is video.js, and only when a video is attached.
 *
 * :nth-child, NOT :nth-of-type, and the difference matters. A video slide is an <a> while
 * a photo slide is a <label> (product.php explains why), and :nth-of-type counts position
 * among siblings OF THE SAME TAG — so with one video among four photos the rules would
 * pair radio 3 with the wrong slide and the gallery would show the wrong picture. Counting
 * children makes the tag irrelevant. Both containers hold nothing but slides, so the two
 * are otherwise identical here.
 *
 * The slides are DISPLAY:NONE, not merely hidden, and that is what keeps this cheap:
 * every evergreen browser defers fetching an <img> inside a display:none ancestor until
 * it is actually shown, so a five-photo product does not download five 1200px images on
 * page load — only the one that starts checked, exactly like an eager JS-driven gallery,
 * but with no script to ship or to go wrong. */
.gallery__radio {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.gallery__main { position: relative; aspect-ratio: 4 / 3; background: var(--media-bg);
                 border: var(--hair); border-radius: var(--r-md); overflow: hidden; }
.gallery__slide { display: none; place-items: center; width: 100%; height: 100%; cursor: pointer; }
.gallery__slide img { width: 100%; height: 100%; object-fit: contain; }
.gallery__thumbs { display: flex; gap: var(--s-2); margin-top: var(--s-3); }
.gallery__thumb { display: block; width: 62px; aspect-ratio: 1; background: var(--media-bg);
                   border: var(--hair); border-radius: var(--r-sm); cursor: pointer;
                   overflow: hidden; box-sizing: border-box; }
.gallery__thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* One rule per slide position. Ten is headroom — no product in the catalogue carries
 * anywhere near that many photos — and a fixed, static block here is simpler and safer
 * than generating per-product CSS just to name the Nth radio. */
.gallery__radio:nth-of-type(1):checked ~ .gallery__main .gallery__slide:nth-child(1),
.gallery__radio:nth-of-type(2):checked ~ .gallery__main .gallery__slide:nth-child(2),
.gallery__radio:nth-of-type(3):checked ~ .gallery__main .gallery__slide:nth-child(3),
.gallery__radio:nth-of-type(4):checked ~ .gallery__main .gallery__slide:nth-child(4),
.gallery__radio:nth-of-type(5):checked ~ .gallery__main .gallery__slide:nth-child(5),
.gallery__radio:nth-of-type(6):checked ~ .gallery__main .gallery__slide:nth-child(6),
.gallery__radio:nth-of-type(7):checked ~ .gallery__main .gallery__slide:nth-child(7),
.gallery__radio:nth-of-type(8):checked ~ .gallery__main .gallery__slide:nth-child(8),
.gallery__radio:nth-of-type(9):checked ~ .gallery__main .gallery__slide:nth-child(9),
.gallery__radio:nth-of-type(10):checked ~ .gallery__main .gallery__slide:nth-child(10) {
  display: grid;
}
.gallery__radio:nth-of-type(1):checked ~ .gallery__thumbs .gallery__thumb:nth-child(1),
.gallery__radio:nth-of-type(2):checked ~ .gallery__thumbs .gallery__thumb:nth-child(2),
.gallery__radio:nth-of-type(3):checked ~ .gallery__thumbs .gallery__thumb:nth-child(3),
.gallery__radio:nth-of-type(4):checked ~ .gallery__thumbs .gallery__thumb:nth-child(4),
.gallery__radio:nth-of-type(5):checked ~ .gallery__thumbs .gallery__thumb:nth-child(5),
.gallery__radio:nth-of-type(6):checked ~ .gallery__thumbs .gallery__thumb:nth-child(6),
.gallery__radio:nth-of-type(7):checked ~ .gallery__thumbs .gallery__thumb:nth-child(7),
.gallery__radio:nth-of-type(8):checked ~ .gallery__thumbs .gallery__thumb:nth-child(8),
.gallery__radio:nth-of-type(9):checked ~ .gallery__thumbs .gallery__thumb:nth-child(9),
.gallery__radio:nth-of-type(10):checked ~ .gallery__thumbs .gallery__thumb:nth-child(10) {
  border-color: var(--brand);
}
.gallery__radio:nth-of-type(1):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(1),
.gallery__radio:nth-of-type(2):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(2),
.gallery__radio:nth-of-type(3):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(3),
.gallery__radio:nth-of-type(4):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(4),
.gallery__radio:nth-of-type(5):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(5),
.gallery__radio:nth-of-type(6):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(6),
.gallery__radio:nth-of-type(7):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(7),
.gallery__radio:nth-of-type(8):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(8),
.gallery__radio:nth-of-type(9):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(9),
.gallery__radio:nth-of-type(10):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-child(10) {
  outline: 2px solid var(--brand); outline-offset: 1px;
}

/* A video slide. The poster is an ordinary photograph from this server and the player is
 * built on click by video.js — so what has to be styled here is the promise that a click
 * will do something, and then the box the player lands in.
 *
 * The badge is drawn, not fetched: a disc and a CSS triangle. An icon file would be a
 * request and a second asset to keep in step, for a shape that is three lines of border. */
/* Keyed on .video, not on .gallery__slide.video: partials/media.php emits the same
 * wrapper for the product gallery and for the home page carousel, so the badge has to
 * work wherever that partial is used. A rule that names one component would have left the
 * carousel with a badge that does not light up on hover and nothing to say why. */
.video { position: relative; text-decoration: none; }
.video__badge {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 64px; border-radius: 50%;
  background: var(--video-badge);
  /* The ring keeps the disc off a dark photograph, where a dark circle on a dark machine
   * would be a smudge rather than a button. */
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55);
  transition: background 120ms ease, transform 120ms ease;
}
.video__badge::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  /* Optically centred, not geometrically: a triangle's visual mass sits left of its
   * bounding box, so -40% instead of -50% nudges it right and it stops looking off-centre. */
  transform: translate(-40%, -50%);
  border-style: solid; border-width: 11px 0 11px 19px;
  border-color: transparent transparent transparent var(--video-fg);
}
.video:hover .video__badge,
.video:focus-visible .video__badge {
  background: var(--video-badge-hi); transform: translate(-50%, -50%) scale(1.06);
}
.video:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* On the dark hero panel the page's brand blue is not the focus colour — it is nearly
 * invisible against the slab. --slab-hi is the same decision the ghost button made. */
.reel .video:focus-visible { outline-color: var(--slab-hi); }
@media (prefers-reduced-motion: reduce) {
  .video__badge { transition: none; }
  .video:hover .video__badge,
  .video:focus-visible .video__badge { transform: translate(-50%, -50%); }
}
/* Once playing, the wrapper holds an iframe instead of a picture. It keeps the display it
 * already had, so the frame simply fills the space the poster occupied. */
.video__frame { width: 100%; height: 100%; border: 0; display: block; }
.video--playing { background: var(--anod-black); }
.gallery__slide.video--playing { display: grid; }
/* Which thumbnail is the video. A small corner triangle rather than a second badge —
 * at 62px a centred disc covers the picture it is meant to preview. */
.gallery__thumb--video { position: relative; }
.gallery__thumb--video::after {
  content: ""; position: absolute; right: 3px; bottom: 3px;
  border-style: solid; border-width: 5px 0 5px 8px;
  border-color: transparent transparent transparent var(--video-fg);
  filter: drop-shadow(0 0 1px rgba(16, 19, 26, 0.9));
}

/* At-a-glance strip: the six numbers a buyer scans before reading anything. */
.glance { display: grid; grid-template-columns: repeat(3, 1fr);
          border-top: var(--hair); border-inline-start: var(--hair); margin: var(--s-6) 0; }
.glance__cell { border-inline-end: var(--hair); border-bottom: var(--hair);
                padding: var(--s-3) var(--s-4); }
.glance__k { font-size: var(--t-xs); letter-spacing: 0.06em; text-transform: uppercase;
             color: var(--page-muted); }
.glance__v { font-family: var(--font-mono); font-size: var(--t-md);
             font-weight: var(--w-medium); }
.glance__v small { font-size: var(--t-xs); color: var(--page-muted);
                   font-weight: var(--w-normal); }

/* Buy box — sticky, hairline, no shadow. No cart: WhatsApp first, phone second,
 * quote third, in that visual order because that is how this market actually buys. */
.buybox { position: sticky; top: calc(var(--nav-h) + var(--s-4));
          border: var(--hair); border-radius: var(--r-md);
          background: var(--page-raised); box-shadow: var(--lip); padding: var(--s-5); }
.buybox__price { font-family: var(--font-mono); font-size: var(--t-2xl);
                 font-weight: var(--w-medium); line-height: 1; }
.buybox__ask   { font-size: var(--t-lg); font-weight: var(--w-medium); }
.buybox__note  { font-size: var(--t-xs); color: var(--page-muted); margin-top: var(--s-2); }
.buybox__cta   { display: grid; gap: var(--s-2); margin-top: var(--s-5); }
.buybox__meta  { margin-top: var(--s-5); padding-top: var(--s-4); border-top: var(--hair);
                 font-size: var(--t-sm); display: grid; gap: var(--s-2); }
.buybox__meta div { display: flex; justify-content: space-between; gap: var(--s-3); }
.buybox__meta dt { color: var(--page-muted); }
.buybox__meta dd { margin: 0; font-family: var(--font-mono); }

/* ============================================================ spec table */

.spectable { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.spectable caption { text-align: left; font-size: var(--t-xs);
                     letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
                     color: var(--brand); padding: var(--s-5) 0 var(--s-2); }
.spectable th, .spectable td { text-align: left; padding: 9px var(--s-3);
                               border-bottom: var(--hair); vertical-align: baseline; }
.spectable th { font-weight: var(--w-normal); color: var(--page-muted); width: 42%; }
.spectable td { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.spectable td .unit { color: var(--page-muted); }
.spectable tbody tr:hover { background: var(--page-raised); }

/* ============================================================ downloads */

.dl { display: grid; border-top: var(--hair); }
.dl__row { display: grid; grid-template-columns: auto 1fr auto auto; gap: var(--s-4);
           align-items: center; padding: var(--s-3) var(--s-2);
           border-bottom: var(--hair); color: inherit; }
.dl__row:hover { background: var(--page-raised); text-decoration: none; }
.dl__kind { font-size: var(--t-xs); letter-spacing: 0.06em; text-transform: uppercase;
            color: var(--brand); width: 84px; }
.dl__title { font-weight: var(--w-medium); font-size: var(--t-sm); }
.dl__meta { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--page-muted); }

/* ============================================================ faq */

.faq { border-top: var(--hair); }
.faq details { border-bottom: var(--hair); }
.faq summary { cursor: pointer; padding: var(--s-4) 0; font-weight: var(--w-medium);
               font-size: var(--t-base); list-style: none; display: flex;
               justify-content: space-between; gap: var(--s-4); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font-family: var(--font-mono); color: var(--brand); }
.faq details[open] summary::after { content: "\2212"; }
.faq details > div { padding-bottom: var(--s-4); max-width: var(--measure);
                     color: var(--page-muted); }

/* ============================================================ tool card */

.tools { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
         border-top: var(--hair); border-inline-start: var(--hair); }
.tool { border-inline-end: var(--hair); border-bottom: var(--hair); padding: var(--s-5);
        display: flex; flex-direction: column; gap: var(--s-2); color: inherit; }
.tool:hover { background: var(--page-raised); text-decoration: none; }
.tool__name { font-weight: var(--w-medium); }
.tool__desc { font-size: var(--t-sm); color: var(--page-muted); }
.tool__free { font-size: var(--t-xs); letter-spacing: var(--ls-eyebrow);
              text-transform: uppercase; color: var(--ok); margin-top: auto; }

/* ============================================================ footer */

.footer { border-top: var(--hair); background: var(--page-sunk); margin-top: var(--s-9); }
/* Brand, Products, Regd., Sales & Factory, Policies — five columns so the two addresses
   sit side by side rather than stacked in one narrow slot (which doubled that slot's
   height and, since a grid row is as tall as its tallest cell, dragged the whole footer
   down with it). The two address columns get a little extra width for the longer lines. */
/* EQUAL COLUMNS, however many there are.
 *
 * This was five hand-tuned tracks — 1.3fr 0.8fr 1fr 1.1fr 0.8fr — proportioned for a
 * first column that held the logo, the tagline and a paragraph of blurb. That column is
 * gone, so the widths fitted nothing: five link lists in tracks of five different widths,
 * with the space between them visibly different at every seam.
 *
 * It was worse than untidy on live data. Every column here is conditional — Policies only
 * when a policy page is published, Registered Office only when that address is entered —
 * so a fixed five-track grid leaves EMPTY TRACKS whenever one is missing, and today, with
 * all four policy pages still drafts, four columns were being laid into five tracks.
 *
 * Flex rather than a grid, because the count is genuinely unknown at build time: one
 * basis, one grow factor, one gap. Every column ends the same width and every gap is the
 * same gap, whether there are three of them or five. */
.footer__in { display: flex; flex-wrap: wrap; gap: var(--s-7); padding-block: var(--s-8); }
.footer__in > div { flex: 1 1 160px; min-width: 0; }
.footer h4 { font-size: var(--t-xs); letter-spacing: var(--ls-eyebrow);
             text-transform: uppercase; color: var(--page-muted); margin-bottom: var(--s-3); }
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.footer a { color: var(--page-fg); font-size: var(--t-sm); }
/* Email addresses are rendered to a PNG at build time (generator/lib/emailimg.php). The
   file is 3x its logical size, so width/height attributes hold it at the intended size
   and it stays crisp on HiDPI. vertical-align keeps it sitting on the text baseline
   rather than hanging below it. */
.emailimg { vertical-align: -2px; max-width: 100%; height: auto; }

.footer__tagline { font-size: var(--t-sm); font-style: italic; color: var(--page-muted);
                   margin: var(--s-3) 0 0; }
.footer__blurb { font-size: var(--t-sm); color: var(--page-muted);
                 margin: var(--s-2) 0 0; max-width: 34ch; }
.footer__social { display: flex; flex-wrap: wrap; gap: var(--s-3);
                  margin: var(--s-4) 0 0; font-size: var(--t-sm); }
.footer address { font-style: normal; font-size: var(--t-sm); color: var(--page-muted);
                  margin-bottom: var(--s-3); line-height: 1.6; }
.footer__legal { border-top: var(--hair); padding-block: var(--s-4);
                 font-size: var(--t-xs); color: var(--page-muted);
                 display: flex; justify-content: space-between; gap: var(--s-4);
                 flex-wrap: wrap; }

/* ============================================================ responsive */

@media (max-width: 900px) {
  .hero__in { grid-template-columns: 1fr; padding-block: var(--s-7); gap: var(--s-6); }
  .hero h1 { font-size: var(--t-2xl); }
  .pdp { grid-template-columns: 1fr; gap: var(--s-6); }
  .buybox { position: static; }
  .trust__in { grid-template-columns: repeat(2, 1fr); }
  .trust__item:nth-child(3) { border-inline-start: 0; }
  /* Two per row, and NOT allowed to grow.
   *
   * A 160px basis fits three or four across at this width and wraps each address to six
   * lines. Half the row minus half the gap is exactly two per row — and flex-grow is 0
   * because with it an odd last column sits alone and stretches to the full width, so on
   * five columns the address block came out twice as wide as everything above it. */
  .footer__in { gap: var(--s-6); }
  .footer__in > div { flex: 0 1 calc(50% - (var(--s-6) / 2)); }
  /* Swap the inline nav for the disclosure menu — never leave the page with neither. */
  .nav { display: none; }
  .menu { display: block; }
  .searchbox { min-width: 0; flex: 1; }
}
@media (max-width: 560px) {
  .glance { grid-template-columns: repeat(2, 1fr); }
  .trust__in { grid-template-columns: 1fr; }
  .trust__item { border-inline-start: 0; border-top: var(--hair); }
  .trust__item:first-child { border-top: 0; }
}

@media print {
  .header, .topbar, .footer, .buybox__cta, .searchbox { display: none; }
  body { background: #fff; color: #000; }
}

/* ===========================================================================
 * COMPOSITION LAYER
 *
 * Everything above defines components. This section arranges them: the dark hero
 * slab, the capability grid, the wide catalogue card, and the hover behaviour that
 * makes a grid of links feel like a grid of links.
 *
 * It arrived as a reference mockup that appended two passes to this file, the second
 * overriding the first — the hero was styled light, then restyled dark, and both
 * copies shipped. What follows is the effective result of the two, merged once, with
 * every literal colour promoted to a token in tokens.css. If you are comparing
 * against that mockup and cannot find a rule, it is because it was dead.
 * ------------------------------------------------------------------------- */

/* Engineering paper. Two hairlines crossing at --blueprint-size, at a tint low enough
 * that it reads as texture rather than as a grid you could count. */
body {
  background-image:
    linear-gradient(var(--blueprint) 1px, transparent 1px),
    linear-gradient(90deg, var(--blueprint) 1px, transparent 1px);
  background-size: var(--blueprint-size) var(--blueprint-size);
}

.skip-link {
  position: fixed; z-index: 100; inset: var(--s-3) auto auto var(--s-3);
  padding: var(--s-2) var(--s-4); border-radius: var(--r-md);
  background: var(--brand); color: var(--brand-on);
  transform: translateY(-160%);
}
.skip-link:focus { transform: translateY(0); }

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

/* ---- masthead ----------------------------------------------------------- */
.topbar { background: var(--anod-black); color: var(--anod-etch);
          border-bottom-color: var(--anod-hair); }
.header { background: color-mix(in srgb, var(--page-raised) 94%, transparent); }
.header__in { min-height: 74px; }

/* The current-page rule sweeps in from the left and out to the right, so hovering
 * along the nav reads as one continuous movement rather than four separate ones. */
.nav a { position: relative; }
.nav a::after {
  content: ""; position: absolute; inset-inline: 0; bottom: 2px; height: 2px;
  background: var(--brand); transform: scaleX(0); transform-origin: right;
  transition: transform 180ms ease;
}
.nav a:hover::after,
.nav a[aria-current]::after { transform: scaleX(1); transform-origin: left; }

/* Two elements that set `display` and are also meant to be hideable.
 *
 * The UA stylesheet's `[hidden] { display: none }` loses to any author rule that sets
 * display on the same element — so `.menu__panel { display: grid }` kept the mobile menu
 * open at all times, and `.nav__enq { display: inline-flex }` put an "Enquiry" link in
 * the header of every page whether or not the list had anything in it. Both were hidden
 * in the markup and neither was hidden on screen.
 *
 * Verified in the preview: without the first rule the panel computes to display:grid with
 * its <details> closed. The second is the same mistake one component over. */
.menu:not([open]) > .menu__panel { display: none; }
.nav__enq[hidden] { display: none; }

/* ---- hero --------------------------------------------------------------- */
.hero {
  color: var(--slab-fg); border-bottom: 0;
  background:
    radial-gradient(circle at 76% 22%, var(--slab-glow), transparent 24rem),
    linear-gradient(118deg, var(--slab-bg-1) 0%, var(--slab-bg-2) 58%, var(--slab-bg-3) 100%);
}
.hero h1 { max-width: 13ch; text-wrap: balance; }
.hero .eyebrow { color: var(--slab-accent); }
.hero .lede { color: var(--slab-muted); font-size: clamp(1.05rem, 1.4vw, 1.25rem); }
.hero .btn--ghost {
  color: var(--slab-fg); background: rgba(255, 255, 255, 0.06);
  border-color: var(--slab-hair); box-shadow: none;
}
.hero .btn--ghost:hover { border-color: var(--slab-hi); background: rgba(255, 255, 255, 0.1); }
.hero__preview a { border-color: var(--slab-hair); }

/* ---- DRO panel ---------------------------------------------------------- */
.panel {
  position: relative; overflow: hidden; border-color: var(--slab-edge);
  box-shadow: 0 20px 45px rgba(25, 28, 34, 0.18), var(--lip);
}
/* A powered-on indicator, drawn rather than photographed: the label and the lamp are
 * two pseudo-elements, so it costs no markup and no request. */
.panel::before {
  content: "ONLINE"; position: absolute; right: var(--s-5); bottom: var(--s-4);
  color: var(--led); font: 500 10px/1 var(--font-mono); letter-spacing: 0.12em;
}
.panel::after {
  content: ""; position: absolute; right: calc(var(--s-5) + 49px); bottom: 17px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--led); box-shadow: 0 0 10px var(--led-glow);
}
.panel__body { display: grid; grid-template-columns: minmax(150px, 0.8fr) 1.2fr;
               gap: var(--s-5); align-items: stretch; }
.panel__body .dro { align-content: center; }
.panel__product {
  display: grid; grid-template-rows: 1fr auto; min-width: 0; overflow: hidden;
  background: var(--tile-bg); color: var(--page-fg); border: 1px solid var(--slab-edge);
}
.panel__product:hover { text-decoration: none; border-color: var(--slab-hi); }
.panel__product img { width: 100%; height: 150px; object-fit: contain; background: var(--media-bg); }
.panel__product > span { display: grid; gap: 2px; padding: var(--s-3);
                         border-top: 1px solid var(--tile-hair); }
.panel__product strong { font: var(--w-medium) var(--t-sm)/1.2 var(--font-display); }
.panel__product small { color: var(--page-muted); font-size: 10px; }

.dro__row { transition: background-color 160ms ease, transform 160ms ease; }
.dro__row:hover { background: rgba(255, 255, 255, 0.045); transform: translateX(2px); }

/* ---- hero carousel ------------------------------------------------------
 * Photographs and videos of machines running, inside the machine panel: footage above,
 * axis readout below, so the panel reads as an instrument showing its work rather than a
 * decorated box.
 *
 * ZERO JAVASCRIPT, the same radio-and-label trick as the product gallery. The slides are
 * display:none rather than merely transparent, which is what keeps a six-slide carousel
 * costing one image: every evergreen browser defers fetching an <img> inside a
 * display:none ancestor until it is shown.
 *
 * NO AUTO-ADVANCE, deliberately. Without script the only way to move it is a CSS keyframe
 * on the whole track, and a dot cannot steer an animation — so auto-advance would mean
 * giving up the controls. A hero that changes while someone is reading it is worse than
 * one they drive.
 *
 * The panel keeps its two-column tile-beside-readout layout when there is no carousel.
 * --reel is the other case, and there the reel is the ONLY thing in the body: the axis
 * readout was there to give the panel something to be, and a photograph of the machine
 * cutting does that better than four invented coordinates. So one full-width column,
 * and no gap to declare because there is nothing to sit beside. */
.panel__body--reel { grid-template-columns: 1fr; }
.reel { position: relative; min-width: 0; }
.reel__radio {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.reel__stage {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  border: 1px solid var(--slab-edge); border-radius: var(--r-sm);
  background: var(--anod-black);
}
.reel__slide { display: none; width: 100%; height: 100%; }
.reel__media { position: relative; display: block; width: 100%; height: 100%; }

/* ---- pictures that must FILL their box ---------------------------------
 * Not "be 100% of it" — fill it. The difference is invisible in the stylesheet and was
 * measured in the browser: the hero carousel rendered a 582px-tall photograph inside a
 * 328px window, and the product gallery a 476px one inside 459px.
 *
 * WHY width:100%;height:100% IS NOT ENOUGH. The image sits inside a grid whose row is
 * auto-sized, so its percentage height resolves against a grid area that is indefinite
 * while that layout is being decided — and an indefinite percentage height computes to
 * auto. The image then takes its intrinsic ratio, object-fit has no box to fit inside,
 * and overflow:hidden crops whatever sticks out. It looks deliberate. In the gallery it
 * was 17px and went unnoticed for a month; in the hero it was 256px and read as a
 * carousel that was not working.
 *
 * Absolute positioning against a positioned ancestor gives a definite size in BOTH axes,
 * which is the thing object-fit actually needs. The loaded video player gets the same
 * treatment for the same reason.
 *
 * These come after the component rules on purpose: identical specificity, so source order
 * is what makes them win. */
.gallery__slide, .reel__media { position: relative; }
.gallery__slide > img, .reel__media > img, .video__frame {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}
/* contain in the gallery, cover in the hero. A product shot must never be cropped — it is
 * the part being sold. The hero is photographs of work, framed as footage. */
.gallery__slide > img { object-fit: contain; }
.reel__media > img { object-fit: cover; }
.reel__cap {
  position: absolute; inset: auto 0 0 0; margin: 0; padding: var(--s-3) var(--s-4);
  font-size: var(--t-sm); color: var(--slab-fg);
  /* Its own scrim. A caption over a photograph has no known background, and this is the
   * same problem the play badge solves the same way. */
  background: linear-gradient(to top, rgba(11, 14, 20, 0.86), rgba(11, 14, 20, 0));
}
.reel__dots { display: flex; gap: var(--s-2); justify-content: center;
              margin-top: var(--s-2); }
/* A bar, not a dot, and padded. The dots are the only control this carousel has, and a
 * 6px-tall target is not a control on a touchscreen — so the padding makes the tap area
 * 22px tall while background-clip keeps the ink 6px. Nothing moves, and a thumb can hit
 * it.
 *
 * VISIBLE, which took a second attempt. They were 4px of --slab-hair (#3c4452) on a
 * #2b2e34 panel — about 1.4:1, which is a hairline you have to already know is there. The
 * person these were built for looked for them and reported them missing. 6px of
 * --anod-etch-dim is the same grammar as the panel's own etched legend and can actually be
 * found; the current one stays --brand-on-dim, which is nearly white. */
.reel__dot {
  /* box-sizing: content-box, and WITHOUT IT NOTHING IS PAINTED AT ALL.
   *
   * The reset sets border-box on everything, so `height: 6px` with `padding: 8px 0` is a
   * 6px box that already owes 16px to padding — the content box computes to zero, and
   * background-clip: content-box faithfully paints zero pixels. The element is still
   * 32x16 and still clickable, so it measures correctly, hit-tests correctly, and is
   * invisible. Measured in the browser: contentBoxHeight 0.
   *
   * This is why the carousel looked like it had no controls. The ink was never there. */
  box-sizing: content-box;
  width: 32px; height: 6px; padding: 8px 0; background-clip: content-box;
  border-radius: 3px; cursor: pointer;
  background-color: var(--anod-etch-dim); transition: background-color 140ms ease;
}
.reel__dot:hover { background-color: var(--slab-muted); }

/* One rule per position, six of them, exactly as the gallery does it — and :nth-child for
 * the same reason: what a slide CONTAINS varies (a plain picture, a link, a video), so
 * pairing by element type would misalign the moment two slides differed. The slide
 * wrapper is always a div to make that impossible to get wrong. */
.reel__radio:nth-of-type(1):checked ~ .reel__stage .reel__slide:nth-child(1),
.reel__radio:nth-of-type(2):checked ~ .reel__stage .reel__slide:nth-child(2),
.reel__radio:nth-of-type(3):checked ~ .reel__stage .reel__slide:nth-child(3),
.reel__radio:nth-of-type(4):checked ~ .reel__stage .reel__slide:nth-child(4),
.reel__radio:nth-of-type(5):checked ~ .reel__stage .reel__slide:nth-child(5),
.reel__radio:nth-of-type(6):checked ~ .reel__stage .reel__slide:nth-child(6) {
  display: block;
}
.reel__radio:nth-of-type(1):checked ~ .reel__dots .reel__dot:nth-child(1),
.reel__radio:nth-of-type(2):checked ~ .reel__dots .reel__dot:nth-child(2),
.reel__radio:nth-of-type(3):checked ~ .reel__dots .reel__dot:nth-child(3),
.reel__radio:nth-of-type(4):checked ~ .reel__dots .reel__dot:nth-child(4),
.reel__radio:nth-of-type(5):checked ~ .reel__dots .reel__dot:nth-child(5),
.reel__radio:nth-of-type(6):checked ~ .reel__dots .reel__dot:nth-child(6) {
  /* background-COLOR, not the shorthand: `background:` resets background-clip to
   * border-box, and the checked dot would then paint its whole 20px tap area. */
  background-color: var(--brand-on-dim);
}
.reel__radio:nth-of-type(1):focus-visible ~ .reel__dots .reel__dot:nth-child(1),
.reel__radio:nth-of-type(2):focus-visible ~ .reel__dots .reel__dot:nth-child(2),
.reel__radio:nth-of-type(3):focus-visible ~ .reel__dots .reel__dot:nth-child(3),
.reel__radio:nth-of-type(4):focus-visible ~ .reel__dots .reel__dot:nth-child(4),
.reel__radio:nth-of-type(5):focus-visible ~ .reel__dots .reel__dot:nth-child(5),
.reel__radio:nth-of-type(6):focus-visible ~ .reel__dots .reel__dot:nth-child(6) {
  outline: 2px solid var(--slab-hi); outline-offset: 3px;
}

/* ---- trust strip -------------------------------------------------------- */
.trust__item { position: relative; }
.trust__item::before {
  content: ""; position: absolute; top: var(--s-5); left: 0;
  width: 2px; height: 28px; background: var(--brand);
}
.trust__item:first-child::before { background: var(--brand-2); }
.trust__k { font-family: var(--font-display); }

/* ---- cards -------------------------------------------------------------- */
.card, .catcard {
  background: color-mix(in srgb, var(--page-raised) 82%, transparent);
  transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
}
.card:hover, .catcard:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--brand) 38%, var(--page-hair));
  box-shadow: 0 14px 30px rgba(30, 38, 58, 0.08);
}
.card__media img, .catcard__img img { transition: transform 260ms ease; }
.card:hover .card__media img,
.catcard:hover .catcard__img img { transform: scale(1.025); }
.card__name { font-family: var(--font-display); }

/* ---- capability grid ---------------------------------------------------- */
.capability { background: var(--media-bg); }
.capability .section-head { align-items: end; }
.capability .section-head > .lede { max-width: 46ch; font-size: var(--t-base); }
.capability__grid { display: grid; grid-template-columns: repeat(3, 1fr); border: var(--hair); }
.capability__item {
  min-height: 148px; display: grid; grid-template-columns: auto 1fr auto;
  align-items: start; gap: var(--s-4); padding: var(--s-5);
  color: inherit; border-inline-start: var(--hair);
  transition: background-color 160ms ease, color 160ms ease;
}
.capability__item:first-child { border-inline-start: 0; }
.capability__item:hover { color: var(--brand-on); background: var(--brand); text-decoration: none; }
.capability__no { color: var(--brand); font-size: var(--t-xs); }
.capability__item strong { display: block; font: var(--w-display) var(--t-lg)/1.2 var(--font-display); }
.capability__item small { display: block; margin-top: var(--s-3);
                          color: var(--page-muted); line-height: 1.5; }
.capability__item > span:last-child { font-size: var(--t-lg); color: var(--brand); }
.capability__item:hover .capability__no { color: var(--brand-on-dim); }
.capability__item:hover small { color: var(--brand-on-dim); }
.capability__item:hover > span:last-child { color: var(--brand-on); }

/* ---- support band ------------------------------------------------------- */
.section--support { background: var(--brand-wash); }
.support-band { display: grid; grid-template-columns: 1.2fr 1fr auto;
                gap: var(--s-7); align-items: center; }
.support-band p { margin: 0; color: var(--page-muted); }
.support-band .eyebrow { color: var(--brand-2); margin-bottom: var(--s-2); }

/* ---- the wide card -----------------------------------------------------
 * Home and category both drop the multi-column grid for a single column of wide
 * cards: photo on the left, name, description and specs on the right. A catalogue of
 * 152 industrial parts is a list of decisions, and a decision needs the numbers next
 * to the picture, not three cards abreast with a name under each. */
/* card.php always writes the summary and the flagged specs. A narrow card has no room
 * for either, so they are off by default and the two wide layouts below switch them on.
 * One markup path, the stylesheet decides — the alternative is a template that has to be
 * told which grid it is being rendered into. */
.card__details > p,
.card .card__spec { display: none; }
.featured-products .card__details > p { display: block; }
.featured-products .card .card__spec,
.catalogue-page .card .card__spec { display: flex; }

.featured-products { background: var(--page-cool); }
/* A real grid, filling the width.
 *
 * This was minmax(0, 940px) — one column capped at 940px inside a 1400px container, so the
 * featured block sat in the left two thirds of the home page with the rest empty. On a
 * product page that space is the buy box; here it was nothing, and a section that stops
 * two thirds of the way across reads as a layout that failed rather than a choice.
 *
 * Two across at desktop, matching the category archive. auto-fit rather than a fixed count
 * so a narrow window steps down to one without a second breakpoint. */
.featured-products .grid {
  border: 0;
  /* SPACED, not flush. The catalogue's cards share hairlines on purpose — that reads as a
   * parts list. These do not: each already carries its own full border on a tinted
   * section background, so with no gap the two of them welded into one block with a
   * double rule down the middle. 24px, a little more than the category archive's 16,
   * because these cards are half as wide again and the space has to read as deliberate
   * rather than as a seam. */
  gap: var(--s-5);
  /* 620px, which yields two across at desktop and one below about 1300px. Not smaller: the
   * card is photo-beside-details and its media column alone has a 280px minimum, so at
   * three across the details got 165px — narrower than the order codes it has to hold.
   * The gap comes out of the same 1352px of content width, so the step down to one column
   * now happens 24px sooner. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 620px), 1fr));
}
/* Three named rows, matching .catalogue-page below. The mockup nested the spec row inside
 * the details block here and made it a direct child of the card on the category page —
 * fine when the two pages are hand-written HTML, impossible when they share one partial.
 * Both layouts name a `specs` area instead; a card grid that omitted it dumped the specs
 * into an implicit row outside the card. */
.featured-products .card {
  display: grid; grid-template-columns: minmax(280px, 0.9fr) 1.1fr;
  grid-template-areas: "media details" "media specs" "media foot";
  padding: 0; gap: 0; border: 1px solid var(--page-hair-cool); background: var(--media-bg);
}
.featured-products .card__spec { grid-area: specs; padding: 0 var(--s-6); }
.featured-products .card__media { grid-area: media; border: 0;
                                  border-inline-end: var(--hair); border-radius: 0; }
.featured-products .card__details { grid-area: details; padding: var(--s-6) var(--s-6) var(--s-4); }
.featured-products .card__name { font-size: var(--t-lg); }
.featured-products .card__details > p { color: var(--page-muted); margin: var(--s-4) 0 0; max-width: 48ch; }
.featured-products .card__foot { grid-area: foot; margin: var(--s-5) var(--s-6) 0;
                                 padding-bottom: var(--s-5); }

.catalogue-page { padding-bottom: var(--s-8); }
.catalogue-head {
  align-items: end; margin: var(--s-3) 0 var(--s-6); padding: var(--s-6);
  background: linear-gradient(120deg, var(--brand-wash), var(--page-cool-lift));
  border-left: 4px solid var(--brand);
}
.catalogue-head .lede { max-width: 60ch; }
/* TWO COLUMNS, filling the page.
 *
 * This was a single column capped at 820px inside a --w-wide container, which left the
 * whole archive hugging the left edge with half the screen empty beside it. On a product
 * page that space is the buy box; a category page has no buy box, so the same layout just
 * looked unfinished — the page looked like something had failed to load.
 *
 * Two columns rather than the three or four an auto-fill grid would give, because the card
 * keeps its photo-beside-details shape and the specs line under it: at three across, the
 * details column is narrower than the order code it has to hold. */
.catalogue-page .grid {
  border: 0; gap: var(--s-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.catalogue-page .card {
  display: grid; grid-template-columns: minmax(140px, 0.62fr) 1.38fr;
  grid-template-areas: "media details" "media specs" "media foot";
  gap: 0; padding: 0; background: var(--media-bg);
  border: var(--hair); box-shadow: var(--lip);
}
.catalogue-page .card__media { grid-area: media; border: 0;
                               border-inline-end: var(--hair); border-radius: 0; }
/* No photograph: one column, and the details take the whole card. The alternative is an
 * empty white rectangle where the picture would be, which reads as a failed image. */
.catalogue-page .card--nophoto,
.featured-products .card--nophoto {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "details" "specs" "foot";
}
.catalogue-page .card__details { grid-area: details; padding: var(--s-5) var(--s-5) var(--s-2); }
.catalogue-page .card__spec { grid-area: specs; padding: var(--s-3) var(--s-5); align-self: center; }
.catalogue-page .card__foot { grid-area: foot; margin: 0 var(--s-5); padding-bottom: var(--s-4); }

/* ---- product page ------------------------------------------------------- */
.pdp > div > h1 { font-size: clamp(2rem, 4vw, 3.4rem); max-width: 16ch; }
.pdp > div > .lede { max-width: 62ch; }

/* Three cells is the design; one and two are what you get when a product only has one
 * or two specs worth flagging. Without these the strip would leave empty columns. */
.glance { background: var(--media-bg); }
.glance--1 { grid-template-columns: 1fr; }
.glance--2 { grid-template-columns: repeat(2, 1fr); }
.glance__cell { position: relative; padding-block: var(--s-4); }
.glance__cell::before { content: ""; position: absolute; inset: 0 auto 0 0;
                        width: 2px; background: var(--brand); }

.gallery { margin-top: var(--s-5); max-width: 780px; }
.gallery__main { border-color: var(--page-hair-cool); }
.gallery__thumb { transition: border-color 150ms ease, transform 150ms ease; }
.gallery__thumb:hover { border-color: var(--brand); transform: translateY(-2px); }

/* Section headings on the product page. They are real <h2>s now — the page used to go
 * from <h1> straight to nothing, which left a screen reader no way to move through it.
 * The --eyebrow modifier keeps the old visual weight where a full heading would shout. */
.section-title { margin: var(--s-7) 0 var(--s-3); font-size: var(--t-lg); }
.section-title--eyebrow {
  color: var(--brand-2); font-size: var(--t-xs); letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase; font-weight: var(--w-medium);
}

.description-panel {
  padding: var(--s-5) var(--s-6) var(--s-6);
  background: color-mix(in srgb, var(--page-raised) 82%, transparent);
  border: var(--hair); border-left: 3px solid var(--brand);
  box-shadow: var(--lip);
}
.description-panel .section-title { margin: 0 0 var(--s-4); }
.description-panel .prose { color: color-mix(in srgb, var(--page-fg) 88%, var(--page-muted)); }

.buybox { border-top: 3px solid var(--brand); background: var(--page-raised);
          box-shadow: 0 18px 42px rgba(31, 38, 52, 0.10), var(--lip); }
.buybox__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.buybox__head .eyebrow { margin: 0; }
.buybox__price { margin-top: var(--s-4); letter-spacing: -0.04em; }
.buybox__note { line-height: 1.45; }
.buybox__assurance { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s-3); }
.buybox__assurance span {
  padding: 4px 8px; background: var(--brand-wash); color: var(--brand-ink);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  border-radius: var(--r-sm); font-size: 11px; font-weight: var(--w-medium);
}

.spectable { border: var(--hair);
             background: color-mix(in srgb, var(--page-raised) 60%, transparent); }
.spectable caption { padding: var(--s-3); background: var(--anod-black); color: var(--anod-etch); }
.spectable tbody tr:nth-child(even) { background: rgba(0, 47, 167, 0.025); }

/* The download rows never said they were downloads. One glyph, in the mono face so it
 * lines up with the file sizes above it. */
.dl__row { position: relative; padding-inline: var(--s-3);
           grid-template-columns: auto 1fr auto auto 18px; }
.dl__row::after { content: "↓"; color: var(--brand); font-family: var(--font-mono); }

.faq details { background: color-mix(in srgb, var(--page-raised) 55%, transparent);
               padding-inline: var(--s-3); }
.faq details[open] { background: var(--page-raised); }

.trust__v, .buybox__note, .footer address, .footer__blurb, .dl__meta { font-size: 0.8125rem; }

/* ---- footer ------------------------------------------------------------- */
/* Graphite. The grid is white at 2% rather than the page's blue, because a blue-tinted
 * line on a dark ground reads as a colour cast; on metal it wants to look like light. */
.footer {
  background:
    linear-gradient(var(--graphite-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--graphite-grid) 1px, transparent 1px),
    linear-gradient(168deg, var(--graphite-1) 0%, var(--graphite-2) 100%);
  background-size: var(--blueprint-size) var(--blueprint-size),
                   var(--blueprint-size) var(--blueprint-size), auto;
  border-top: 3px solid var(--brand);
  /* The sheen: one bright hairline along the top edge, inside the brand rule. It is the
   * same trick as --lip on a card, which is why it is a token there and a literal here —
   * this one sits on dark and has to be brighter to register at all. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09);
}
.footer, .footer a { color: var(--graphite-fg); }
.footer h4 { color: var(--anod-etch); }
.footer a:hover { color: #fff; }
.footer address,
.footer__legal { color: var(--graphite-muted); }
.footer__legal { border-top-color: var(--graphite-hair); }
.footer__social a { color: var(--graphite-muted); }
.footer__social a:hover { color: var(--graphite-fg); }

/* ---- responsive --------------------------------------------------------- */
@media (max-width: 900px) {
  .support-band { grid-template-columns: 1fr; gap: var(--s-4); }
  .support-band .btn { justify-self: start; }
  .buybox { box-shadow: var(--lip); }
  .capability__grid { grid-template-columns: 1fr; }
  .capability__item,
  .capability__item:first-child { border-inline-start: 0; border-top: var(--hair); }
  .capability__item:first-child { border-top: 0; }
  .catalogue-head { align-items: start; }
  /* One column below 900px: two of these cards side by side puts the photo and the details
   * each under 200px, which is narrower than the specs line they carry. */
  .catalogue-page .grid { grid-template-columns: minmax(0, 1fr); }

  /* One column, and the buy box climbs above the gallery: on a phone the price and the
   * two CTAs should arrive before three product photographs, not after them.
   * display:contents dissolves the left-hand wrapper so its children and the buy box
   * become siblings in one flow and can be ordered against each other. */
  .pdp > div { display: contents; }
  .pdp > div > .eyebrow { order: 1; }
  .pdp > div > h1 { order: 2; }
  .pdp > div > .lede { order: 3; }
  .pdp > div > .glance { order: 4; }
  .pdp > .buybox { order: 5; }
  .pdp > div > .gallery { order: 10; }
  .pdp > div > .prose,
  .pdp > div > .description-panel { order: 11; }
  .pdp > div > .section-title,
  .pdp > div > .spectable,
  .pdp > div > .dl,
  .pdp > div > .faq { order: 12; }
}

@media (max-width: 560px) {
  .topbar__in span:first-child { display: none; }
  .topbar__in { justify-content: center; min-height: 30px; }
  .hero__in { padding-block: var(--s-8); }
  .hero h1 { font-size: clamp(2.1rem, 11vw, 3rem); }
  .hero__cta .btn { width: 100%; }
  .section { padding-block: var(--s-7); }
  .panel { padding: var(--s-5); }
  .panel__body { grid-template-columns: 1fr; }
  .panel__product { grid-template-columns: 120px 1fr; grid-template-rows: none; }
  .panel__product img { height: 104px; }
  .panel__product > span { border-top: 0; border-inline-start: 1px solid var(--tile-hair);
                           align-content: center; }
  .dro__row { font-size: var(--t-md); }
  .capability__item { min-height: 124px; }
  .catalogue-head { margin-inline: calc(-1 * var(--s-5)); padding: var(--s-5); border-left-width: 3px; }
  .catalogue-head .btn { width: 100%; }
  .pdp > div > h1 { font-size: 2.1rem; }
  .glance { grid-template-columns: 1fr 1fr; }
  .glance__cell:last-child { grid-column: 1 / -1; }
  .description-panel { padding: var(--s-5); }
  .featured-products .card,
  .catalogue-page .card { display: flex; padding: var(--s-4); }
  .featured-products .card__media,
  .catalogue-page .card__media { border: var(--hair); }
  .featured-products .card__details,
  .catalogue-page .card__details,
  .catalogue-page .card__spec { padding-inline: 0; }
  .featured-products .card__foot,
  .catalogue-page .card__foot { margin-inline: 0; }
  .dl__row { grid-template-columns: 1fr auto; }
  .dl__kind, .dl__meta { grid-column: 1; }
  .dl__row::after { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
}

/* ============================================================ the index pages
 *
 * Product tree, human sitemap, all downloads. Long tabular pages whose job is to be
 * scannable and searchable with the browser's own Find — which is the search this site did
 * not have. So: dense rows, real table semantics, and no decoration that costs height.
 */
.listtable {
  width: 100%; border-collapse: collapse; font-size: var(--t-sm);
  border: var(--hair); background: var(--media-bg);
}
.listtable thead th {
  position: sticky; top: 0; z-index: 1;
  text-align: left; padding: var(--s-2) var(--s-3);
  background: var(--anod-black); color: var(--anod-etch);
  font-family: var(--font-display); font-weight: var(--w-medium);
  font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase;
}
.listtable tbody th,
.listtable td { text-align: left; padding: 7px var(--s-3); border-top: var(--hair);
                vertical-align: top; }
.listtable tbody th { font-weight: var(--w-medium); }
.listtable tbody tr:hover { background: var(--brand-wash); }
.listtable__spec { color: var(--page-muted); font-size: var(--t-xs); line-height: 1.5; }
.listtable__spec b { color: var(--page-fg); font-family: var(--font-mono);
                     font-weight: var(--w-medium); }
/* The product cell on the downloads table spans its files, so it needs a visible edge or
 * the group reads as one row with a tall first cell. */
.listtable--dl tbody th[rowspan] { border-inline-end: var(--hair);
                                   background: color-mix(in srgb, var(--page-raised) 60%, transparent); }
.listtable--dl tbody th[rowspan] span { display: block; font-size: var(--t-xs); }
.listtable--dl tbody { border-top: 2px solid var(--page-hair); }

/* Jump links. With 38 categories the page is long, and reaching the one you want should
 * not need a scroll. Plain in-page anchors — no script. */
.jump { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0 0 var(--s-6); }
.jump a {
  padding: 4px var(--s-3); border: var(--hair); border-radius: var(--r-sm);
  background: var(--page-raised); box-shadow: var(--lip);
  color: var(--page-fg); font-size: var(--t-xs); font-weight: var(--w-medium);
}
.jump a:hover { border-color: var(--brand); color: var(--brand); text-decoration: none; }
.jump .num { color: var(--page-muted); }

.treecat { margin-bottom: var(--s-7); }
.treecat .section-title { margin-top: 0; scroll-margin-top: var(--s-5); }
.chipcount { display: inline-block; margin-inline-end: var(--s-4);
             font-size: var(--t-xs); color: var(--page-muted); }
.chipcount b { color: var(--page-fg); }

/* ============================================================ prose tables
 *
 * tc_markdown wraps every table in .tablewrap and neither had any styling — which nothing
 * noticed until a page actually contained one. The G-code reference is three columns of
 * short codes against long explanations, and unstyled that reads as two unrelated lists:
 * the code sits at the far left, its meaning starts wherever the browser felt like, and
 * nothing ties a row together.
 *
 * The wrap scrolls horizontally rather than letting a wide table push the page sideways —
 * a table is the one thing in body copy that genuinely cannot always fit.
 */
.tablewrap { overflow-x: auto; margin: var(--s-5) 0; }
.tablewrap table {
  width: 100%; border-collapse: collapse; font-size: var(--t-sm);
  border: var(--hair); background: var(--media-bg);
}
.tablewrap th {
  text-align: left; padding: var(--s-2) var(--s-3);
  background: var(--page-sunk); border-bottom: var(--hair);
  font-family: var(--font-display); font-weight: var(--w-medium);
  font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase;
}
.tablewrap td { padding: 8px var(--s-3); border-top: var(--hair); vertical-align: top; }
.tablewrap tbody tr:nth-child(even) { background: rgba(0, 47, 167, 0.022); }
/* A first column of codes wants to be as narrow as its content and stay on one line —
 * otherwise "G0" gets a third of the table and its explanation is pushed away from it. */
.tablewrap td:first-child { width: 1%; white-space: nowrap; }
/* Inline code inside a table is the subject of the row, so it carries weight. */
.tablewrap code { font-family: var(--font-mono); font-size: 0.9375em;
                  color: var(--brand-ink); font-weight: var(--w-medium); }

/* ============================================================ tool chips
 *
 * Each chip carries a line drawing behind its text. The drawing is inline SVG inheriting
 * currentColor, which is why a planned chip can dim its illustration by dimming its own
 * colour — one property, no second asset, and the two can never disagree.
 *
 * A planned chip is a <div>. It is styled to look unavailable rather than merely quiet,
 * because the honest signal is "not yet", not "this link is subtly different".
 */
.chips {
  display: grid; gap: var(--s-4); margin-bottom: var(--s-8);
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}
.chip {
  position: relative; overflow: hidden; isolation: isolate;
  display: grid; grid-template-columns: 1fr auto; align-items: end; gap: var(--s-3);
  min-height: 132px; padding: var(--s-5);
  border: var(--hair); border-radius: var(--r-md);
  background: var(--page-raised); box-shadow: var(--lip);
}
/* Bottom-right, bleeding off both edges, at a size that reads as a watermark rather than
 * as an illustration with text on top of it. */
.chip__art {
  position: absolute; z-index: -1; right: -14px; bottom: -10px;
  width: 168px; height: 112px; opacity: 0.10; color: var(--brand);
  transition: opacity 200ms ease, transform 200ms ease;
}
.chip__body { display: grid; gap: 4px; min-width: 0; }
.chip__name { font: var(--w-display) var(--t-lg)/1.15 var(--font-display);
              letter-spacing: var(--ls-display); }
.chip__note { font-size: var(--t-xs); color: var(--page-muted); line-height: 1.5;
              max-width: 30ch; }

.chip--ready { color: var(--page-fg); border-inline-start: 3px solid var(--brand); }
.chip--ready .chip__go { font-size: var(--t-lg); color: var(--brand); }
.chip--ready:hover {
  text-decoration: none; border-color: color-mix(in srgb, var(--brand) 40%, var(--page-hair));
  box-shadow: 0 14px 30px rgba(30, 38, 58, 0.10), var(--lip);
  transform: translateY(-2px);
}
.chip--ready:hover .chip__art { opacity: 0.18; transform: translate(-4px, -4px); }
.chip--ready { transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease; }

/* Planned: flat, no lift, no accent edge, and a label that says so in words. Dimmed by
 * colour rather than by `opacity` on the whole chip — opacity would fade the border and
 * the label together and make the card look like it was still loading. */
.chip--planned {
  color: var(--page-muted);
  background: color-mix(in srgb, var(--page-raised) 55%, transparent);
  border-style: dashed; box-shadow: none;
}
.chip--planned .chip__art { opacity: 0.055; color: var(--page-muted); }
.chip--planned .chip__name { font-weight: var(--w-medium); color: var(--page-muted); }
.chip__soon {
  align-self: end; padding: 3px 8px; border-radius: var(--r-sm);
  background: var(--page-sunk); color: var(--page-muted);
  font-size: 10px; font-weight: var(--w-medium);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase; white-space: nowrap;
}

@media (max-width: 560px) {
  .chips { grid-template-columns: 1fr; }
  .chip { min-height: 112px; }
}

/* ============================================================ search
 *
 * The box ships with the `hidden` attribute and search.js removes it. `.searchbox` sets
 * display, which beats the UA's [hidden] rule — the same trap that kept the mobile menu
 * permanently open and put an "Enquiry" link on every page. Hence the explicit rule.
 */
.searchbox[hidden],
.search__panel[hidden] { display: none; }
.searchbox { position: relative; color: var(--page-muted); }
.searchbox input { color: var(--page-fg); }
.search__panel {
  position: absolute; z-index: 40; top: calc(100% + var(--s-2)); inset-inline: 0;
  display: grid; max-height: 60vh; overflow-y: auto;
  background: var(--page-raised); border: var(--hair); border-radius: var(--r-md);
  box-shadow: 0 18px 42px rgba(31, 38, 52, 0.16), var(--lip);
}
.search__hit {
  display: grid; grid-template-columns: 1fr auto; gap: 0 var(--s-3);
  padding: var(--s-3); border-bottom: var(--hair); color: var(--page-fg);
}
.search__hit:last-child { border-bottom: 0; }
.search__hit:hover { background: var(--brand-wash); text-decoration: none; }
.search__name { font-size: var(--t-sm); font-weight: var(--w-medium); line-height: var(--lh-snug); }
.search__sku { font-size: var(--t-xs); color: var(--page-muted); }
.search__cat { grid-column: 1 / -1; font-size: var(--t-xs); color: var(--page-muted); }
.search__none,
.search__more { padding: var(--s-3); font-size: var(--t-xs); color: var(--page-muted); }

@media (max-width: 900px) {
  /* The search box STAYS on mobile, on its own row under the logo.
   *
   * It used to be hidden here, on the reasoning that it would not fit beside a hamburger
   * and the product tree was a tap away in the menu. That was the wrong call: search is
   * more useful on a phone, not less — the screen shows four products at a time, so
   * scrolling a 152-product catalogue is exactly the job you want to skip. Hiding the one
   * control that skips it, on the device where it matters most, to save a row of the
   * masthead was a poor trade.
   *
   * The masthead wraps: logo and hamburger on the first row, search full width beneath. */
  .header__in { flex-wrap: wrap; row-gap: var(--s-3); padding-bottom: var(--s-3); }
  .searchbox { order: 3; flex-basis: 100%; min-width: 0; }
  /* The results panel is positioned against .searchbox, which now spans the masthead, so
   * it lines up with the screen edges without any extra rules. */
}

@media (max-width: 700px) {
  /* A five-column table does not fit a phone. Scroll it horizontally rather than let the
   * browser crush the spec column to one character per line — the numbers are the point.
   * The wrapper is the page's own .catalogue-page, so no extra markup. */
  .listtable { display: block; overflow-x: auto; white-space: nowrap; }
  .listtable__spec { white-space: normal; min-width: 22ch; }
  .listtable thead th { position: static; }
}

/* Movement is decoration here — every transition above is a hover affordance, none of
 * it carries meaning. Anyone who has asked for less of it loses nothing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}
