/* 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-of-type rules below show the matching slide and highlight the matching thumb. No
 * script runs anywhere on this page, which is the property the whole site is built to
 * keep — see generator/templates/home.php for the same choice on the hero preview.
 *
 * 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-of-type(1),
.gallery__radio:nth-of-type(2):checked ~ .gallery__main .gallery__slide:nth-of-type(2),
.gallery__radio:nth-of-type(3):checked ~ .gallery__main .gallery__slide:nth-of-type(3),
.gallery__radio:nth-of-type(4):checked ~ .gallery__main .gallery__slide:nth-of-type(4),
.gallery__radio:nth-of-type(5):checked ~ .gallery__main .gallery__slide:nth-of-type(5),
.gallery__radio:nth-of-type(6):checked ~ .gallery__main .gallery__slide:nth-of-type(6),
.gallery__radio:nth-of-type(7):checked ~ .gallery__main .gallery__slide:nth-of-type(7),
.gallery__radio:nth-of-type(8):checked ~ .gallery__main .gallery__slide:nth-of-type(8),
.gallery__radio:nth-of-type(9):checked ~ .gallery__main .gallery__slide:nth-of-type(9),
.gallery__radio:nth-of-type(10):checked ~ .gallery__main .gallery__slide:nth-of-type(10) {
  display: grid;
}
.gallery__radio:nth-of-type(1):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(1),
.gallery__radio:nth-of-type(2):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(2),
.gallery__radio:nth-of-type(3):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(3),
.gallery__radio:nth-of-type(4):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(4),
.gallery__radio:nth-of-type(5):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(5),
.gallery__radio:nth-of-type(6):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(6),
.gallery__radio:nth-of-type(7):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(7),
.gallery__radio:nth-of-type(8):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(8),
.gallery__radio:nth-of-type(9):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(9),
.gallery__radio:nth-of-type(10):checked ~ .gallery__thumbs .gallery__thumb:nth-of-type(10) {
  border-color: var(--brand);
}
.gallery__radio:nth-of-type(1):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(1),
.gallery__radio:nth-of-type(2):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(2),
.gallery__radio:nth-of-type(3):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(3),
.gallery__radio:nth-of-type(4):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(4),
.gallery__radio:nth-of-type(5):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(5),
.gallery__radio:nth-of-type(6):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(6),
.gallery__radio:nth-of-type(7):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(7),
.gallery__radio:nth-of-type(8):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(8),
.gallery__radio:nth-of-type(9):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(9),
.gallery__radio:nth-of-type(10):focus-visible ~ .gallery__thumbs .gallery__thumb:nth-of-type(10) {
  outline: 2px solid var(--brand); outline-offset: 1px;
}

/* 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); }

/* ---- 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); }
.featured-products .grid { grid-template-columns: minmax(0, 940px); border: 0; }
/* 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 inline nav is replaced by the disclosure menu here, and the search box goes with
   * it rather than squeezing into the masthead beside a hamburger. The tree page is one
   * tap away in the menu and does the same job. */
  .searchbox { display: none; }
}

@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;
  }
}
