/* ==========================================================================
   Haesyn: haesyn.com

   The idea: a machine on a measurement bench. Stark white, true black, one
   cold steel accent, a live coordinate grid behind the opening screen, and
   the twenty-four model names running across the page as a moving wall.

   Deliberately nothing like Yang's Bar. That page is dark, warm and
   atmospheric, copper light in a room at night. This one is bright, cold and
   straight. They are different brands under one parent and should not read as
   two skins of one template.

   The palette is the brand's own: the Haesyn wordmark is pure black on white,
   with no colour in it at all, so the page is monochrome and earns its one
   accent from the stainless it sells.
   ========================================================================== */

@font-face { font-family: 'HS Display'; src: url('assets/fonts/disp-700.woff2') format('woff2'); font-weight: 700; font-display: swap; }
@font-face { font-family: 'HS Display'; src: url('assets/fonts/disp-800.woff2') format('woff2'); font-weight: 800; font-display: swap; }
@font-face { font-family: 'HS Tech';    src: url('assets/fonts/tech-400.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'HS Tech';    src: url('assets/fonts/tech-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }

:root {
  /* THE EIRENE RECIPE, in Haesyn's own temperature. A surface is frosted glass
     over a ground that shines, not a white box on a white page: translucent,
     widely blurred, one hairline in the accent at low alpha, and a large soft
     shadow. Haesyn's shine is steel rather than gold; that is the only change. */
  --surface:  255, 255, 255;
  --glass:    rgba(var(--surface), 0.70);
  --edge:     rgba(38, 94, 158, 0.18);
  --soft:     0 18px 60px rgba(20, 34, 54, 0.07);
  --radius:   20px;
  --blur:     blur(20px);

  /* ==========================================================================
     THREE SIZES. THE WHOLE SITE.
     Display for anything that leads, body for anything that is read, small for
     anything that labels. Nothing else exists.

     Hierarchy is carried by WEIGHT, COLOUR and SPACE rather than by size. That
     is the discipline that makes a page look composed rather than assembled: an
     h1 and an h2 are the same size here on purpose, and what separates them is
     that one sits alone at the top of a screen and the other has a rule above.
     ========================================================================== */
  --t-display: clamp(1.8rem, 4.8vw, 3rem);
  --t-body:    1rem;
  --t-small:   0.72rem;

  --paper: #ffffff;
  --wash:  #f3f4f6;     /* the alternate ground, a shade of cold grey */
  --ink:   #0b0c0e;
  --soft:  #62676e;
  --rule:  #dcdfe4;
  --steel: #265e9e;     /* the one accent: technical marks only, never decoration */
  --gut:   clamp(20px, 5vw, 72px);
  --disp: 'HS Display', 'Helvetica Neue', Arial, sans-serif;
  --tech: 'HS Tech', ui-monospace, 'SF Mono', Menlo, monospace;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; color: var(--ink);
  background:
    radial-gradient(circle at 10% 8%,  rgba(38, 94, 158, 0.13), transparent 30rem),
    radial-gradient(circle at 90% 4%,  rgba(140, 150, 165, 0.14), transparent 34rem),
    linear-gradient(180deg, #ffffff 0%, #f8f9fb 52%, #eef0f4 100%);
  background-attachment: fixed;
  font: 400 var(--t-body)/1.65 var(--tech); overflow-x: hidden;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

.skip { position: absolute; left: -9999px; background: var(--ink); color: var(--paper); padding: 12px 18px; font-weight: 500; z-index: 100; }
.skip:focus { left: 12px; top: 12px; }
:where(a, button):focus-visible { outline: 2px solid var(--steel); outline-offset: 3px; }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* A label style used in a dozen places: small, spaced, upper. */
.eyebrow, .block-label, .fig-group, .plate dt, .colophon dt, .top-nav a {
  font: 500 var(--t-small)/1 var(--tech); letter-spacing: .18em; text-transform: uppercase;
}

/* ==========================================================================
   Top bar: flush to the edge, a rule under it, no floating pill
   ========================================================================== */
.top {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,.92); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--rule);
}
.top-in {
  display: flex; align-items: center; gap: 16px;
  padding: 10px var(--gut); max-width: 1680px; margin: 0 auto;
}
.top-mark { display: inline-flex; align-items: center; flex: 0 0 auto; }
.top-mark img { width: 118px; height: auto; }
.top-nav { margin-left: auto; display: none; gap: 26px; }
.top-nav a { color: var(--soft); text-decoration: none; padding: 6px 0; }
.top-nav a:hover { color: var(--ink); }
.top-call {
  flex: 0 0 auto; text-decoration: none; background: var(--ink); color: var(--paper);
  padding: 11px 16px; font: 500 var(--t-small)/1 var(--tech); letter-spacing: .02em;
  display: inline-flex; align-items: center;
}
.top-call:hover { background: var(--steel); }
.top-call-full { display: none; }

/* ==========================================================================
   Buttons
   ========================================================================== */
.cta { display: flex; flex-wrap: wrap; gap: 10px; }
.btn {
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 14px 24px; text-decoration: none; border: 1px solid var(--ink);
  font: 500 var(--t-body)/1 var(--tech); letter-spacing: .02em;
  transition: background .15s, color .15s, border-color .15s;
}
.btn-solid { background: var(--ink); color: var(--paper); }
.btn-solid:hover { background: var(--steel); border-color: var(--steel); }
.btn-line {
  background: var(--glass); color: var(--ink); border-color: var(--edge);
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  box-shadow: var(--soft);
}
.btn-line:hover { border-color: var(--ink); }

/* ==========================================================================
   OPENING: the machine on a measurement field
   ========================================================================== */
.open {
  position: relative; isolation: isolate; overflow: hidden;
  padding: 40px var(--gut) 0;
  border-bottom: 1px solid var(--rule);
}
/* width/height are not optional here. A <canvas> is a replaced element, so
   `width: auto` resolves to its intrinsic 300x150 rather than stretching to
   `inset: 0` the way a plain div would. Measured 27 Sep 2026: the box was
   300x150 against a 1440x880 section, and the field drew into the corner. */
#scan {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: -1; pointer-events: none;
}

.open-copy { position: relative; max-width: 22rem; }
.eyebrow { display: flex; align-items: center; gap: 9px; margin: 0 0 18px; color: var(--steel); }
.dot { width: 7px; height: 7px; background: var(--steel); border-radius: 50%; flex: 0 0 auto; }

.open h1 {
  margin: 0 0 20px;
  font: 800 var(--t-display)/1.02 var(--disp);
  letter-spacing: -0.035em; text-wrap: balance;
}
.open-sub { margin: 0 0 26px; color: var(--soft); font-size: var(--t-body); }
.open .cta { margin-bottom: 34px; }

.open-rig { margin: 0 0 30px; position: relative; }
.open-rig img { width: 100%; max-width: 380px; margin: 0 auto; }
.open-rig figcaption {
  margin-top: 12px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
  padding-top: 10px; border-top: 1px solid var(--rule);
}
.open-rig b { font: 500 var(--t-body)/1.3 var(--tech); }
.open-rig span { color: var(--soft); font: 400 var(--t-small)/1 var(--tech); letter-spacing: .12em; text-transform: uppercase; }

/* The rating plate: plain facts, bordered like a stamped panel. */
.plate { position: relative; margin: 0; display: grid; gap: 10px; }
.plate > div {
  padding: 14px 18px; display: flex; gap: 16px; align-items: baseline;
  border: 1px solid var(--edge); border-radius: var(--radius);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  box-shadow: var(--soft);
}
.plate dt { color: var(--soft); flex: 0 0 88px; }
.plate dd { margin: 0; font: 500 var(--t-body)/1.3 var(--tech); }

/* ==========================================================================
   THE WALL: every model name, moving
   The only loud thing on the page, and it carries no information that is not
   also set, still and readable, in the index below.
   ========================================================================== */
.wall { padding: 40px 0 44px; overflow: hidden; border-bottom: 1px solid var(--rule); }
.wall-row { overflow: hidden; }
.wall-back { margin-top: 10px; }
.wall-track { display: flex; width: max-content; animation: wall 46s linear infinite; }
.wall-track-rev { animation-direction: reverse; }
.wall-set {
  display: flex; align-items: center; flex: 0 0 auto;
  font: 800 var(--t-display)/1.25 var(--disp); letter-spacing: -0.03em;
  white-space: nowrap;
}
.wall-set b { font-weight: 800; padding: 0 .28em; }
.wall-set i { font-style: normal; color: var(--steel); font-weight: 700; }

/* The second row is set in outline, so the two read as foreground and back. */
.wall-back .wall-set b {
  color: transparent;
  -webkit-text-stroke: 1px var(--rule);
  paint-order: stroke fill;
}
.wall-back .wall-set i { color: var(--rule); }

@keyframes wall { from { transform: translate3d(0,0,0); } to { transform: translate3d(-50%,0,0); } }
.wall:hover .wall-track { animation-play-state: paused; }

/* ==========================================================================
   Blocks
   ========================================================================== */
.block { padding: 54px var(--gut) 62px; border-bottom: 1px solid var(--rule); }
.block-wash { background: rgba(255,255,255,0.35); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }

.block-rule { display: flex; align-items: center; gap: 14px; margin-bottom: 30px; }
.block-n { color: var(--steel); font: 500 var(--t-small)/1 var(--tech); letter-spacing: .1em; }
.block-label { color: var(--soft); }
.block-rule::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

.block-head { max-width: 72ch; margin-bottom: 40px; }
.block-head h2 {
  margin: 0 0 16px;
  font: 700 var(--t-display)/1.12 var(--disp);
  letter-spacing: -0.028em; text-wrap: balance;
}
.lede { margin: 0; color: var(--soft); }

/* ---------- The index: names with leader rules ---------- */
.index { display: grid; gap: 34px; }
.index h3 {
  display: flex; align-items: baseline; gap: 12px;
  margin: 0 0 12px; padding-bottom: 10px; border-bottom: 1px solid var(--ink);
  font: 700 var(--t-body)/1 var(--disp); letter-spacing: -0.01em;
}
.index .count { margin-left: auto; color: var(--steel); font: 500 var(--t-small)/1 var(--tech); letter-spacing: .14em; }
.index ul { list-style: none; margin: 0; padding: 0; }
.index li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--rule);
  font-size: var(--t-body);
}
.index li span { flex: 0 1 auto; }
/* the leader: a rule that fills the gap, as a contents page does with dots */
.index li i { flex: 1 1 auto; height: 1px; background: var(--rule); align-self: center; min-width: 14px; }
.index li em { flex: 0 0 auto; font-style: normal; color: var(--soft); font: 400 var(--t-small)/1 var(--tech); letter-spacing: .14em; text-transform: uppercase; }

/* ==========================================================================
   FIGURES: each machine given a whole spread and a numeral to sit in
   ========================================================================== */
.figs { list-style: none; margin: 0; padding: 0; display: grid; gap: 52px; }
.fig { position: relative; display: grid; gap: 18px; }
/* The plate number, set as an outline. It lived behind the figure at first;
   the product shots are on white and covered it entirely, so it now heads the
   copy column where there is always room for it. */
.fig-num {
  display: block; margin: 0 0 4px;
  font: 800 var(--t-display)/.9 var(--disp); letter-spacing: -0.06em;
  color: transparent; -webkit-text-stroke: 1px var(--rule);
  pointer-events: none; user-select: none;
}
.fig figure { position: relative; z-index: 1; margin: 0; }
.fig img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: contain;
  background: var(--glass); border: 1px solid var(--edge); border-radius: var(--radius); padding: 22px;
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  box-shadow: var(--soft);
  transition: transform .5s cubic-bezier(.2,.7,.3,1);
}
.fig:hover img { transform: scale(1.03); }
.fig-copy { position: relative; z-index: 1; }
.fig-group { margin: 0 0 8px; color: var(--steel); }
.fig-copy h3 { margin: 0 0 8px; font: 700 var(--t-body)/1.15 var(--disp); letter-spacing: -0.024em; }
.fig-copy p { margin: 0; color: var(--soft); font-size: var(--t-body); }
.fig-note { margin: 44px 0 0; max-width: 62ch; color: var(--soft); font-size: var(--t-body); }

/* ==========================================================================
   THE MACHINE CARDS, MADE TO BEHAVE LIKE SOMETHING ON A BENCH
   --------------------------------------------------------------------------
   The figures were photographs with captions. Now each one is a station: the
   probe from the opening screen follows the pointer across the product, the
   crosshair reads its position, and every machine carries its own enquiry that
   names it.

   The probe is the ONLY part that needs a pointer, and nothing depends on it.
   The enquiry link is a plain anchor that works with no script at all, and it
   is visible without hovering on a touch screen, because a hover-only action on
   a phone is an action nobody can reach.
   ========================================================================== */
.fig figure { position: relative; overflow: hidden; }

/* z-index is not optional. The probe is the figure's FIRST child and the photograph
   comes after it, so without this the image paints straight over the crosshair. It
   cost a screenshot to find: the opacity was 1 and the reticle was tracking
   correctly, and it was being covered the whole time. A property being set is not
   the same as a thing being visible. */
.probe { position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: 0; transition: opacity .25s ease; }
.fig:hover .probe, .fig:focus-within .probe { opacity: 1; }

/* the two hairlines, positioned from JS by custom property */
.probe i { position: absolute; background: var(--steel); opacity: .45; }
.probe i:nth-child(1) { left: 0; right: 0; height: 1px; top: var(--py, 50%); }
.probe i:nth-child(2) { top: 0; bottom: 0; width: 1px; left: var(--px, 50%); }

/* the reticle where they cross */
.probe b {
  position: absolute; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  top: var(--py, 50%); left: var(--px, 50%);
  border: 1px solid var(--steel); border-radius: 50%;
  box-shadow: 0 0 0 4px rgba(38,94,158,.10);
}
.probe b::before, .probe b::after {
  content: ""; position: absolute; background: var(--steel);
}
.probe b::before { left: 50%; top: -7px; width: 1px; height: 6px; }
.probe b::after { top: 50%; left: -7px; height: 1px; width: 6px; }

/* the photograph lifts very slightly under the probe, so the card feels touched */
.fig img { transition: transform .5s cubic-bezier(.2,.7,.3,1), filter .4s ease; }
.fig:hover img, .fig:focus-within img { transform: scale(1.035); filter: contrast(1.04); }

/* the card's own frame answers too */
.fig figure { transition: border-color .3s ease, box-shadow .3s ease; }
.fig:hover figure, .fig:focus-within figure {
  box-shadow: 0 18px 40px -26px rgba(11,12,14,.55);
}
.fig:hover .fig-num, .fig:focus-within .fig-num { -webkit-text-stroke-color: var(--steel); }
.fig-num { transition: -webkit-text-stroke-color .3s ease; }

/* ---------- the machine gets measured when you arrive ----------
   Two dimension lines draw themselves across the product, the way a drawing marks
   an overall width and height, with a tick at each end. They grow from their own
   start rather than fading in, because a measurement is a thing that is taken, not
   a thing that appears. */
.dims { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.dims u, .dims s { position: absolute; display: block; background: var(--steel); text-decoration: none; opacity: .55; }

/* the horizontal measure, low across the machine */
.dims u {
  left: 9%; right: 9%; bottom: 11%; height: 1px;
  transform: scaleX(0); transform-origin: left center;
  transition: transform .62s cubic-bezier(.22,.7,.28,1);
}
/* the vertical measure, down the right */
.dims s {
  top: 11%; bottom: 11%; right: 7%; width: 1px;
  transform: scaleY(0); transform-origin: center top;
  transition: transform .62s cubic-bezier(.22,.7,.28,1) .1s;
}
.fig:hover .dims u, .fig:focus-within .dims u { transform: scaleX(1); }
.fig:hover .dims s, .fig:focus-within .dims s { transform: scaleY(1); }

/* the end ticks, drawn with the lines' own pseudo elements */
.dims u::before, .dims u::after, .dims s::before, .dims s::after {
  content: ""; position: absolute; background: var(--steel);
}
.dims u::before, .dims u::after { top: -4px; width: 1px; height: 9px; }
.dims u::before { left: 0; } .dims u::after { right: 0; }
.dims s::before, .dims s::after { left: -4px; height: 1px; width: 9px; }
.dims s::before { top: 0; } .dims s::after { bottom: 0; }

/* ---------- the probe sweeps the product as it arrives ----------
   One pass of a pale steel band, the same idea as the sweep on the opening screen's
   canvas, so the card and the hero are visibly the same instrument. */
.sweep {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden;
}
.sweep::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: 45%;
  background: linear-gradient(90deg, transparent, rgba(38,94,158,.13) 45%, rgba(255,255,255,.35) 52%, rgba(38,94,158,.10) 60%, transparent);
  transform: translate3d(-160%,0,0);
  transition: transform 0s;
}
.fig:hover .sweep::before, .fig:focus-within .sweep::before {
  transform: translate3d(340%,0,0);
  transition: transform 1.05s cubic-bezier(.3,.6,.3,1);
}

/* ---------- each card arrives as the page reaches it ----------
   Scroll-driven, in CSS, inside an @supports. No script is involved, so there is no
   repeat of the failure this project already had once, where five of seven sections
   sat at opacity 0 because an observer had not fired yet. Where this is unsupported
   the card is simply there, fully drawn, which is also its resting state here. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .fig figure {
      animation: figure-in linear both;
      animation-timeline: view();
      animation-range: entry 4% entry 46%;
    }
    .fig-num {
      animation: num-in linear both;
      animation-timeline: view();
      animation-range: entry 6% entry 52%;
    }
  }
}
/* Transform and clip only. Text is never animated and never hidden. */
@keyframes figure-in {
  from { transform: translate3d(0, 26px, 0) scale(.985); clip-path: inset(0 0 12% 0); }
  to   { transform: none; clip-path: inset(0 0 0 0); }
}
@keyframes num-in {
  from { transform: translate3d(-14px, 0, 0); }
  to   { transform: none; }
}

/* ---------- the enquiry that names its machine ---------- */
.fig-ask {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 16px; padding: 11px 0;
  text-decoration: none; color: var(--ink);
  font: 500 var(--t-small)/1 var(--tech); letter-spacing: .02em;
  border-bottom: 1px solid var(--rule);
  transition: color .2s ease, border-color .2s ease, gap .2s ease;
}
.fig-ask svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: 0 0 auto; }
.fig-ask:hover, .fig-ask:focus-visible { color: var(--steel); border-color: var(--steel); gap: 14px; }


/* ==========================================================================
   CREED
   ========================================================================== */
.creed { padding: 78px var(--gut); background: var(--ink); border-bottom: 1px solid var(--ink); }
.creed p {
  margin: 0 auto; max-width: 16ch; color: var(--paper);
  font: 800 var(--t-display)/1.06 var(--disp);
  letter-spacing: -0.038em;
}
.creed em { font-style: normal; color: transparent; -webkit-text-stroke: 1px rgba(255,255,255,.55); }

/* ==========================================================================
   Our standard
   ========================================================================== */
.std { list-style: none; margin: 0; padding: 0; counter-reset: s; display: grid; gap: 26px; }
.std li { counter-increment: s; padding-top: 18px; border-top: 1px solid var(--ink); }
.std li::before {
  content: counter(s, decimal-leading-zero);
  display: block; margin-bottom: 12px; color: var(--steel);
  font: 500 var(--t-small)/1 var(--tech); letter-spacing: .18em;
}
.std h3 { margin: 0 0 8px; font: 700 var(--t-body)/1.25 var(--disp); letter-spacing: -0.018em; }
.std p { margin: 0; color: var(--soft); font-size: var(--t-body); }

/* ==========================================================================
   Enquire
   ========================================================================== */
.ask { display: grid; gap: 40px; }
.ask h2 { margin: 0 0 14px; font: 700 var(--t-display)/1.12 var(--disp); letter-spacing: -0.028em; }
.ask .cta { margin-top: 24px; }
.colophon { margin: 0; display: grid; gap: 10px; }
.colophon > div {
  padding: 14px 18px; border: 1px solid var(--edge); border-radius: var(--radius);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  box-shadow: var(--soft);
}
.colophon dt { margin-bottom: 6px; color: var(--soft); }
.colophon dd { margin: 0; font-size: var(--t-body); min-width: 0; overflow-wrap: anywhere; }
.colophon a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.colophon a:hover { border-color: var(--steel); color: var(--steel); }

/* ==========================================================================
   Footer
   ========================================================================== */
.foot { padding: 30px var(--gut) 44px; }
.foot-in { max-width: 1680px; margin: 0 auto; display: grid; gap: 6px; }
.foot p { margin: 0; }
.foot-brand { font: 500 var(--t-body)/1.4 var(--tech); }
.foot-parent, .foot-copy { color: var(--soft); font-size: var(--t-small); }
.foot-parent a { text-decoration: underline; text-underline-offset: 3px; }
.foot-parent a:hover { color: var(--steel); }

/* ==========================================================================
   Wider screens
   ========================================================================== */
@media (min-width: 640px) {
  .open-copy { max-width: 30rem; }
  .plate { grid-template-columns: repeat(2, 1fr); column-gap: 40px; }
  .index { grid-template-columns: repeat(2, 1fr); gap: 34px 46px; }
  .figs { gap: 64px; }
  .std { grid-template-columns: repeat(3, 1fr); gap: 28px; }
  .colophon { grid-template-columns: repeat(3, 1fr); column-gap: 40px; }
  .colophon { border-top: 1px solid var(--ink); }
}

@media (min-width: 1000px) {
  .top-nav { display: flex; }
  .top-call-full { display: inline; }
  .top-call-short { display: none; }
  .top-mark img { width: 136px; }

  /* The opening becomes a real spread: copy left, machine right, spec strip
     running the full width underneath both. */
  .open {
    min-height: 88svh;
    display: grid; grid-template-columns: 1.05fr .95fr;
    grid-template-rows: 1fr auto; gap: 0 56px;
    align-items: center; padding: 56px var(--gut) 0;
  }
  .open-copy { max-width: 42rem; grid-column: 1; grid-row: 1; }
  .open-rig { grid-column: 2; grid-row: 1; margin: 0; }
  .open-rig img { max-width: 100%; }
  .plate { grid-column: 1 / -1; grid-row: 2; grid-template-columns: repeat(4, 1fr); column-gap: 32px; }

  .wall { padding: 56px 0 60px; }
  .block { padding: 78px var(--gut) 88px; }

  /* Preparation carries eleven of the twenty-four, so it takes the wider column. */
  .index { grid-template-columns: 1.6fr 1fr 1fr; gap: 46px 52px; align-items: start; }
  .index > section:first-child { grid-row: span 2; }
  .index > section:first-child ul { columns: 2; column-gap: 40px; }
  .index > section:first-child li { break-inside: avoid; }

  /* Each figure becomes a spread, alternating side so the page has a rhythm
     rather than a column of identical cards. */
  .figs { gap: 96px; }
  .fig { grid-template-columns: 1.15fr .85fr; gap: 48px; align-items: center; }
  .fig:nth-child(even) figure { order: 2; }
  .fig:nth-child(even) .fig-copy { order: 1; }
  .fig img { aspect-ratio: 5 / 4; padding: 28px; }
  .fig-copy h3 { font-size: var(--t-display); }
  .fig-copy p { font-size: var(--t-body); }

  .ask { grid-template-columns: 1.1fr 1fr; gap: 64px; align-items: start; }
  .colophon { grid-template-columns: 1fr; }

  .foot-in { grid-template-columns: 1fr auto; align-items: center; }
  .foot-copy { grid-column: 2; grid-row: 1 / span 2; text-align: right; }
}

/* ==========================================================================
   TOUCH FLOORS: asked of the pointer, and last in the file on purpose.
   A tablet is 768 or 820 wide and sails past any `max-width: 700px` rule, so
   these must be asked of `pointer: coarse` rather than of a width; and they
   must sit after the width queries, or an equal-specificity rule above wins.
   ========================================================================== */
@media (pointer: coarse) {
  .btn { min-height: 46px; }
  /* A hover-only action is an action a thumb cannot reach, so on touch the enquiry
     is simply always there, and the probe never shows because there is no pointer. */
  .fig-ask { min-height: 44px; }
  .probe { display: none; }
  .top-call { min-height: 44px; }
  .top-nav a { min-height: 44px; display: inline-flex; align-items: center; }
  .top-mark { min-height: 44px; }
  .colophon a, .foot-parent a { min-height: 44px; display: inline-flex; align-items: center; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
  /* With the animation off the track would sit at its start, which is correct:
     the first names are readable and the rest are reachable in the index. */
}

/* ==========================================================================
   PATENT MARK
   Standalone, with no label in front of it. It is a claim about the product,
   not a row in a specification table.
   ========================================================================== */
.patent { margin: 0 0 18px; }
.patent span {
  display: inline-block; border: 1px solid var(--ink); border-radius: 999px;
  padding: 6px 15px; font: 500 var(--t-small)/1 var(--tech); letter-spacing: .2em; text-transform: uppercase;
}

/* ==========================================================================
   FOOTER POLICY LINKS
   ========================================================================== */
.foot-links { display: flex; flex-wrap: wrap; gap: 6px 22px; }
.foot-links a {
  color: var(--soft); text-decoration: none; padding: 4px 0;
  font: 400 var(--t-small)/1.4 var(--tech);
  border-bottom: 1px solid transparent;
}
.foot-links a:hover { color: var(--steel); border-color: var(--steel); }

/* ==========================================================================
   POLICY PAGES
   The same instrument as the rest of the site, set for reading: one column,
   a measure that stops at 68 characters, and the section numbers the blocks
   on the home page already use, so a policy does not feel bolted on.
   ========================================================================== */
.doc { padding: 40px var(--gut) 72px; max-width: 900px; }
.doc-head { margin-bottom: 40px; padding-bottom: 28px; border-bottom: 1px solid var(--ink); }
.doc-head h1 {
  margin: 0 0 14px;
  font: 800 var(--t-display)/1.04 var(--disp);
  letter-spacing: -0.032em; text-wrap: balance;
}
.doc-meta { margin: 0; color: var(--soft); font: 400 var(--t-small)/1.5 var(--tech); letter-spacing: .1em; text-transform: uppercase; }
.doc-lede { margin: 18px 0 0; max-width: 62ch; color: var(--soft); font-size: var(--t-body); }

.doc section { margin-bottom: 34px; }
.doc h2 {
  display: flex; align-items: baseline; gap: 14px;
  margin: 0 0 14px;
  font: 700 var(--t-body)/1.25 var(--disp); letter-spacing: -0.018em;
}
.doc h2 b { flex: 0 0 auto; color: var(--steel); font: 500 var(--t-small)/1.6 var(--tech); letter-spacing: .14em; }
.doc p, .doc li { max-width: 68ch; }
.doc p { margin: 0 0 12px; }
.doc ul { margin: 0 0 12px; padding-left: 0; list-style: none; }
.doc ul li { position: relative; padding-left: 20px; margin-bottom: 8px; }
.doc ul li::before { content: ""; position: absolute; left: 2px; top: .7em; width: 8px; height: 1px; background: var(--steel); }
.doc a { color: var(--steel); text-decoration: none; border-bottom: 1px solid var(--rule); }
.doc a:hover { border-color: var(--steel); }
.doc strong { font-weight: 500; }

/* the plain-fact panel each policy closes on */
.doc-contact { margin-top: 40px; display: grid; gap: 10px; }
.doc-contact > div {
  padding: 14px 18px; display: flex; gap: 16px; align-items: baseline;
  border: 1px solid var(--edge); border-radius: var(--radius);
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  box-shadow: var(--soft);
}
.doc-contact dt { flex: 0 0 96px; color: var(--soft); font: 500 var(--t-small)/1.5 var(--tech); letter-spacing: .18em; text-transform: uppercase; }
/* min-width: 0 and a break opportunity, or a long unbreakable value sets the floor.
   Measured 28 Sep 2026: the contact email is about 245px of unbreakable text, dt is a
   fixed 96px, and with the gap and padding that made the row 395px inside a 350px
   column. Because the grid sizes to its widest item, ALL THREE rows inflated and every
   policy page scrolled sideways on a phone, 415px of content in a 390px viewport. */
.doc-contact dd { margin: 0; font-size: var(--t-body); min-width: 0; overflow-wrap: anywhere; }

.doc-back {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 34px;
  text-decoration: none; color: var(--ink);
  font: 500 var(--t-small)/1 var(--tech); border-bottom: 1px solid var(--rule); padding-bottom: 10px;
}
.doc-back:hover { color: var(--steel); border-color: var(--steel); }

@media (min-width: 900px) {
  .doc { padding: 56px var(--gut) 96px; }
  .doc-contact { grid-template-columns: repeat(3, 1fr); column-gap: 40px; border-top: 1px solid var(--ink); }
}


/* A line under the contact buttons, saying what the form gives you that a phone
   call does not. Sits with the buttons, not with the prose. */
.ask-note { margin: 12px 0 0; font-size: var(--t-small); color: var(--soft); line-height: 1.55; max-width: 44ch; }

/* Shown only while the form has no database behind it. index.php injects it and
   stops injecting it the moment the config file exists, so this style becomes
   dead weight rather than something that must be removed. */
.enq-down {
  background: rgba(38, 94, 158, 0.06); border: 1px solid rgba(38, 94, 158, 0.28);
  border-radius: var(--radius); padding: 18px 18px 20px; margin: 0 0 20px;
}
.enq-down-h { margin: 0 0 8px; font-weight: 500; font-size: 1.05rem; }
.enq-down p { margin: 0 0 10px; line-height: 1.6; }
.enq-down-act { margin: 14px 0 0; }
/* The colour is forced because `.doc a` (0,1,1) paints every link in this article
   steel and adds a rule under it, which on a solid black button is steel-on-black
   at 2.96:1 -- measured, not guessed. `.enq-down-act .btn` is (0,2,0) and wins.
   White on this button is 19.6:1. */
.enq-down-act .btn {
  width: 100%; justify-content: center; min-height: 52px;
  color: var(--paper); border-bottom: 0; text-decoration: none;
}
.enq-down-act .btn:hover { color: var(--paper); border-bottom: 0; }
/* The form stays visible but reads as closed rather than broken. */
.enq.is-down .enq-grid, .enq.is-down .field, .enq.is-down .shots-field { opacity: .5; }
.enq.is-down input, .enq.is-down textarea, .enq.is-down .shots-drop { pointer-events: none; }

/* ==========================================================================
   THE ENQUIRY FORM  (/enquiry/)
   --------------------------------------------------------------------------
   Written mobile first: every rule here is the phone layout, and the one width
   query below only widens the grid. A visitor at a trade stall is holding a
   phone, so the phone is not the fallback.

   The panel is the site's own glass recipe -- translucent, widely blurred, one
   steel hairline at low alpha, large soft shadow -- laid over the measurement
   field so the blur has something to do. Glass over a flat white page is just a
   white box with extra rendering cost.
   ========================================================================== */

/* The canvas sits behind the whole article rather than inside the panel, so one
   surface floats over one ground. position: fixed and not absolute: the page
   scrolls and the field should not travel with it. */
/* #scan.enq-bg AND NOT .enq-bg. The opening screen already styles #scan at line
   ~141 as position: absolute, z-index: -1, and an id beats a class no matter which
   comes later in the file. Measured 29 Sep 2026: with a plain .enq-bg the canvas
   came out absolute and one viewport tall, so the field stopped dead partway down
   the form and the rest of the page sat on flat white. The id is repeated here to
   win the specificity rather than to select anything new. */
#scan.enq-bg {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 0; pointer-events: none; background: var(--wash);
}
.enq-page { position: relative; z-index: 1; }

.enq {
  background: var(--glass);
  -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  border: 1px solid var(--edge); border-radius: var(--radius);
  box-shadow: var(--soft);
  padding: 22px 18px; margin: 0 0 28px;
}
/* WITHOUT backdrop-filter the panel must stop being translucent, or the text
   lands on whatever the canvas is drawing and the contrast is anyone's guess.
   Firefox before 103 and every older Android browser land here. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .enq { background: var(--paper); }
}

.enq-grid { display: grid; grid-template-columns: 1fr; gap: 0; }

.field { margin: 0 0 18px; }
.field label {
  display: block; margin: 0 0 7px;
  font: 500 var(--t-small)/1.3 var(--tech);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--soft);
}
.req { color: var(--steel); margin-left: 2px; }

/* font: inherit, because a phone that decides an input is 13px will zoom the whole
   page in when it is focused, and never zoom back out. 16px minimum is the rule
   that prevents it; --t-body is 1rem. */
.field input, .field textarea {
  width: 100%; font: 400 var(--t-body)/1.45 inherit; font-family: inherit;
  color: var(--ink); background: rgba(var(--surface), 0.92);
  border: 1px solid var(--rule); border-radius: 12px;
  padding: 13px 14px; min-height: 50px;
  -webkit-appearance: none; appearance: none;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.field textarea { min-height: 128px; resize: vertical; line-height: 1.55; }
.field input::placeholder, .field textarea::placeholder { color: #9aa0a8; }
.field input:hover, .field textarea:hover { border-color: #c2c8d0; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: none; border-color: var(--steel);
  box-shadow: 0 0 0 3px rgba(38, 94, 158, 0.16);
}
/* :user-invalid and not :invalid. :invalid matches an empty required field from
   the moment the page loads, so every required field would be outlined red before
   anybody had typed a character. */
.field input:user-invalid, .field textarea:user-invalid {
  border-color: #b02020; box-shadow: 0 0 0 3px rgba(176, 32, 32, 0.12);
}
.field-note { display: block; margin: 7px 0 0; font-size: var(--t-small); color: var(--soft); line-height: 1.5; }

/* ---------------------------------------------------------------- photos
   A styled label standing in for the file input, because the native control
   cannot be styled and reads as "Choose File / no file chosen" on every phone.
   The input itself is still in the DOM and still focusable -- it is moved off
   screen rather than display: none, which would take it out of the tab order and
   out of reach of a screen reader. */
.shots-field { margin: 0 0 18px; }
.shots-in {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.shots-drop {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 16px; min-height: 56px; cursor: pointer;
  border: 1px dashed var(--rule); border-radius: 14px;
  background: rgba(var(--surface), 0.6);
  transition: border-color .16s ease, background .16s ease;
}
.shots-drop:hover { border-color: var(--steel); background: rgba(var(--surface), 0.85); }
/* The focus ring has to be driven by the INPUT's focus, since the input is the
   thing that actually receives it and the label is what is seen. */
.shots-in:focus-visible + .shots-drop {
  border-color: var(--steel); border-style: solid;
  box-shadow: 0 0 0 3px rgba(38, 94, 158, 0.16);
}
.shots-icon { flex: 0 0 auto; width: 26px; height: 26px; color: var(--steel); }
.shots-words { display: block; font-size: var(--t-body); line-height: 1.35; }
.shots-words b { display: block; font-weight: 500; }
.shots-words span { display: block; font-size: var(--t-small); color: var(--soft); margin-top: 3px; }

.shots-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; margin: 10px 0 0; padding: 0; list-style: none; }
/* The page's shell is <article class="doc">, and the site styles prose lists inside
   .doc with a hairline dash and a 20px indent. These previews are a grid of
   thumbnails, not prose, so both are turned off. The selector repeats .doc because
   `.doc ul li::before` is (0,1,3) and `.shots-list li::before` is only (0,1,2) --
   sitting later in the file does not win a specificity argument. */
.shots-list li { position: relative; margin: 0; }
.doc .shots-list li { padding-left: 0; }
.doc .shots-list li::before { content: none; }
.shots-list img { display: block; width: 100%; height: 84px; object-fit: cover; border-radius: 10px; border: 1px solid var(--rule); background: var(--wash); }
.shots-list figcaption { font-size: 0.66rem; color: var(--soft); margin-top: 4px; overflow-wrap: anywhere; }

/* ---------------------------------------------------------------- the rest */
/* A honeypot. NOT display: none -- some bots skip anything that is display: none,
   and the point is that they fill it in. Off screen and out of the tab order. */
.enq-trap { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

.enq-actions { display: flex; flex-direction: column; gap: 14px; margin: 4px 0 0; }
.enq-actions .btn { width: 100%; justify-content: center; min-height: 52px; }
.enq-privacy { margin: 0; font-size: var(--t-small); color: var(--soft); line-height: 1.55; }

.enq-msg { margin: 16px 0 0; padding: 0; font-size: var(--t-body); line-height: 1.55; }
.enq-msg:empty { display: none; }
.enq-msg.is-good, .enq-msg.is-bad { padding: 13px 15px; border-radius: 12px; }
.enq-msg.is-good { background: rgba(38, 94, 158, 0.08); border: 1px solid rgba(38, 94, 158, 0.22); }
.enq-msg.is-bad  { background: rgba(176, 32, 32, 0.07); border: 1px solid rgba(176, 32, 32, 0.26); }
/* The reference is meant to be read back over a phone, so it is set in the
   technical face and does not wrap mid-reference. */
.enq-ref { font-family: var(--tech); font-weight: 500; white-space: nowrap; }

.enq-other { margin: 0 0 8px; }
.enq-other h2 { font-size: var(--t-body); font-weight: 500; margin: 0 0 14px; }
.enq-other h2 b { font-family: var(--tech); font-size: var(--t-small); letter-spacing: 0.12em; color: var(--soft); font-weight: 500; margin-right: 8px; }

/* A quiet way in for the two people who read the enquiries. Not in the nav: it is
   not for visitors, and a nav link would invite the whole internet to try it. */
.enq-staff { margin: 22px 0 0; font-size: var(--t-small); color: var(--soft); }
.enq-staff a { color: var(--soft); }

@media (min-width: 640px) {
  .enq { padding: 30px 28px; }
  .enq-grid { grid-template-columns: 1fr 1fr; column-gap: 20px; }
  .enq-actions { flex-direction: row; align-items: center; gap: 20px; }
  .enq-actions .btn { width: auto; flex: 0 0 auto; }
  .enq-privacy { flex: 1 1 auto; }
}

@media (pointer: coarse) {
  .foot-links a { min-height: 44px; display: inline-flex; align-items: center; }
  .doc-back { min-height: 44px; }
  .doc a { min-height: 24px; }
  /* The enquiry form. A tablet is 820px wide and sails past every max-width rule
     above, so these are asked of the POINTER. This block stays last in the file:
     put it above the width queries and an equal-specificity rule below wins. */
  .field input { min-height: 52px; }
  .field textarea { min-height: 132px; }
  .shots-drop { min-height: 62px; }
  .enq-actions .btn { min-height: 52px; }
  .enq-staff a { display: inline-block; min-height: 44px; line-height: 44px; }
}
