/* ==========================================================================
   AK Telecom — public stylesheet
   One file. No framework, no preprocessor, no build step.
   Breakpoints: 1100 / 900 / 640. Large-first, max-width only.
   ========================================================================== */

/* --- Webfont --------------------------------------------------------------
   Inter, self-hosted, three weights, latin subset, woff2 only. Every browser
   this site supports has read woff2 since 2016, so a woff sibling would be
   three more files nobody downloads. The swap descriptor on each face is what
   keeps the first paint honest: the system stack renders immediately and Inter
   replaces it when it arrives, so there is no window of invisible text on a
   slow connection. The URLs are relative on purpose — an absolute one would
   put a third-party request on every page view, and the test asserts against
   it. Do not write the descriptor's name and value into this comment: the test
   counts occurrences and expects exactly one per face.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(../fonts/inter-400.woff2) format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(../fonts/inter-600.woff2) format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(../fonts/inter-700.woff2) format("woff2");
}

:root {
  /* Brand. Blue leads, red accents, black grounds. */
  --blue:       #375DA4;
  --blue-deep:  #24406F;
  --blue-wash:  #EDF3FC;
  --red:        #EC3338;
  --red-deep:   #BE262B;

  /* Ink and surfaces. */
  --ink:        #08111F;
  --ink-2:      #16243C;
  --dim:        #58677C;
  --line:       #DCE3ED;
  --bg:         #FFFFFF;
  --bg-2:       #F5F7FB;

  /* Glass — used by the hero card and the floating nav. */
  --glass:      rgba(255, 255, 255, .82);
  --glass-line: rgba(255, 255, 255, .62);

  /* Type. Inter once it arrives, the system stack until it does — and forever
     if the woff2 files are ever lost in a deploy. The stack is not a
     placeholder; it is the fallback that keeps the site legible when the font
     404s, which is why it stays spelled out in full. */
  --ff: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --fs-h1:   clamp(1.95rem, 3.2vw, 2.85rem);
  --fs-h2:   clamp(1.45rem, 2.1vw, 1.95rem);
  --fs-h3:   1.08rem;
  --fs-lede: clamp(.98rem, 1.1vw, 1.08rem);
  --lh:      1.6;
  /* Display tracking. Inter at 3.5rem reads loose at its default spacing, so
     the two display sizes and the counters pull in slightly. Body copy does
     not: tightening text you actually read costs legibility. */
  --track-h: -.022em;

  /* Rhythm. --pad-x is the horizontal gutter every full-width strip shares —
     the bands, the nav, the drawer, the footer. It is a token rather than a
     repeated clamp() because the nav computes its own max-width from it, which
     is what lands the logo on the same pixel as every heading below it. */
  --max:    1160px;
  --pad:    clamp(2.75rem, 5vw, 4.75rem);
  --pad-x:  clamp(1rem, 3.5vw, 2.25rem);
  --gap:    clamp(.9rem, 1.7vw, 1.5rem);
  --rad:    10px;
  --rad-lg: 18px;

  /* Depth and motion. Two layers each, not one: a tight 1–2px layer that reads
     as the contact edge, and a wide diffuse layer that reads as the cast. A
     single large blur on its own looks like a smudge printed under the element
     rather than the element sitting above the page. */
  --shadow-sm: 0 1px 2px rgba(11, 13, 16, .06);
  --shadow:    0 1px 2px rgba(11, 13, 16, .05), 0 4px 14px rgba(11, 13, 16, .06);
  --shadow-lg: 0 2px 6px rgba(11, 13, 16, .06), 0 20px 48px rgba(11, 13, 16, .14);
  --dur:  .38s;
  --ease: cubic-bezier(.22, .61, .36, 1);
}

/* --- Reset ---------------------------------------------------------------- */

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

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

body {
  margin: 0;
  overflow-x: hidden;            /* the canvas and the tilt card must never widen the page */
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ff);
  font-size: .95rem;
  line-height: var(--lh);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

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

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

/* Selected text takes the brand blue instead of the browser's own. White on
   --blue is 6.4:1, and because the selection paints its own background it reads
   the same on a light band and on a deep one. */
::selection { background: var(--blue); color: #fff; }

:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Skip link: present for keyboards, out of the way for everyone else. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 60;
  background: var(--ink);
  color: #fff;
  padding: .7rem 1.1rem;
  border-radius: 0 0 var(--rad) 0;
}
.skip:focus { left: 0; color: #fff; text-decoration: none; }

/* --- Type ---------------------------------------------------------------- */

/* text-wrap: balance evens out the line lengths of a heading rather than
   leaving one word alone on the last line; text-wrap: pretty does the same for
   the orphan at the end of a paragraph. A browser without them ignores the
   declaration entirely, which is the fallback — there is nothing to guard. */
h1, h2, h3, h4 {
  margin: 0 0 .6em;
  line-height: 1.18;
  font-weight: 700;
  letter-spacing: -.01em;
  text-wrap: balance;
}
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

/* The browser sets <address> in italic, and nothing on this site ever wanted that.
   There is exactly one address element in the whole tree — the postal address in
   the footer's "Reach us" column, includes/footer.php:56, so it is on every page —
   and it was the only slanted text anywhere in the footer, sitting directly above
   three upright contact links. On a supplier's site that block is a trust signal
   before it is prose: a real company at a real place in Kolkata. Italic reads as
   emphasis or as a quotation, and here it is neither; it is an artefact of an
   element whose UA style predates anybody caring how it looked.
   The margin is the second half of the same problem. Chrome gives <address> no
   margin at all, so the block ran straight into the <ul> beneath it, and because
   .foot li a is a 44px flex row the phone number's glyphs land about 16px below
   the address's last line — less than the address's own 23.8px leading, so the
   phone number read as a fourth line of the postal address rather than the first
   item of a list. .5rem takes that to roughly one clear line of separation, which
   distinguishes where-we-are from how-to-reach-us without splitting two groups
   that share one heading and one column. No print counterpart is needed: the
   print block hides .foot outright. */
address { font-style: normal; margin: 0 0 .5rem; }

/* The scale belongs on the elements, not only on utility classes. bv_head() puts
   .h1 on a hero heading and nothing at all on a section heading, and .h2/.h3 have
   no other consumer anywhere in the tree — so every section heading was rendering
   at the UA's own 1.5em: 24px beneath a 56px hero, and only 1.27x the lede sitting
   under it, which is a hierarchy shallow enough to read as unstyled. The classes
   stay as aliases for markup that wants a heading's size without its rank. A
   component still wins where it sets its own: .eyebrow, .card__title and .foot h3
   are all (0,1,0) or better against a bare element selector. */
.h1 { font-size: var(--fs-h1); letter-spacing: var(--track-h); }
h2, .h2 { font-size: var(--fs-h2); letter-spacing: var(--track-h); }
h3, .h3 { font-size: var(--fs-h3); }

/* The label above a section heading. Red, but not the same red on every tone.

   --red on white measures 4.118:1 at 12.48px/700, and 12.48px bold is not WCAG
   large text, so it needs 4.5. On the --bg-2 tint a run of two light bands
   carries it drops to 3.876. Six of the ten eyebrows on the home page sat
   there: four section labels and the two dates inside the news cards.
   --red-deep clears it — 5.78:1 on white, 5.44:1 on the tint — and it is not a
   new decision: .pill--red and .field__err already say that red text on a light
   background is the deeper one. The eyebrow was the only place that said
   otherwise.

   The other four sit on the fixed canvas, where the arithmetic inverts: --red
   is 4.72:1 over the scrim and --red-deep would be 3.37. So the two dark tones
   keep the bright red, and the cards on those tones take it back again — a
   .card is white on every tone, which is exactly what put the news dates in the
   failing group. Print counterpart at the foot of the file: on paper every band
   is white, so every eyebrow prints as the light-background one. */
.eyebrow {
  display: inline-block;
  margin: 0 0 .8rem;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--red-deep);
}
.band--deep .eyebrow,       .band--ink .eyebrow       { color: var(--red); }
.band--deep .card .eyebrow, .band--ink .card .eyebrow { color: var(--red-deep); }

.lede { font-size: var(--fs-lede); color: var(--ink-2); max-width: 58ch; text-wrap: pretty; }
.small { font-size: .875rem; }
.muted { color: var(--dim); }

ul.plain { list-style: none; margin: 0; padding: 0; }

/* --- Layers --------------------------------------------------------------- */

#fibre {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  background: var(--ink);
}

/* --- Bands ---------------------------------------------------------------- */

.band {
  position: relative;
  z-index: 1;
  padding: var(--pad) var(--pad-x);
  background: var(--bg);
}
main > .band:first-child {
  padding-top: clamp(1.1rem, 2.4vw, 1.85rem);
  padding-bottom: clamp(1.25rem, 2.6vw, 2rem);
}

.band__in { max-width: var(--max); margin: 0 auto; }
.band__in--read { max-width: 72ch; }
.band[id] { scroll-margin-top: 5.5rem; }

/* Listing intros are intentionally shallow: breadcrumb, title/count, and—on the
   catalogue—one compact taxonomy rail. */
.page-intro {
  padding-top: clamp(.85rem, 1.8vw, 1.35rem);
  padding-bottom: clamp(.95rem, 2vw, 1.5rem);
  border-bottom: 1px solid var(--line);
}
.page-intro .crumbs { margin-bottom: .1rem; }
.page-intro__top {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: .25rem var(--gap);
  margin-bottom: .45rem;
}
.page-intro__top h1, .page-intro__top h2, .page-intro__top p { margin: 0; }
.category-rail {
  display: flex;
  flex-wrap: nowrap;
  gap: .28rem;
  width: 100%;
  margin: .35rem 0 0;
  padding: 0 0 .2rem;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.category-rail::-webkit-scrollbar { display: none; }
.category-rail .btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: .25rem .52rem;
  border-color: rgba(55, 93, 164, .28);
  background: rgba(255, 255, 255, .7);
  color: var(--blue-deep);
  font-size: clamp(.68rem, .72vw, .78rem);
  white-space: nowrap;
}
.category-rail .btn:hover { background: var(--blue); border-color: var(--blue); color: #fff; }

.band--tight { padding-top: calc(var(--pad) / 2); padding-bottom: calc(var(--pad) / 2); }
.band--tint  { background: var(--bg-2); }
.band--wash  { background: linear-gradient(135deg, var(--blue-wash), var(--bg)); }
.band--light { background-image: linear-gradient(180deg, rgba(55, 93, 164, .025), transparent 28rem); }

/* Two light bands in a row read as one enormous white page. The home page runs
   2348px of it where the category cards meet the product grid, and there is
   nothing there to tell a reader one section has ended and another begun.
   The tint alone would not survive a run of three — white, tint, tint leaves
   the second seam invisible — so the hairline carries the separation and the
   tint only softens it. That works at any run length.
   The tone class comes from bv_open(), so a run can only arise from the order
   an editor puts blocks in; this needs no maintenance when they reorder them.
   Its print counterpart is in the @media print block at the foot of the file:
   at (0,2,0) this rule outranks print's own `.band { background: #fff }`. */
.band--light + .band--light {
  border-top: 1px solid var(--line);
  background: var(--bg-2);
}

.band--ink {
  background: var(--ink-2);
  color: #fff;
}
.band--ink .lede { color: rgba(255, 255, 255, .82); }
.band--ink .muted { color: rgba(255, 255, 255, .62); }
.band--ink a { color: #fff; }

/* The only bands that let the canvas through. */
.band--deep {
  background: transparent;
  color: #fff;
}
.band--deep .lede { color: rgba(255, 255, 255, .84); }
.band--deep .muted { color: rgba(255, 255, 255, .64); }

/* The scrim that makes white text on a live canvas legal.
   Deliberately sets no `overflow` and no `transform`: Task 27's pinned scene is a
   `position: sticky` child of this element, and either property would silently
   unstick it — `overflow` makes a scroll container, `transform` a containing block.
   `isolation: isolate` is what makes `z-index: -1` safe: without a stacking context
   the pseudo-element escapes the parent and paints behind the canvas.
   The gradient is inscribed exactly in its own box — 50% extent from a 50% centre —
   so its alpha reaches zero on all four edges. A larger extent clips the fade into a
   hard-edged rectangle. */
.band--deep .band__in { position: relative; isolation: isolate; }
.band--deep .band__in::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -56px -14px;               /* 14 < 16px: can never widen the document */
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 50%,
              rgba(7, 14, 28, .90)   0%,
              rgba(7, 14, 28, .84)  46%,
              rgba(7, 14, 28, .46)  74%,
              rgba(7, 14, 28, 0)   100%);
}

/* The head group — eyebrow, heading, lede — is one unit. The heading sits tight
   against the lede that explains it, and the air below the group is what separates
   the whole of it from the content. Written the other way round (the full gap on
   the heading, 1em on the lede) it measured 43px above the lede and 19px below it,
   so the lede read as a caption belonging to the grid rather than as the sentence
   under the heading.
   Only an h2 head is re-spaced. A hero h1 is followed by chips, not prose, and the
   one div.band__head in the tree (case-study.php) has no lede sibling to pair with.
   A browser without :has() keeps 1.4 gaps on both steps: evenly loose, never
   broken, and nothing else in the group moves. */
.band__head { max-width: 62ch; margin: 0 0 calc(var(--gap) * 1.4); }
h2.band__head:has(+ .lede)          { margin-bottom: calc(var(--gap) * .35); }
h2.band__head + .lede:not(:last-child) { margin-bottom: calc(var(--gap) * 1.4); }
.band__head h2 { margin-bottom: 0; }
.band__head:has(> h2) {
  max-width: none;
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: .8rem var(--gap);
}

/* --- Nav ------------------------------------------------------------------ */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(255, 255, 255, .88);
  border-bottom: 1px solid rgba(8, 17, 31, .09);
  backdrop-filter: saturate(1.6) blur(18px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .72);
}

/* The bar's inner box is deliberately NOT `max-width: var(--max)`. A band is a
   full-bleed section with --pad-x of its own padding and a 1180px --max box
   centred inside that, so its content starts at pad-x + (space - 1180) / 2.
   A 1180px nav box with its padding on the inside starts 40px further in, and
   the logo ends up sitting inboard of every heading on the page. Widening the
   box by exactly the padding it carries makes the two arithmetics identical at
   every viewport, because both use the same --pad-x.
   The padding stays here rather than moving up to .nav: .nav__drawer is a
   sibling of this element, and padding on the parent would inset the drawer's
   full-bleed border-top hairline at 1100px and below. */
.nav__in {
  max-width: calc(var(--max) + var(--pad-x) * 2);
  margin: 0 auto;
  padding: .35rem var(--pad-x);
  display: flex;
  align-items: center;
  gap: clamp(.65rem, 1.35vw, 1.15rem);
}

/* flex: 0 0 auto on the brand, and nowrap on the wordmark. Without them the bar
   solves an overflow by shrinking its first item: the logo gave up 1.2px, which
   was enough to break "AK Telecom" onto two lines and take the header to 110px
   tall. The links are the item that should absorb a deficit — see the note on
   .nav__links below — and a clipped wordmark is worse than a wrapped menu. */
.nav__logo { display: flex; align-items: center; gap: .6rem; margin-right: auto; min-height: 44px; flex: 0 0 auto; }
.nav__logo strong { white-space: nowrap; }
.nav__logo img { height: 38px; width: auto; }

/* flex-wrap on a bar that is not supposed to wrap: the menu is generated from the
   pages table, so its width is editor-controlled. Wrapping to a second line is an
   ugly header; a button shoved off the right edge is a page that scrolls sideways. */
.nav__links { display: flex; flex-wrap: wrap; align-items: center; gap: .15rem; list-style: none; margin: 0; padding: 0; }

/* .9rem and .6rem of side padding rather than .95/.8. Nine editor-controlled
   links at the old figures wanted 817px in a 1100px box that also had to hold a
   140px brand and an 85px button — 4px over, which is what wrapped the header
   onto two rows. These figures bring the same nine to 724px and leave real
   headroom for a tenth. The drawer is unaffected: its own rule below sets a
   48px target and .5rem of padding, and it is written later. */
.nav__links a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 .6rem;
  border-radius: var(--rad);
  color: var(--ink-2);
  font-weight: 600;
  font-size: .9rem;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav__links a:hover { background: var(--blue-wash); color: var(--blue-deep); text-decoration: none; }

/* Set by includes/header.php from current_route(). */
.nav__links a[aria-current="page"] { color: var(--blue); background: var(--blue-wash); }

.nav__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--rad);
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
}

/* Hidden on desktop, where .nav__links is the navigation. Below 1100px the
   media query makes it visible again, and JavaScript — not CSS — is what
   collapses it behind the toggle. See the note under the media queries.
   Its horizontal padding is the shared --pad-x so the drawer's links start on
   the same pixel as the bar's logo above them. */
.nav__drawer {
  display: none;
  border-top: 1px solid var(--line);
  background: var(--bg);
  padding: .5rem var(--pad-x) 1rem;
}
.nav__drawer .nav__links { flex-direction: column; align-items: stretch; }

/* --- Scroll progress ------------------------------------------------------ */

.progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 50;
  height: 3px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--blue), var(--red));
  pointer-events: none;
}

/* --- Footer --------------------------------------------------------------- */

.foot {
  position: relative;
  z-index: 1;
  overflow: hidden;
  background:
    radial-gradient(circle at 8% 0%, rgba(55, 93, 164, .22), transparent 24rem),
    radial-gradient(circle at 94% 100%, rgba(236, 51, 56, .09), transparent 22rem),
    linear-gradient(135deg, #07101e, #0d1b31 62%, #0a1425);
  color: rgba(255, 255, 255, .72);
  padding: clamp(1.35rem, 2.5vw, 2rem) var(--pad-x) .75rem;
  font-size: .86rem;
}
.foot::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: linear-gradient(90deg, var(--blue), #7698d4 55%, var(--red));
}
.foot__grid {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 3vw, 3rem);
}
.foot__grid > div {
  min-width: 0;
  padding: .35rem 0;
}
.foot__grid > div + div {
  padding-left: clamp(1rem, 2.5vw, 2.4rem);
  border-left: 1px solid rgba(255, 255, 255, .1);
}
.foot__brand {
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  min-height: 48px;
  color: #fff;
}
.foot__brand:hover { color: #fff; text-decoration: none; }
.foot__brand strong { font-size: 1.02rem; letter-spacing: -.01em; }
.foot__mark {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  flex: 0 0 48px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 13px;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .25);
}
.foot__mark img { width: 40px; height: 40px; object-fit: contain; }
.foot__tag { max-width: 34ch; margin: .65rem 0 .35rem; line-height: 1.45; }
.foot__brands { margin: 0; color: rgba(255, 255, 255, .52); font-size: .78rem; }
.foot h3 {
  color: #fff;
  font-size: .74rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  margin-bottom: .45rem;
}
.foot a { color: rgba(255, 255, 255, .76); transition: color var(--dur) var(--ease); }
.foot a:hover { color: #fff; text-decoration: none; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li a { display: inline-flex; align-items: center; gap: .45rem; min-height: 36px; }
.foot__nav ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 .65rem; }
.foot__contact address { margin-bottom: .25rem; line-height: 1.45; }
.foot__contact > p { margin-top: .25rem; color: rgba(255, 255, 255, .55); font-size: .78rem; }
.foot__bar {
  max-width: var(--max);
  margin: .8rem auto 0;
  padding-top: .65rem;
  border-top: 1px solid rgba(255, 255, 255, .1);
  display: flex;
  flex-wrap: wrap;
  gap: .35rem var(--gap);
  align-items: center;
  justify-content: space-between;
  color: rgba(255, 255, 255, .48);
  font-size: .75rem;
}

/* --- Buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  min-height: 44px;
  padding: .5rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--rad);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: .9rem;
  font-weight: 700;
  cursor: pointer;
  text-align: center;
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease),
              border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.btn:hover { text-decoration: none; transform: translateY(-2px); box-shadow: var(--shadow); }
.btn:active { transform: none; }
.btn:focus-visible { outline-color: var(--red); }

/* The key button carries a contact shadow at rest and a blue-tinted cast on
   hover. A neutral grey shadow under a saturated blue button is the detail that
   makes a control look pasted onto the page rather than sitting on it; the cast
   of a real object takes the colour of the object. Both rules match on two
   classes and are written after .btn:hover, so they win on specificity and on
   order. The translateY comes from .btn:hover and is already named in the
   reduced-motion block, so nothing new is owed there. */
.btn--key { background: var(--blue); border-color: var(--blue); color: #fff; box-shadow: var(--shadow-sm); }
.btn--key:hover { background: var(--blue-deep); border-color: var(--blue-deep); color: #fff; box-shadow: 0 6px 18px rgba(55, 93, 164, .28); }

.btn--danger { background: var(--red); border-color: var(--red); color: #fff; }
.btn--danger:hover { background: var(--red-deep); border-color: var(--red-deep); color: #fff; }

.btn--ghost { background: transparent; border-color: currentColor; color: inherit; }

/* Compact only horizontally. The 44px floor is not negotiable on a touch screen,
   so .btn--sm buys its compactness from padding and type size, never from height. */
.btn--sm { padding: .3rem .75rem; font-size: .9rem; }

.btn-row { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center; }

/* --- Pills ---------------------------------------------------------------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .2rem .65rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-2);
  color: var(--ink-2);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
}
.pill--blue { background: var(--blue-wash); border-color: #c9d6ec; color: var(--blue-deep); }
.pill--red  { background: #fdeced; border-color: #f4c3c5; color: var(--red-deep); }
.pill--calm { background: var(--bg-2); color: var(--dim); }

.pill-row { display: flex; flex-wrap: wrap; gap: .4rem; }

/* --- Grids ----------------------------------------------------------------
   A .grid is a <ul> at fifteen of its twenty-four call sites, so the list
   defaults have to go here: without this every card grid on the site sits
   40px right of the heading above it and flush to the right gutter. Markers
   never showed because .card is display:flex, which is why the indent was the
   only symptom.

   The UA's block margin goes with them. The note that used to sit here said the
   card bands' rhythm had been set with it in place; measuring says otherwise.
   Its top half never rendered at all — it collapses against the 42.56px the
   head group already leaves below itself. Its bottom half rendered in two
   places nobody chose: it escapes .band__in, which has no padding of its own,
   so every band ending in a grid stood 116.8px deep at the bottom against
   100.8px at the top; and where a card wall is followed by its CTA it was the
   only thing between them, .btn-row carrying no margin of its own by design
   (see .chips + .btn-row further down). Both figures were Chrome's 1em.

   :not(:last-child) states that space deliberately, and only where something
   follows the list inside the same box, so a band that ends in a grid now ends
   on its own padding. var(--gap) is not a new number: the pager already claims
   exactly that much after a list, and .dl-list + p uses it for the same
   junction one component over. It covers every tail the tree actually has —
   the btn-row bv_cta() emits under gallery_grid, post_list and product_grid,
   service.php's Call button, album.php's centred back-link, category.php's
   sort form sitting above its own results, and the pager, which brings the
   same figure and simply collapses with it. */

.grid { display: grid; gap: var(--gap); list-style: none; padding-inline-start: 0; margin-block: 0; }
.grid:not(:last-child) { margin-bottom: var(--gap); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid--auto { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid--split { grid-template-columns: 1.1fr .9fr; align-items: center; }

/* --- Cards ----------------------------------------------------------------
   `object-fit: contain` rather than `cover` on .card__media is a catalogue
   decision, not a taste one. These are photographs of instruments on white
   backgrounds; `cover` crops the ends off a fusion splicer to fill a box.
   `contain` letterboxes against --bg-2 and always shows the whole tool.
   -------------------------------------------------------------------------- */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 1px solid rgba(22, 36, 60, .12);
  border-radius: var(--rad-lg);
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(8, 17, 31, .04), 0 14px 34px rgba(25, 47, 82, .09);
}
.card::before {
  content: "";
  position: absolute;
  inset: 0 auto auto 0;
  z-index: 1;
  width: 3px;
  height: 0;
  background: linear-gradient(var(--blue), var(--red));
  transition: height var(--dur) var(--ease);
}
.card:focus-within::before, .card--lift:hover::before { height: 100%; }
.card__media { background: linear-gradient(145deg, #fbfcfe, var(--bg-2)); aspect-ratio: 16 / 10; overflow: hidden; }
.card__media img { width: 100%; height: 100%; object-fit: contain; }
.card__body { padding: .95rem 1rem 1.1rem; display: flex; flex-direction: column; gap: .45rem; flex: 1 1 auto; }

/* No font-size here, deliberately. Every one of the thirteen .card__title
   consumers in the tree is an <h3>, so the element rule hands it --fs-h3
   (19.2px) and the card inherits the site's own scale instead of a local
   opinion. It used to declare 1.05rem — 16.8px against 16px body copy, a ratio
   of 1.05, which is not a step a reader can see. Every card on the site read as
   a paragraph with a bold first line. --fs-h3 makes it 1.2, the same ratio the
   scale uses everywhere else. Nothing else in the card changes: line-height
   1.18 comes from the same element rule, `margin: 0` still defers the spacing
   to .card__body's flex gap, and the grid's stretch alignment absorbs the ~3px
   the taller line adds to a row. */
.card__title { font-weight: 700; margin: 0; transition: color var(--dur) var(--ease); }
.card__foot { margin-top: auto; padding-top: .6rem; }

/* Three things move on hover, not one: the card rises, its shadow deepens, and
   its edge and title take the brand blue. A lift on its own reads as a physical
   nudge; the colour is what says "this is a link you are about to follow".
   border-color joins the transition list so the edge fades rather than snaps.
   Only the transform needs naming in the reduced-motion block — a colour with a
   zeroed duration simply arrives instantly, which is the desired behaviour. */
.card--lift { transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.card--lift:hover, .card--lift:focus-within { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: rgba(55, 93, 164, .38); }
.card--lift:hover .card__title, .card--lift:focus-within .card__title { color: var(--blue-deep); }

/* A card on a deep band is a pale panel on near-black, where the light --shadow
   is arithmetically invisible: there is nothing darker than the canvas for it to
   darken. Without this the deep cards band is the one place on the site where
   the cards look pasted flat onto the page. Its print counterpart is in the
   @media print block — at (0,2,0) this outranks print's `.card`. */
.band--deep .card { box-shadow: 0 18px 40px rgba(3, 10, 24, .45); }

/* …and because the rule above matches .card--lift:hover's (0,2,0) and is written
   after it, the hover state has to be restated at (0,3,0) or a deep card would
   lose its bloom on hover instead of gaining one. */
.band--deep .card--lift:hover { box-shadow: 0 26px 60px rgba(3, 10, 24, .6); }

/* A card keeps its white background on every tone — `.card` sets `background:
   var(--bg)` once and no tone overrides it, and the shadow two rules up exists
   precisely to lift that white panel off the dark canvas. What no tone did was
   put the text colour back. `.band--deep { color: #fff }` inherits straight
   through the card wall, so on the deep bands the title, the body copy and the
   card foot were all white on white: 1:1 contrast, four cards in the homepage's
   "Why buy here" band and every card in the news band rendering as blank
   rectangles. Only the parts that declare their own colour survived — the blue
   .ico and the blue anchor — which is why the band looked sparse rather than
   broken, and why it took a per-band measurement to find.
   Three legs, because three different things set colour on the way in:
   the tone's own (0,1,0) `color`, beaten by re-declaring at the card boundary;
   `.band--deep .muted` at (0,2,0), which needs (0,3,0) — white at 64% alpha over
   white is as unreadable as solid white; and `.band--ink a` at (0,2,0), the same
   trap for links. Deep needs no anchor reset: it never overrode `a`, which is why
   deep-band card links were the one legible thing in them.
   `.band--ink` has no emitter today — bv_open() is a hard light/deep binary — but
   it is a maintained tone with four rules and a print counterpart, so it is fixed
   here rather than left as the same landmine for whoever wires it up.
   Counterparts are in the @media print block: at (0,2,0) and (0,3,0) these
   outrank print's own reset, and --ink and --dim are not #000. */
.band--deep .card,        .band--ink .card        { color: var(--ink); }
.band--deep .card .muted, .band--ink .card .muted { color: var(--dim); }
.band--ink  .card a { color: var(--blue); }

/* A whole card that is one link: the anchor covers it, the text stays selectable. */
.card__link { position: absolute; inset: 0; z-index: 2; }
.card__link span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* --- Glass ---------------------------------------------------------------- */

.glass {
  position: relative;
  isolation: isolate;               /* load-bearing: see the sheen note below */
  border-radius: var(--rad-lg);
  border: 1px solid var(--glass-line);
  background: var(--glass);
  backdrop-filter: saturate(1.5) blur(18px);
  box-shadow: var(--shadow-lg);
  padding: clamp(1.2rem, 2.6vw, 2rem);
}

/* .glass__sheen sits at z-index -1. Without `isolation: isolate` on the parent a
   negative-z child escapes behind the parent's OWN background and border — the
   sheen disappears and on some engines paints over the border. */
.glass__sheen {
  position: absolute;
  inset: 0;
  z-index: -1;                      /* behind the content, in front of nothing else */
  border-radius: inherit;
  background: radial-gradient(120% 90% at 12% 8%,
              rgba(255, 255, 255, .55), rgba(255, 255, 255, 0) 62%);
  pointer-events: none;
}

/* Glass on a deep band. The contrast floor rests on this rule: frosted white over
   the canvas cannot carry white text, so on `.band--deep` the pane inverts to dark
   and the sheen drops to a hint. Measured 5.23:1 body, 8.82:1 headings.
   The sheen has to dim in the same breath — a .55 white highlight over a dark pane
   lifts the top-left corner to a pale patch exactly where a card's heading sits.
   The border and shadow move for the same reason: a --glass-line hairline vanishes
   against dark, and the light-band shadow reads as grey haze rather than depth. */
.band--deep .glass {
  background-color: rgba(9, 17, 32, .80);
  border-color: rgba(255, 255, 255, .16);
  box-shadow: 0 2px 6px rgba(3, 10, 24, .4), 0 20px 48px rgba(3, 10, 24, .55);
}
.band--deep .glass__sheen {
  background: radial-gradient(120% 90% at 12% 8%,
              rgba(255, 255, 255, .14), rgba(255, 255, 255, 0) 62%);
}

/* No blur available: fall back to opaque, never to translucent-without-blur.
   A 72%-opaque white pane with no blur behind it lets whatever is underneath read
   straight through the text, and the sticky nav is the worst case because content
   scrolls under it continuously. Note the shape of the query — a value-less
   `@supports not (backdrop-filter)` is an invalid feature query and evaluates false
   on EVERY engine, so the fallback would never apply anywhere. All three rules win
   on source order, not specificity, so this block must stay after what it overrides. */
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: var(--bg-2); border-color: var(--line); }
  .band--deep .glass { background: rgb(9, 17, 32); }
  .nav { background: var(--bg); }
}

/* Tilt hosts are progressively enhanced by tilt.js. The child depth hook stays
   flat before JavaScript and under reduced motion. */
[data-tilt] {
  position: relative;
  transform-style: preserve-3d;
  transition: transform var(--dur) var(--ease);
  will-change: transform;
}
[data-tilt].tilt { perspective: 1000px; }
[data-tilt] [data-tz] { transform: translateZ(0); transform-style: preserve-3d; }
.tilt__inner {
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  transform-style: preserve-3d;
}

/* --- Reveal on scroll ----------------------------------------------------- */

.reveal {
  opacity: 1;
  transform: none;
}
.motion-ready .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.motion-ready .reveal.is-in { opacity: 1; transform: none; }
.motion-ready .reveal--d1.is-in { transition-delay: .08s; }
.motion-ready .reveal--d2.is-in { transition-delay: .16s; }
.motion-ready .reveal--d3.is-in { transition-delay: .24s; }

/* --- 360° viewer (dormant until a turntable sequence is shot) --------------
   Visible only when a data-frames attribute is present, which only happens when
   an album of turntable frames exists. Complete, tested and invisible.
   -------------------------------------------------------------------------- */

.spin { display: none; }
.spin[data-frames] { display: block; position: relative; touch-action: pan-y; cursor: grab; }
.spin img { width: 100%; }
.spin__hint { position: absolute; bottom: .6rem; left: 50%; transform: translateX(-50%); }

/* --- Forms ---------------------------------------------------------------- */

.form { display: grid; gap: .85rem; max-width: 660px; }
.form--wide {
  max-width: none;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .85rem var(--gap);
}
.form--enquiry { max-width: 860px; }
.form--service { max-width: 940px; }
.form--wide > .eyebrow,
.form--wide > .field:has(textarea),
.form--wide > .field--act { grid-column: 1 / -1; }

.field { display: grid; align-content: start; gap: .3rem; min-width: 0; margin: 0; }
.field__label { font-weight: 700; font-size: .86rem; color: inherit; }
.field__label .req { color: var(--red); }

.field input, .field select, .field textarea {
  width: 100%;
  min-height: 46px;
  padding: .62rem .78rem;
  border: 1px solid rgba(22, 36, 60, .22);
  border-radius: var(--rad);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              background var(--dur) var(--ease);
}
.field textarea { min-height: 112px; resize: vertical; }

.field input:hover, .field select:hover, .field textarea:hover { border-color: rgba(55, 93, 164, .48); }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--blue);
  background: #fff;
  box-shadow: 0 0 0 4px rgba(55, 93, 164, .15);
}

.field__hint { font-size: .84rem; color: var(--dim); }
.field__err { font-size: .86rem; color: var(--red-deep); font-weight: 600; }
.field.is-bad input, .field.is-bad select, .field.is-bad textarea { border-color: var(--red); }

/* .field is a one-column grid, so a button inside one stretches the full width
   of the form — the whole band at .form--wide. This is the submit row. */
.field--act { justify-items: start; margin: 0; }

.form-note {
  padding: .9rem 1.1rem;
  border-radius: var(--rad);
  border: 1px solid var(--line);
  background: var(--bg-2);
  font-size: .93rem;
}
.form-note--ok  { background: #eef8f0; border-color: #b8dcc0; color: #14532d; }
.form-note--bad { background: #fdeced; border-color: #f4c3c5; color: var(--red-deep); }

/* The honeypot. Off-screen, not display:none, and never type="hidden" — both of
   those are detectable by a scraper in one line, and both are what a naive
   honeypot uses. An off-screen but genuinely rendered text input is filled by
   form-stuffing bots and never seen by a person. Screen readers CAN reach it,
   which is why Task 28's honeypot_field() adds tabindex="-1", autocomplete="off"
   and aria-hidden="true". */
.hp {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- Spec tables (product specifications, downloads) ---------------------- */

.tbl-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--rad-lg); }
.tbl { width: 100%; border-collapse: collapse; font-size: .94rem; }
.tbl th, .tbl td { padding: .7rem .9rem; text-align: left; border-bottom: 1px solid var(--line); }
.tbl th { background: var(--bg-2); font-weight: 700; white-space: nowrap; }
.tbl tr:last-child td { border-bottom: 0; }

/* Scoped to tbody so the header row does not light up under the cursor. These
   tables are specification lists, and a row tint is what lets an eye carry one
   spec across a wide table without losing the line. The tbody element is
   inserted by the parser whether or not the template writes it, so this works
   either way. */
.tbl tbody tr:hover td { background: var(--blue-wash); }

.tbl td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Two-column key/value specs read better than a 2-row table on a phone. */
.specs { display: grid; grid-template-columns: max-content 1fr; gap: .5rem 1.2rem; }
.specs dt { font-weight: 600; color: var(--dim); }
.specs dd { margin: 0; }

/* --- Pagination ----------------------------------------------------------- */

.pager { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; justify-content: center; margin-top: var(--gap); }
.pager a, .pager span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 .6rem;
  border: 1px solid var(--line);
  border-radius: var(--rad);
  font-weight: 600;
}
.pager a:hover { background: var(--blue-wash); text-decoration: none; }
.pager .is-here { background: var(--blue); border-color: var(--blue); color: #fff; }
.pager .is-off { color: var(--dim); border-style: dashed; }

/* --- Breadcrumbs ---------------------------------------------------------- */

.crumbs { display: flex; flex-wrap: wrap; gap: .3rem .5rem; font-size: .86rem; color: var(--dim); margin: 0 0 .9rem; padding: 0; }
/* A breadcrumb anchor at .86rem is a 16px-tall tap target, and it was the last
   interactive thing on the site under the 44px floor. The floor is not a phone
   rule — a trackpad and a shaky hand miss a 16px target on a 27" monitor too —
   so this is unconditional rather than tucked inside the 640 block. Only the box
   grows; the type stays .86rem, so the trail still reads as a trail and not as a
   second nav. `.crumbs li` becomes a flex container so the "/" separator stays
   centred against the taller anchor beside it, and the anchor needs
   `inline-flex` because `min-height` does nothing to a non-replaced inline box. */
.crumbs li { list-style: none; display: flex; align-items: center; }
.crumbs li a { display: inline-flex; align-items: center; min-height: 44px; }
.crumbs li + li::before { content: "/"; margin-right: .5rem; color: var(--line); }

/* --- Empty states --------------------------------------------------------- */

.empty {
  text-align: center;
  padding: clamp(2rem, 6vw, 4rem) 1rem;
  border: 1px dashed var(--line);
  border-radius: var(--rad-lg);
  color: var(--dim);
}

/* --- Utilities (the whole list — nothing gets added without a use) -------- */

.stack { display: grid; gap: var(--gap); }
.stack--sm { gap: .6rem; }
.center { text-align: center; }
.center .lede { margin-inline: auto; }
.nowrap { white-space: nowrap; }
.sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); border: 0;
}

/* --- Motion hooks (driven by assets/js/scroll.js) -------------------------
   These serve the four scripts rather than any band, but they are ordinary
   component rules and so they belong here, ABOVE the breakpoints. Written
   below them instead, `.pin { height: 260vh }` would beat the 640px
   `.pin { height: auto }` on source order — a media query adds no
   specificity — and every phone would scroll 260vh of nothing.
   The .reveal--dN delays are not repeated: Task 24 already sets them on
   .is-in, which is the more correct hook anyway. */

[data-para] {
  --para: 0px;                    /* scroll.js overwrites it every frame */
  transform: translate3d(0, var(--para), 0);
  will-change: transform;
}

/* Tabular figures. Without them a counter ticking 8 → 9 → 10 changes width on
   every single frame and shoves whatever sits next to it sideways. */
.count { font-variant-numeric: tabular-nums; }

/* Desktop flagship: one controlled glass stage, one active model. With no JS the
   ordinary grid below remains visible; motion-ready is only added after the shared
   observer is available, so progressive enhancement cannot hide the content. */
.pin { --pin-p: 0; position: relative; height: auto; }
.pin__scene { position: static; height: auto; min-height: 0; overflow: visible; }
.pin__stage {
  width: 100%;
  max-width: var(--max);
  padding: clamp(1rem, 2.3vw, 1.65rem);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--rad-lg);
  background: rgba(9, 17, 32, .84);
  box-shadow: 0 28px 70px rgba(3, 10, 24, .52);
  backdrop-filter: saturate(1.25) blur(18px);
}
.pin__stage .band__head { margin-bottom: 1rem; }
.pin__steps {
  --pin-p: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
  margin: 0;
  padding: 0;
  list-style: none;
}
.motion-ready .pin { height: 220vh; }
.motion-ready .pin__scene {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 560px;
  display: grid;
  align-items: center;
  overflow: hidden;
}
.motion-ready .pin__steps { position: relative; display: block; min-height: min(52vh, 480px); }
.pin__step {
  position: absolute;
  inset: 0;
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, .95fr) minmax(280px, .75fr);
  gap: clamp(1.2rem, 3vw, 2.5rem);
  align-items: center;
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, 16px, 0) scale(.985);
  pointer-events: none;
  transition: opacity .48s var(--ease), transform .55s var(--ease), visibility .55s;
}
.pin__step.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
}
.pin__media {
  height: min(50vh, 450px);
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--rad-lg);
  background: linear-gradient(145deg, #fff, #eef2f8);
}
.pin__media img { display: block; width: 100%; height: 100%; max-width: none; max-height: none; object-fit: contain; filter: drop-shadow(0 18px 22px rgba(8, 17, 31, .16)); }
.pin__copy { max-width: 44ch; padding: .5rem; }
.pin__copy h3 { margin: 0 0 .45rem; font-size: clamp(1.35rem, 2.1vw, 1.9rem); letter-spacing: var(--track-h); }
.pin__copy > p:not(.pin__n):not(.pin__ref) { margin-bottom: .7rem; color: rgba(255, 255, 255, .78); }
.pin__n { margin: 0 0 .35rem; font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: .08em; color: var(--red); }
.pin__ref { margin: .65rem 0 0; padding-top: .65rem; border-top: 1px solid rgba(255, 255, 255, .1); font-size: .78rem; }

.nav[data-stuck] {
  box-shadow: var(--shadow);
  border-bottom-color: var(--line);
}

/* Tracking has to be exact while the pointer is on the card; the eased slide
   back to level is what the transition in .tilt__inner is for. */
[data-tilt]:hover .tilt__inner { transition: none; }

/* spin.js builds the img with class spin__img. Positioning for the hint is
   already set in the components section — only the chip styling is new. */
.spin__img { display: block; width: 100%; height: auto; user-select: none; }
.spin[data-drag] { cursor: grabbing; }
.spin__hint {
  font-size: .8rem;
  color: var(--dim);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: 999px;
  padding: .15rem .6rem;
}
.spin[data-drag] .spin__hint { opacity: 0; }

/* ==========================================================================
   Block components. Every selector here is emitted by a template in Task 27.
   ========================================================================== */

/* Icons. The sprite supplies viewBox, stroke and stroke-width, so these rules do
   sizing and inline flow only — and they are what lets an icon sit beside text
   at all, since the global reset makes every svg display: block. Without the
   inline-block an icon would take its own line everywhere on the site. */
.ico     { display: inline-block; flex: none; width: 24px; height: 24px; vertical-align: -.18em; }
.ico--sm { width: 18px; height: 18px; }
.ico--lg { width: 28px; height: 28px; }
.ico--ph { width: 100%; height: 100%; padding: 22%; color: var(--dim); }

/* Cards, extended. .card, .card--lift, .card__body, .card__title, .card__media,
   .card__foot and .card__link are Task 24's; these four serve cards,
   product_grid and gallery_grid. .card__media--crop img has the same specificity
   as Task 24's .card__media img and wins on source order alone — which is why
   this section sits below those rules and above the breakpoints. */
.card__body > .ico { color: var(--blue); margin-bottom: .7rem; }
.card__foot .ico   { color: var(--blue); transition: transform var(--dur) var(--ease); }
.card--lift:hover .card__foot .ico { transform: translateX(4px); }
.card__media--crop img { object-fit: cover; }

/* Hero: a compact equipment stage with a shallow 3D plate rather than an
   unconstrained photograph occupying most of the viewport. */
.hero        { display: grid; grid-template-columns: 1.06fr .94fr; gap: clamp(1.25rem, 3vw, 2.5rem); align-items: center; }
.hero__copy  { max-width: 56ch; }
.hero__media {
  --para: 0px;
  display: flex;
  justify-content: center;
  min-height: 300px;
  padding: clamp(.8rem, 2vw, 1.35rem);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--rad-lg);
  background:
    radial-gradient(circle at 70% 18%, rgba(95, 139, 222, .23), transparent 36%),
    rgba(9, 17, 32, .76);
  box-shadow: 0 24px 62px rgba(3, 10, 24, .45);
  backdrop-filter: saturate(1.4) blur(16px);
}
.hero__media::after {
  content: "";
  position: absolute;
  inset: auto 12% 7% 12%;
  height: 14%;
  border-radius: 50%;
  background: rgba(3, 10, 24, .38);
  filter: blur(20px);
  transform: translateZ(-20px);
  pointer-events: none;
}
.hero__img   { position: relative; z-index: 1; width: auto; max-width: 100%; max-height: 390px; margin: auto; object-fit: contain; filter: drop-shadow(0 22px 26px rgba(0, 0, 0, .26)); }

/* Every media column in this file is a grid item wrapping an <img> that carries an
   intrinsic width attribute. A grid item's min-width defaults to auto, so the column
   refuses to shrink below that intrinsic width and the band overflows — visible
   between 900 and 1100, where the two-column layout is still on but the viewport is
   narrower than the image. One rule for all three columns, and on the wrapper rather
   than the img, because the overflow belongs to the grid item. */
.hero__media, .split__media, .strip__media { min-width: 0; }

.chips       { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 1.3rem 0 0; padding: 0; }
.chip        { display: flex; flex-direction: column; padding: .5rem .9rem; border: 1px solid var(--glass-line); border-radius: var(--rad); background: var(--glass); }
.chip b      { font-size: 1.05rem; line-height: 1.2; }
.chip .muted { font-size: .78rem; line-height: 1.35; }

/* A chip borrows --glass and --glass-line from the pane above, but it never
   borrowed the inversion that comes with them. On a deep band that pane resolves to
   about rgb(186, 188, 193) over the canvas, and the band's own `color: #fff`
   inherits straight into the chip: white bold text on light grey, measured at
   1.90:1, with the .muted label under it at roughly 1.35:1. Both are illegible, and
   the hero is the one place they appear. Inverted to the values `.band--deep .glass`
   already uses — 18.98:1 for the value, 8.08:1 for the label — and the hairline
   lifted to white, because --glass-line cannot be seen against dark.
   No backdrop-filter here, deliberately: a chip is 100px wide, its pane is 80%
   opaque over near-black, and adding blur would mean a fourth entry in the no-blur
   @supports block above for no legibility gained. The 1.35 on the label is the same
   repair from the other side — inherited 1.6 body leading put 7px of air between
   two lines that are meant to read as one spec. */
.band--deep .chip { background-color: rgba(9, 17, 32, .80); border-color: rgba(255, 255, 255, .16); }

/* The chips and the button row below them touched exactly, with no gap at all.
   .chips has `margin: 1.3rem 0 0` — nothing on the bottom — and bv_cta() emits
   its row as a <p>, so the sheet's `p { margin: 0 0 1em }` gives it no top
   margin either. Stated as an adjacency rather than on .btn-row itself, because
   .btn-row is also two filter <nav>s and a row of spec pills elsewhere, and none
   of those wants a hero's worth of space above it. */
.chips + .btn-row { margin-top: 1.4rem; }

/* Call to action. One centred column; the phone line is a 44px touch target. */
.cta          { max-width: 62ch; margin: 0 auto; text-align: center; }
.cta .btn-row { justify-content: center; margin-top: 1.5rem; }
.cta__phone   { display: flex; align-items: center; justify-content: center; gap: .5rem; margin: 1.1rem 0 0; font-size: var(--fs-lede); }
.cta__phone a { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }

/* Prose — the only place long-form HTML lands: rich_text, an FAQ answer, the copy
   side of a split. Width is a modifier because 100ch is unreadable and 54ch
   wastes a page of tables. */
.prose, .prose--narrow { max-width: 68ch; }
.prose--wide           { max-width: 100%; }
.prose > :last-child   { margin-bottom: 0; }
.prose h2, .prose h3   { margin-top: 1.6em; }
.prose ul, .prose ol   { margin: 0 0 1em; padding-left: 1.2rem; }
.prose li              { margin-bottom: .35em; }
.prose img             { border-radius: var(--rad); margin: .4rem 0 1.2rem; }
.prose a               { text-decoration: underline; text-underline-offset: 2px; }

/* Split: copy one side, a tilted image the other. The registry field is labelled
   "Image sits", so the modifier names the side the *image* takes. The DOM is always
   copy-then-media, which already puts the image on the right — so split--right needs
   no rule at all and only split--left reorders. Both halves are direct grid children,
   so `order` applies. */
.split        { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); align-items: center; }
.split--left .split__media { order: 1; }
.split--left .split__copy  { order: 2; }
.split__img   { width: 100%; border-radius: var(--rad-lg); box-shadow: var(--shadow); }

.ticks       { list-style: none; margin: 1.2rem 0 0; padding: 0; display: grid; gap: .55rem; }
.ticks li    { display: flex; align-items: flex-start; gap: .6rem; }
.ticks .ico  { color: var(--blue); margin-top: .18em; }

/* Spec strip: Task 24's <dl class="specs"> beside one image. */
.strip       { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); align-items: center; }
.strip__copy { max-width: 60ch; }

/* A height ceiling, which .hero__img does not need and this does. Both declare
   width: 100% and let the height follow the aspect ratio; that works in the hero
   because its .95fr track is 534px and the copy beside it is a full h1, lede,
   chips and button row — 559px of photograph against 596px of text. Here the copy
   is a head group and nothing else, 255px of it, while a 1fr track is 575px, so a
   near-square product cut-out rendered 601px tall: 2.36 times the height of the
   prose it illustrates, and the largest image on the page including the hero's.
   30rem caps it at 480px, still the second-largest image on the site, which is
   the hierarchy this band should have. A cap only ever shrinks, so a strip that
   does carry its ten spec rows keeps a taller copy column and an image sized to
   it. width: auto is what lets max-height win over max-width; margin-left: auto
   then puts the narrowed photograph's right edge back on the band's own right
   edge instead of leaving it ragged, and is reset where the block folds to one
   column. */
.strip__img  { display: block; width: auto; height: auto; max-width: 100%; max-height: 30rem; margin-left: auto; border-radius: var(--rad-lg); box-shadow: var(--shadow); }

/* Counters. .count is Task 25's (tabular-nums, so the number does not jitter as
   it counts); .stats is the row it sits in. auto-fit rather than a fixed column
   count: three, four or five figures all read as one even row. */
.stats       { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: .65rem; text-align: center; }
.stats--compact { padding: .65rem; border: 1px solid rgba(255, 255, 255, .14); border-radius: var(--rad-lg); background: rgba(9, 17, 32, .68); backdrop-filter: blur(14px); }
.stats--compact .stat { padding: .55rem .8rem; border-right: 1px solid rgba(255, 255, 255, .1); }
.stats--compact .stat:last-child { border-right: 0; }
.stat .count { display: block; font-size: clamp(1.55rem, 3vw, 2.35rem); font-weight: 700; line-height: 1.05; letter-spacing: var(--track-h); color: var(--blue); }
.band--deep .stat .count { color: #fff; }

/* The label is a caption, not a sentence: small, uppercase, widely tracked, and
   dimmed so the figure above it is unambiguously the thing being read. At .9rem
   in the same weight and colour as body copy it competed with its own number.
   Tracking is positive here and negative on the figure for the same reason —
   tight display type reads as one word, loose small caps read as a label.
   --dim is 4.99:1 on --bg, so it clears AA at this size.
   The deep-band colour is not optional: the base rule sets a dark grey, the home
   page's stats band is deep, and --dim on the near-black canvas would be all but
   invisible. rgba(255, 255, 255, .64) is the sheet's existing .band--deep .muted
   value — 8.16:1 over --ink — so the two dimmed roles stay one colour. */
.stat__label { display: block; margin-top: .45rem; font-size: .78rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--dim); }
.band--deep .stat__label { color: rgba(255, 255, 255, .64); }

/* FAQ. The default disclosure triangle is removed and i-arrow rotates in its
   place — that rotation is state, not decoration, so it stays under reduced
   motion; only its transition is dropped. */
.qa-list    { display: grid; gap: .6rem; }
.qa         { border: 1px solid var(--line); border-radius: var(--rad); background: var(--bg); box-shadow: var(--shadow-sm); }
.qa summary { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 44px; padding: .8rem 1rem; font-weight: 600; cursor: pointer; list-style: none; }
.qa summary::-webkit-details-marker { display: none; }
.qa summary .ico       { color: var(--blue); transition: transform var(--dur) var(--ease); }
.qa[open] summary .ico { transform: rotate(90deg); }
.qa__a      { padding: 0 1rem 1rem; }

/* Client logos. A fixed cell is what makes nine logos of nine different
   proportions read as a row instead of a jumble. Greyscale at rest keeps a wall
   of other people's brand colours from competing with this one. */
.logos      { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--gap); align-items: center; justify-content: center; }
.logo       { display: flex; align-items: center; justify-content: center; width: 160px; min-height: 64px; }
.logo a     { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 44px; }
.logo__img  { max-height: 56px; width: auto; filter: grayscale(1); opacity: .72; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.logo:hover .logo__img { filter: none; opacity: 1; }
.logo__text { font-weight: 600; text-align: center; color: var(--dim); }

/* Testimonials. .glass is Task 24's and already carries the 5.23:1 contrast floor
   on a deep band, so the quote adds shape and nothing about colour. */
.quotes    { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--gap); }
.quote     { margin: 0; padding: 1.4rem; border-radius: var(--rad-lg); }
.quote__t  { margin: 0; font-size: var(--fs-lede); }
.quote__by { display: flex; flex-direction: column; margin-top: 1rem; font-style: normal; }
.quote__by .muted { font-size: .85rem; }

/* Product detail uses a contained equipment plate and dense information panels;
   it deliberately avoids the square, cropped gallery-tile treatment. */
.product-detail { padding-top: clamp(1.25rem, 2.5vw, 2.25rem); padding-bottom: clamp(2rem, 4vw, 3.5rem); }
.product-hero.grid--split { grid-template-columns: minmax(0, 1.02fr) minmax(320px, .88fr); align-items: start; gap: clamp(1rem, 2.5vw, 2rem); }
.product-gallery { min-width: 0; display: grid; gap: .65rem; }
.shot__a.product-gallery__main {
  position: relative;
  width: 100%;
  height: clamp(280px, 36vw, 430px);
  aspect-ratio: auto;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: clamp(.7rem, 2vw, 1.25rem);
  border: 1px solid rgba(55, 93, 164, .16);
  border-radius: var(--rad-lg);
  background:
    radial-gradient(circle at 74% 20%, rgba(55, 93, 164, .12), transparent 32%),
    linear-gradient(145deg, #fff, #edf2f9);
  box-shadow: 0 20px 44px rgba(25, 47, 82, .12);
  cursor: zoom-in;
  touch-action: manipulation;
}
.product-gallery__main::after {
  content: "";
  position: absolute;
  inset: auto 18% 6% 18%;
  height: 8%;
  border-radius: 50%;
  background: rgba(8, 17, 31, .12);
  filter: blur(13px);
  pointer-events: none;
}
.shot__a.product-gallery__main img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
  transform-origin: 50% 50%;
  transition: transform var(--dur) var(--ease);
}
.product-gallery:not([data-zoomed="1"]) .product-gallery__main:hover img { transform: scale(1.025); }
.product-gallery[data-zoomed="1"] .product-gallery__main { cursor: zoom-out; }
.product-gallery[data-zoomed="1"] .product-gallery__main img { transform: scale(1.85); }
.product-gallery__thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .5rem; margin: 0; }
.product-gallery__thumbs .shot__a {
  aspect-ratio: 4 / 3;
  padding: .25rem;
  border: 1px solid var(--line);
  background: #fff;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.product-gallery__thumbs .shot__a img { object-fit: contain; }
.product-gallery__thumbs .shot__a:hover,
.product-gallery__thumbs .shot__a.is-current { border-color: var(--blue); box-shadow: 0 0 0 2px rgba(55, 93, 164, .16); }
.product-gallery__spin { border-radius: var(--rad-lg); overflow: hidden; background: #fff; }
.product-summary {
  position: sticky;
  top: 5.25rem;
  padding: clamp(1.15rem, 2.3vw, 1.75rem);
  border: 1px solid rgba(55, 93, 164, .16);
  border-radius: var(--rad-lg);
  background: rgba(255, 255, 255, .84);
  box-shadow: 0 18px 45px rgba(25, 47, 82, .11);
  backdrop-filter: saturate(1.3) blur(15px);
}
.product-summary::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  border-radius: var(--rad-lg) 0 0 var(--rad-lg);
  background: linear-gradient(var(--blue), var(--red));
}
.product-summary h1 { margin-bottom: .5rem; }
.product-summary .btn-row { gap: .5rem; margin-bottom: .8rem; }
.product-summary .ticks { margin-top: .9rem; margin-bottom: 1rem; }
.product-details { background: var(--bg-2); }
.product-details__grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(280px, .8fr); gap: var(--gap); align-items: start; }
.product-panel {
  min-width: 0;
  padding: clamp(1rem, 2.2vw, 1.5rem);
  border: 1px solid var(--line);
  border-radius: var(--rad-lg);
  background: #fff;
  box-shadow: var(--shadow);
}
.product-panel--description { grid-column: 1 / -1; }
.product-panel h2 { margin-bottom: .8rem; }
.product-panel .prose { max-width: 78ch; }
.product-panel .specs { margin: 0; }
.product-panel .dl__a { box-shadow: none; }
.product-enquiry { padding-top: clamp(2rem, 4vw, 3.5rem); padding-bottom: clamp(2rem, 4vw, 3.5rem); }
.enquiry-shell {
  display: grid;
  grid-template-columns: minmax(230px, .62fr) minmax(0, 1.38fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding: clamp(1.15rem, 2.6vw, 2rem);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--rad-lg);
  background: rgba(9, 17, 32, .80);
  box-shadow: 0 24px 60px rgba(3, 10, 24, .48);
  backdrop-filter: saturate(1.25) blur(18px);
}
.enquiry-shell__intro { align-self: start; position: sticky; top: 5.25rem; }
.enquiry-shell__intro h2 { font-size: clamp(1.35rem, 2vw, 1.8rem); }
.enquiry-shell__context { margin-top: 1rem; padding-top: .85rem; border-top: 1px solid rgba(255, 255, 255, .12); font-size: .84rem; }
.enquiry-shell__form { min-width: 0; }
.enquiry-shell .form-note { margin: 0 0 .85rem; }
.enquiry-shell .field__hint { color: rgba(255, 255, 255, .64); }

/* Downloads. The whole row is the anchor and clears 44px without being told. */
.dl-list     { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.dl-list--library { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
.dl-list--library > li { min-width: 0; }
.dl-list--library .dl__a { height: 100%; align-items: flex-start; padding: .85rem 1rem; }
.dl-list--library .dl__t { overflow-wrap: anywhere; }
.dl-list--library .muted { line-height: 1.4; }
/* The contact shadow does the work a hairline border cannot: it separates a stack
   of these rows from the page without drawing another line beside the one they
   already have. box-shadow joins the transition list because the hover state
   deepens it — border-color and background were changing with no transition at
   all here, so they snapped; that is fixed in the same declaration. Nothing needs
   adding to the reduced-motion block: it zeroes every duration in the sheet, and
   an instant colour change is the correct behaviour there. */
.dl__a       { display: flex; align-items: center; gap: .8rem; min-height: 56px; padding: .7rem 1rem; border: 1px solid var(--line); border-radius: var(--rad); background: var(--bg); color: var(--ink); box-shadow: var(--shadow-sm); transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.dl__a:hover { border-color: var(--blue); background: var(--blue-wash); box-shadow: var(--shadow); }
.dl__a .ico  { color: var(--red); }
.dl__t       { font-weight: 600; }
.dl__b       { flex: 1; min-width: 0; display: grid; gap: .15rem; }
/* Two row shapes exist. The downloads library wraps its text in .dl__b, which is
   `flex: 1` and therefore already fills the row. The block and the product page emit
   a flat row — icon, name, meta as three siblings — where nothing claims the free
   space, so on the home page's 1180px row the name and the file size huddled inside
   the first 200px with 975px of empty white beside them, and the two read as a pair
   of labels rather than a name and its size: both 16px, differing only in weight and
   tone. Pushing the meta to the far edge lets position carry the hierarchy the way
   every file listing does, and leaves the name alone on the left where the eye
   starts. No size step on top of it — in a file row, name and size are conventionally
   the same size, and --dim is already 5:1 against white.
   The child combinator is doing real work: it takes the flat shape's trailing .muted
   and deliberately misses the two inside .dl__b, which are stacked lines of a text
   block and must not be pushed anywhere. nowrap keeps "Datasheet · 1.6 MB" on one
   line so a 56px row stays 56px; the padding guarantees a gap however long the name
   grows (the longest published title is 34 characters, about 275px of the ~1100px
   available). The push is undone below 640 — see the narrow block. */
.dl__a > .muted { margin-left: auto; padding-left: 1rem; white-space: nowrap; }
/* The block's optional note sits directly after the list, and both had zero margin
   between them — .dl-list is `margin: 0` and a paragraph carries only a bottom one —
   so a remark about the list began on the exact pixel the last row's border ended and
   read as a seventh row that had lost its box. var(--gap) is not a chosen number: the
   pager already claims exactly that much after the same list on the downloads page,
   so this is the sheet's own answer to "what follows a .dl-list" applied to the one
   place that was missing it. Nothing sets a bottom margin here — p:last-child clears
   it, and the band's padding is the space below. */
.dl-list + p { margin-top: var(--gap); }

/* Gallery photographs. A photo is not a card: it wants a square crop, a tighter
   gutter than .grid gives, and the image itself to move on hover rather than the
   tile lifting. Three rules plus the gutter, exactly as gallery_grid.php promises. */
.shots       { gap: .6rem; }
.shot__a     { display: block; aspect-ratio: 1; overflow: hidden; border-radius: var(--rad); background: var(--bg-2); }
.shot__a img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur) var(--ease); }
.shot:hover img { transform: scale(1.06); }

/* Native modal gallery. Real image links remain the no-JS fallback. */
html.lightbox-open { overflow: hidden; }
.lightbox {
  position: fixed;
  inset: 0;
  width: 100%;
  max-width: none;
  height: 100vh;
  max-height: none;
  margin: 0;
  padding: clamp(.75rem, 3vw, 2rem);
  border: 0;
  background: rgba(2, 7, 15, .94);
  color: #fff;
  overflow: auto;
}
.lightbox[open] { display: grid; place-items: center; }
.lightbox::backdrop { background: rgba(2, 7, 15, .82); backdrop-filter: blur(8px); }
.lightbox__panel {
  position: relative;
  width: min(94vw, 1380px);
  max-width: 100%;
  padding: clamp(.55rem, 1.5vw, 1rem);
  border: 1px solid rgba(255, 255, 255, .15);
  border-radius: var(--rad-lg);
  background: linear-gradient(145deg, rgba(21, 35, 58, .94), rgba(5, 12, 24, .96));
  box-shadow: 0 35px 100px rgba(0, 0, 0, .62);
}
.lightbox__figure { display: grid; justify-items: center; gap: .65rem; margin: 0; }
.lightbox__image {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: calc(100vh - 8rem);
  border-radius: var(--rad);
  object-fit: contain;
  box-shadow: 0 16px 50px rgba(0, 0, 0, .4);
}
.lightbox__caption { max-width: 72ch; color: rgba(255, 255, 255, .78); text-align: center; }
.lightbox__close {
  position: absolute;
  z-index: 2;
  top: .85rem;
  right: .85rem;
  display: grid;
  place-items: center;
  width: 46px;
  min-width: 46px;
  height: 46px;
  min-height: 46px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 50%;
  background: rgba(8, 17, 31, .9);
  color: #fff;
  font: inherit;
  font-size: 1.7rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.lightbox__close:hover { background: var(--red); border-color: var(--red); }

/* Scene typography and media are defined with the pinned stage above. */
/* Contact details beside the Maps frame. No wrapper div and no .embed class: the
   global reset already makes an iframe a block, so a ratio is all it needs. */
.band--map li      { display: flex; align-items: flex-start; gap: .7rem; }
.band--map li .ico { color: var(--blue); }
/* flex-start above is right for the address: a pin labelling three lines belongs
   beside the first of them, not floating at the middle of the block. It is wrong
   for the two rows whose label is a link, because those carry min-height: 44px for
   the thumb and centre their own text inside it — so the text sat 22px down while
   the 28px glyph sat 14px down, and the phone and mail icons rode 7.2px above the
   things they label while the pin above them lined up. Same row type, two
   alignments. The row is exactly 44px tall when its only content is that link, so
   centring the glyph in it puts both at 22px. Scoped with :has() because the three
   rows genuinely differ and the address must keep flex-start; the head-group
   rhythm already depends on :has(), and this one only moves an icon 7px. */
.band--map li:has(> a) > .ico { align-self: center; }
.band--map a       { display: inline-flex; align-items: center; min-height: 44px; }
.band--map iframe  { width: 100%; height: auto; aspect-ratio: 4 / 3; border-radius: var(--rad-lg); }

/* ==========================================================================
   Breakpoints — 1100, 900, 640. There are no others.
   1100: the 4-up grids and the split hero stop fitting, and the nav bar collapses
         to a drawer — see the note above that block for why it is not 900.
   900:  tablet — multi-column becomes 2-up, every two-column block folds.
   640:  phone — everything is one column.
   Kept together at the bottom on purpose: when every component carries its own
   media query, changing the tablet layout means hunting eight hundred lines.
   ========================================================================== */

/* The drawer is open until JavaScript closes it. Below 1100px .nav__drawer is
   display:block — a plain vertical list, visible, no toggle needed. Only the
   .nav[data-js="1"] pair hides it, and data-js is set by initNav() at the moment
   it wires the toggle up, so the collapse and the thing that un-collapses it
   arrive together or not at all. Hidden-in-CSS-revealed-by-JS is how sites end up
   with no navigation at all on the one browser that didn't run the script.

   The button is scoped to the same attribute, and for the same reason read the
   other way round: a control that cannot work should not be on the screen. Without
   the script the menu is already open, so a visible toggle would be both useless
   and a lie about what tapping it does.

   Collapsed at 1100 and not at 900: the bar is a single nowrap flex row of logo,
   nine links and the Call button, and the links alone measure 776px. Between about
   900 and 1075 that row does not fit, and the item that gave way was the button —
   last in the row and the only one with nothing left to shrink — which was pushed
   past the right edge and made the whole page scroll sideways to reach it. Folding
   the bar at the same width the 4-up grids fold at costs a small-laptop visitor one
   tap and removes a 175px band of broken layout. The links keep flex-wrap: wrap as
   a second line of defence, because the menu is built from the pages table: a tenth
   nav page would otherwise walk straight back into the same overflow. */
@media (max-width: 1100px) {
  .nav__links { display: none; }
  .nav__drawer { display: block; }
  .nav__drawer .nav__links { display: flex; }
  .nav[data-js="1"] .nav__toggle { display: inline-flex; }
  .nav[data-js="1"] .nav__drawer { display: none; }
  .nav[data-js="1"] .nav__drawer[data-open="1"] { display: block; }
  .nav__drawer .nav__links a { min-height: 48px; padding: 0 .5rem; }

  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--split:not(.product-hero) { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .specs { grid-template-columns: 1fr; gap: .2rem; }
  .specs dd { margin-bottom: .6rem; }
  .product-hero.grid--split, .product-details__grid, .enquiry-shell { grid-template-columns: 1fr; }
  .product-summary, .enquiry-shell__intro { position: static; }
  .product-gallery__main { max-height: 520px; }

  /* Every two-column block folds here, the scene's step included. split--left's
     reorder is cancelled rather than left standing: with one column, `order` would
     put the image above the copy on half the splits and below it on the other half. */
  .hero, .split, .strip { grid-template-columns: 1fr; }
  .about-hero__grid, .about-story__grid, .about-workflow__intro,
  .about-founder__grid, .about-serve__grid { grid-template-columns: 1fr; }
  .about-hero__grid { min-height: 0; }
  .about-hero__copy .h1 { max-width: 18ch; }
  .about-hero__visual { width: min(100%, 38rem); min-height: 24rem; margin-inline: auto; }
  .about-story__lead { position: static; }
  .about-story__lead h2 { max-width: 20ch; }
  .about-capability-grid, .about-flow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .about-flow li:nth-child(2) { border-right: 0; }
  .about-flow li:nth-child(-n + 2) { border-bottom: 1px solid rgba(255, 255, 255, .15); }
  .about-founder__portrait { width: min(100%, 28rem); margin-inline: auto; }
  .about-founder__copy { max-width: none; }
  .about-workflow__intro .lede { margin-left: 0; }
  .split--left .split__copy, .split--left .split__media { order: 0; }
  .hero__copy { max-width: none; }
  .hero__media { min-height: 250px; max-width: 620px; margin-inline: auto; }
  /* One column, so the strip image's right-edge alignment has nothing to align to. */
  .strip__img { margin-left: 0; }
  .pin { height: auto; }
  .pin__scene { position: static; height: auto; min-height: 0; overflow: visible; }
  .pin__steps { min-height: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }
  .pin__step, .pin__step.is-active {
    position: relative;
    inset: auto;
    display: flex;
    flex-direction: column;
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, .12);
    border-radius: var(--rad-lg);
    background: rgba(9, 17, 32, .78);
  }
  .pin__media { width: 100%; height: auto; aspect-ratio: 4 / 3; border-radius: 0; }
  .pin__copy { max-width: none; padding: 1rem; }
}

/* A lone button on a phone should be full width and easy to hit; two in a .btn-row
   should share the row rather than stack. Written in this order a button is
   full-width by default and only opts out inside a row.
   The tilt dies here too, not just under reduced motion: pointer-driven 3D has no
   input on a touch screen, and `perspective` on a narrow viewport makes a card that
   overflows its own container. Cheaper than teaching the JS about viewport width. */
@media (max-width: 640px) {
  .grid--2, .grid--3, .grid--4, .grid--auto { grid-template-columns: 1fr; }
  .about-capability-grid, .about-reasons, .about-brand-grid,
  .about-direction__grid, .about-audience { grid-template-columns: 1fr; }
  .about-hero__visual { min-height: 20rem; padding: 1.1rem; }
  .about-orbit { width: min(17rem, 68vw); }
  .about-orbit__node { min-width: 4.7rem; min-height: 2rem; padding: .25rem .45rem; font-size: .65rem; }
  .about-orbit__node--2 { right: -2rem; }
  .about-orbit__node--4 { left: -2rem; }
  .about-hero__caption { display: none; }
  .about-capability { min-height: 0; }
  .about-capability__icon { margin-bottom: 2rem; }
  .about-flow { grid-template-columns: 1fr; }
  .about-flow li { min-height: 8rem; border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .15); }
  .about-flow li:nth-child(-n + 2) { border-bottom: 1px solid rgba(255, 255, 255, .15); }
  .about-flow li:last-child { border-bottom: 0; }
  .about-reason { grid-template-columns: 2.2rem 1fr; }
  .about-brand { min-height: 17rem; }
  .about-founder__portrait { width: min(100%, 23rem); }
  .about-facts { grid-template-columns: 1fr; }
  .about-facts div, .about-facts div:nth-child(3n),
  .about-facts div:nth-last-child(-n + 3) { min-height: 6.5rem; border-right: 0; border-bottom: 1px solid var(--line); }
  .about-facts div:last-child { border-bottom: 0; }
  .about-closing__card { border-radius: var(--rad-lg); }
  .form--wide { grid-template-columns: 1fr; }
  .page-intro__top { display: block; }
  .page-intro__top h1, .page-intro__top h2 { margin-bottom: .2rem; }
  .category-rail { margin-top: .3rem; }
  .dl-list--library { grid-template-columns: 1fr; }
  .foot { padding-top: 1.1rem; }
  .foot__grid { grid-template-columns: 1fr; gap: .55rem; }
  .foot__grid > div { padding: .35rem 0; }
  .foot__grid > div + div { padding-left: 0; padding-top: .75rem; border-left: 0; border-top: 1px solid rgba(255, 255, 255, .1); }
  .foot__nav ul { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot li a { min-height: 44px; }
  .foot__bar { justify-content: flex-start; margin-top: .5rem; }
  .lightbox { padding: .5rem; }
  .lightbox__panel { width: 100%; padding: .4rem; }
  .lightbox__image { max-height: calc(100vh - 5rem); }
  .lightbox__close { top: .65rem; right: .65rem; }
  .btn { width: 100%; }
  .btn-row .btn { width: auto; flex: 1 1 auto; }
  /* Two more opt-outs, for the same reason as .btn-row: the nav bar is a row, not a
     column. A full-width Call button in it takes 339px of a 560px screen and squeezes
     the ☰ to 33px — narrower than the 44px the button declares for itself, and it is
     the one control a phone visitor actually needs. */
  .nav__in .btn { width: auto; }
  .nav__toggle { flex: 0 0 auto; }
  [data-tilt], [data-tilt] [data-tz] { transform: none; perspective: none; }
  .hero__media { min-height: 220px; padding: .7rem; }
  .hero__img { max-height: 300px; }
  .product-gallery__main { aspect-ratio: 4 / 3; padding: .55rem; }
  .product-gallery__thumbs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .product-summary, .product-panel, .enquiry-shell { padding: 1rem; }
  .enquiry-shell { gap: 1rem; }
  .stats--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stats--compact .stat { border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .1); }
  .stats--compact .stat:nth-last-child(-n + 2) { border-bottom: 0; }
  /* A pinned scene needs viewport height to be worth pinning. On a phone it
     becomes an ordinary section and the JS keeps writing --pin-p to nothing. */
  .pin { height: auto; }
  .pin__scene { position: static; height: auto; min-height: 0; padding: 0; }

  .pin__steps { grid-template-columns: 1fr; }
  .pin__step, .pin__step.is-active { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); }
  .pin__media { aspect-ratio: 4 / 3; }
  .pin__copy { align-self: center; }

  /* Photographs stay two-up when everything else has gone to one — a 1fr column of
     square crops is a very long page for very little. This rule has the same
     specificity as the .grid--4 line at the top of this block and wins only because
     it is written after it. */
  .shots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .logo { width: 132px; }
  /* The static scene rules are already established at 900px; phone only changes
     the grid to one column above. */

  /* A download row is about 311px wide here. Pinning the file size to the right of a
     name that has wrapped to two lines reads as a mistake rather than a column, so
     the meta goes back to sitting after the name and wrapping under it. */
  .dl__a > .muted { margin-left: 0; padding-left: 0; }
}

/* --- About --------------------------------------------------------------- */

.about-hero { overflow: hidden; }
.about-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(360px, .8fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
  min-height: min(42rem, calc(100vh - 5rem));
}
.about-hero__copy { max-width: 64ch; }
.about-hero__copy .h1 { max-width: 16ch; }
.about-hero__copy .lede { max-width: 62ch; }
.about-hero__promise {
  margin: 1.35rem 0 0;
  padding-left: 1rem;
  border-left: 3px solid var(--red);
  font-weight: 700;
  color: #fff;
}
.about-hero__copy .btn-row { margin-top: 1.6rem; }
.about-hero__visual {
  position: relative;
  min-height: 27rem;
  display: grid;
  place-items: center;
  padding: 2rem;
  border: 1px solid rgba(255, 255, 255, .15);
  border-radius: 2rem;
  background:
    radial-gradient(circle at 50% 45%, rgba(55, 93, 164, .28), transparent 45%),
    rgba(9, 17, 32, .68);
  box-shadow: 0 32px 78px rgba(3, 10, 24, .46);
  isolation: isolate;
}
.about-hero__visual::before {
  content: "";
  position: absolute;
  inset: 10%;
  z-index: -1;
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 50%;
}
.about-orbit { position: relative; width: min(21rem, 72vw); aspect-ratio: 1; border: 1px solid rgba(255, 255, 255, .18); border-radius: 50%; }
.about-orbit::before, .about-orbit::after { content: ""; position: absolute; inset: 18%; border: 1px solid rgba(255, 255, 255, .1); border-radius: 50%; }
.about-orbit::after { inset: 36%; background: rgba(55, 93, 164, .15); }
.about-orbit__core { position: absolute; inset: 50% auto auto 50%; z-index: 2; translate: -50% -50%; display: grid; place-items: center; width: 5.5rem; height: 5.5rem; border-radius: 50%; background: #fff; color: var(--blue); box-shadow: 0 0 0 .65rem rgba(255, 255, 255, .08), 0 18px 40px rgba(0, 0, 0, .32); }
.about-orbit__core .ico { width: 2.1rem; height: 2.1rem; }
.about-orbit__node { position: absolute; display: grid; place-items: center; min-width: 5.8rem; min-height: 2.4rem; padding: .35rem .65rem; border: 1px solid rgba(255, 255, 255, .17); border-radius: 999px; background: rgba(9, 17, 32, .9); color: #fff; font-size: .78rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; box-shadow: var(--shadow); }
.about-orbit__node--1 { top: -1rem; left: 50%; translate: -50% 0; }
.about-orbit__node--2 { top: 50%; right: -2.8rem; translate: 0 -50%; }
.about-orbit__node--3 { bottom: -1rem; left: 50%; translate: -50% 0; }
.about-orbit__node--4 { top: 50%; left: -2.8rem; translate: 0 -50%; }
.about-hero__caption { position: absolute; inset: auto 1.4rem 1.15rem; display: flex; justify-content: space-between; gap: .8rem; margin: 0; font-size: .78rem; }
.about-hero__caption span { color: rgba(255, 255, 255, .62); }

.about-story__grid { display: grid; grid-template-columns: minmax(15rem, .72fr) minmax(0, 1.28fr); gap: clamp(2rem, 7vw, 6rem); align-items: start; }
.about-story__lead { position: sticky; top: 6rem; }
.about-story__lead h2 { max-width: 13ch; }
.about-story__copy { max-width: 72ch; font-size: var(--fs-lede); }
.about-story__copy p:first-child { font-weight: 600; color: var(--ink-2); }

.about-capability-grid { list-style: none; margin: 2rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gap); }
.about-capability { position: relative; min-height: 19rem; padding: 1.25rem; overflow: hidden; border: 1px solid rgba(55, 93, 164, .16); border-radius: var(--rad-lg); background: linear-gradient(155deg, #fff 5%, var(--blue-wash)); box-shadow: 0 16px 34px rgba(25, 47, 82, .09); }
.about-capability__icon { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; margin-bottom: 3.5rem; border-radius: var(--rad); background: var(--blue); color: #fff; }
.about-capability__step { position: absolute; top: .65rem; right: .9rem; color: rgba(55, 93, 164, .15); font-size: 4.5rem; font-weight: 700; line-height: 1; letter-spacing: -.08em; }
.about-capability h3 { position: relative; }
.about-capability p { position: relative; margin: 0; color: var(--dim); }

.about-workflow__intro { display: grid; grid-template-columns: minmax(0, .9fr) minmax(20rem, .75fr); gap: clamp(1.5rem, 5vw, 4.5rem); align-items: end; }
.about-workflow__intro .lede { margin-left: auto; }
.about-flow { list-style: none; margin: 2.4rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); border-block: 1px solid rgba(255, 255, 255, .15); }
.about-flow li { position: relative; min-height: 10rem; padding: 1.4rem; display: flex; flex-direction: column; justify-content: end; border-right: 1px solid rgba(255, 255, 255, .15); }
.about-flow li:last-child { border-right: 0; }
.about-flow span { position: absolute; inset: .9rem .9rem auto auto; color: rgba(255, 255, 255, .28); font-weight: 700; }
.about-flow b { font-size: clamp(1.2rem, 2vw, 1.55rem); }
.about-flow small { margin-top: .25rem; color: rgba(255, 255, 255, .64); }

.about-reasons { list-style: none; margin: 2rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--gap); border-top: 1px solid var(--line); }
.about-reason { display: grid; grid-template-columns: 3rem 1fr; gap: .9rem; padding: 1.25rem 0; border-bottom: 1px solid var(--line); }
.about-reason > span { color: var(--red-deep); font-size: .78rem; font-weight: 700; letter-spacing: .1em; }
.about-reason h3 { margin-bottom: .3rem; }
.about-reason p { margin: 0; max-width: 52ch; color: var(--dim); }

.about-founder__grid { display: grid; grid-template-columns: minmax(17rem, .72fr) minmax(0, 1.28fr); gap: clamp(2rem, 7vw, 6rem); align-items: center; }
.about-founder__portrait { margin: 0; }
.about-founder__photo, .about-founder__placeholder { position: relative; width: 100%; aspect-ratio: 4 / 5; overflow: hidden; border: 1px solid rgba(255, 255, 255, .16); border-radius: 9rem 9rem var(--rad-lg) var(--rad-lg); box-shadow: 0 28px 66px rgba(3, 10, 24, .52); }
.about-founder__photo img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }
.about-founder__placeholder { display: grid; place-items: center; align-content: center; background: radial-gradient(circle at 50% 30%, rgba(91, 137, 220, .45), transparent 38%), linear-gradient(145deg, rgba(255, 255, 255, .12), rgba(9, 17, 32, .8)); color: rgba(255, 255, 255, .68); font-size: .75rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
.about-founder__placeholder::before { content: ""; position: absolute; width: 42%; aspect-ratio: 1; top: 16%; border-radius: 50%; background: rgba(255, 255, 255, .12); box-shadow: 0 10rem 0 3.3rem rgba(255, 255, 255, .09); }
.about-founder__monogram { position: relative; z-index: 1; margin-bottom: .5rem; color: #fff; font-size: clamp(3rem, 7vw, 5.5rem); font-weight: 700; line-height: 1; letter-spacing: -.07em; }
.about-founder__placeholder > span:last-child { position: relative; z-index: 1; }
.about-founder__portrait figcaption { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: .85rem .25rem 0; }
.about-founder__portrait figcaption span { color: rgba(255, 255, 255, .62); }
.about-founder__copy { max-width: 68ch; }
.about-founder__lead { font-size: var(--fs-lede); font-weight: 600; color: #fff; }
.about-founder__signature { display: flex; flex-direction: column; margin-top: 1.6rem; padding-top: 1rem; border-top: 1px solid rgba(255, 255, 255, .15); }
.about-founder__signature span { color: rgba(255, 255, 255, .62); font-size: .85rem; }

.about-brand-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); margin-top: 2rem; }
.about-brand { position: relative; min-height: 20rem; padding: clamp(1.5rem, 4vw, 3rem); overflow: hidden; border-radius: var(--rad-lg); color: #fff; box-shadow: var(--shadow-lg); }
.about-brand::after { content: ""; position: absolute; width: 18rem; height: 18rem; right: -8rem; bottom: -9rem; border: 1px solid rgba(255, 255, 255, .2); border-radius: 50%; box-shadow: 0 0 0 2.5rem rgba(255, 255, 255, .04); }
.about-brand--ak { background: linear-gradient(145deg, var(--blue-deep), var(--blue)); }
.about-brand--stel { background: linear-gradient(145deg, var(--ink), #243956); }
.about-brand__name { margin: 0 0 3rem; color: rgba(255, 255, 255, .18); font-size: clamp(3.6rem, 8vw, 6.5rem); font-weight: 700; line-height: .9; letter-spacing: -.08em; }
.about-brand h3, .about-brand p { position: relative; z-index: 1; max-width: 33rem; }
.about-brand p:last-child { color: rgba(255, 255, 255, .76); }

.about-serve__grid { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: clamp(2rem, 6vw, 5rem); align-items: start; }
.about-audience { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .55rem; }
.about-audience li { min-height: 3.5rem; display: flex; align-items: center; gap: .65rem; padding: .7rem .8rem; border: 1px solid var(--line); border-radius: var(--rad); background: #fff; box-shadow: var(--shadow-sm); }
.about-audience .ico { color: var(--blue); }

.about-direction__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }
.about-direction .glass { min-height: 20rem; display: flex; flex-direction: column; justify-content: end; }
.about-direction .glass h2 { max-width: 21ch; }
.about-direction .glass > p:last-child { color: rgba(255, 255, 255, .7); }

.about-facts { margin: 2rem 0 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border: 1px solid var(--line); border-radius: var(--rad-lg); overflow: hidden; background: #fff; box-shadow: var(--shadow); }
.about-facts div { min-height: 8.5rem; padding: 1.15rem; display: flex; flex-direction: column; justify-content: space-between; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.about-facts div:nth-child(3n) { border-right: 0; }
.about-facts div:nth-last-child(-n + 3) { border-bottom: 0; }
.about-facts dt { color: var(--dim); font-size: .75rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
.about-facts dd { margin: 1rem 0 0; color: var(--blue-deep); font-size: clamp(1.15rem, 2.1vw, 1.65rem); font-weight: 700; line-height: 1.2; }

.about-closing__card { max-width: 58rem; margin: 0 auto; padding: clamp(1.5rem, 5vw, 3.5rem); border: 1px solid rgba(255, 255, 255, .16); border-radius: 2rem; background: radial-gradient(circle at 80% 10%, rgba(55, 93, 164, .35), transparent 32%), rgba(9, 17, 32, .76); text-align: center; box-shadow: 0 28px 68px rgba(3, 10, 24, .5); }
.about-closing__card h2 { max-width: 22ch; margin-inline: auto; }
.about-closing__card > p:not(.eyebrow):not(.btn-row):not(.about-closing__mail) { max-width: 56ch; margin-inline: auto; color: rgba(255, 255, 255, .74); }
.about-closing__card .btn-row { justify-content: center; margin-top: 1.5rem; }
.about-closing__mail { margin-top: .9rem; }
.about-closing__mail a { color: #fff; text-decoration: underline; text-underline-offset: .2em; }

/* ==========================================================================
   Reduced motion. Every animated thing on this site is listed here.
   If you add motion and do not add it to this block, you have added a bug.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .001ms;
    animation-iteration-count: 1;
    transition-duration: .001ms;
  }

  /* Revealed content must be visible, not merely un-animated. Zeroing the duration
     makes the transition instant, but the element's INITIAL state is still
     opacity: 0 — and Task 25 never fires the observer under reduced motion, so
     without this line the content stays invisible forever. That is the single most
     common way a reduced-motion implementation blanks a page. */
  .reveal { opacity: 1; transform: none; }

  /* No pointer-driven 3D, no lift. */
  [data-tilt], [data-tilt] [data-tz] { transform: none; perspective: none; }
  .card--lift:hover, .card--lift:focus-within { transform: none; box-shadow: var(--shadow); }
  .product-gallery__main img,
  .product-gallery__main:hover img,
  .product-gallery[data-zoomed="1"] .product-gallery__main img { transform: none; }
  .btn:hover { transform: none; }

  /* The canvas is stopped by JS; the element goes away entirely here. Removing the
     black ground would leave .band--deep's white text on white, so the deep bands
     take a solid fallback ground in the same breath. */
  #fibre { display: none; }
  .band--deep { background: var(--ink); }

  /* The progress bar becomes nothing rather than a growing one. */
  .progress { display: none; }

  /* Scroll-driven position is motion whether it is eased or not. --para is
     never written (the loop returns early), but the transform must go too or
     every parallax card keeps whatever offset it was last given. The pin
     flattens for the same reason it flattens on a phone. */
  [data-para] { transform: none; }
  .pin { height: auto; }
  .pin__scene { position: static; height: auto; min-height: 0; overflow: visible; }
  .pin__steps { min-height: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); }
  .pin__step, .pin__step.is-active {
    position: relative;
    inset: auto;
    display: flex;
    flex-direction: column;
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, .12);
    border-radius: var(--rad-lg);
    background: rgba(9, 17, 32, .78);
  }
  .pin__media { width: 100%; height: auto; aspect-ratio: 4 / 3; border-radius: 0; }
  .pin__copy { max-width: none; padding: 1rem; }

  /* This block cancels transition DURATION, not transforms, so every hover
     transform has to be named. Task 27 adds exactly two and pays both here.
     The FAQ arrow is the deliberate exception: its rotation is the only visual
     difference between an open and a closed answer, so the transform stays and
     only its transition dies. */
  .card--lift:hover .card__foot .ico { transform: none; }
  .shot:hover img { transform: none; }
}

/* ==========================================================================
   Print. Datasheet pages and product pages get printed by buyers.
   ========================================================================== */

@media print {
  #fibre, .progress, .nav, .foot, .skip, .btn, .pager, .spin { display: none; }
  .band { padding: 0 0 1.2rem; background: #fff; color: #000; }
  .band--deep, .band--ink { background: #fff; color: #000; }

  /* Every descendant colour rule the two dark tones carry is (0,2,0), and each of
     them outranks the (0,1,0) `color: #000` on the line above. Untreated, a
     printed deep band came out as a heading and nothing else: its lede, its muted
     text, its counters, its stat labels and its scene step numbers were all white
     on white paper. Restated here at matching specificity, later in the source. */
  .band--deep .lede, .band--deep .muted, .band--deep .stat .count,
  .band--deep .stat__label, .band--deep .pin__n,
  .band--ink .lede, .band--ink .muted, .band--ink a { color: #000; }

  /* The cards on those tones put ink back at the card boundary, one step more
     specific again: (0,2,0) for the card itself and (0,3,0) for the muted text and
     the ink-tone links inside it. All three outrank the reset above, so on paper
     they would print --ink and --dim instead of black — the grey body copy being
     the one that shows. Restated here as a single list. */
  .band--deep .card,        .band--ink .card,
  .band--deep .card .muted, .band--ink .card .muted,
  .band--ink  .card a { color: #000; }

  /* The eyebrow is the same trap running the other way. Its dark-tone override
     is (0,2,0) and sets the bright red, which on paper is a red on white that
     measures 4.118:1. Every band prints white, so every eyebrow prints as the
     light-background red. The (0,3,0) card rule already sets this value, so it
     needs no line of its own. */
  .band--deep .eyebrow, .band--ink .eyebrow { color: var(--red-deep); }

  /* A run of two light bands separates itself with a tint and a hairline, at
     (0,2,0) against `.band`'s (0,1,0) two lines up. Paper has page breaks and does
     not need either. */
  .band--light + .band--light { background: #fff; border-top: 0; }
  .glass { border: 1px solid #999; box-shadow: none; background: #fff; backdrop-filter: none; }
  /* Both of these are needed. The deep-band rules above are MORE SPECIFIC than the
     generic ones beside them — `.band--deep .glass` (0,2,0) outranks `.glass`
     (0,1,0) — so without an explicit override a printed page carries dark panes
     with black text on them and the scrim paints a dark ellipse onto white paper.
     Neither shows up on screen or in the test suite. Any future .band--deep-scoped
     rule that paints a background needs its print counterpart in the same commit. */
  .band--deep .glass { background: #fff; border-color: #999; box-shadow: none; }
  /* The hero chips are the same trap one size down: (0,2,0) again, so the inverted
     dark pane would print with black text sitting on it. */
  .band--deep .chip { background: #fff; border-color: #999; }
  .band--deep .band__in::before { display: none; }
  .card { break-inside: avoid; box-shadow: none; }
  /* Same specificity trap as the glass panes: the deep-band card shadow is (0,2,0)
     and would survive the (0,1,0) reset above, printing a heavy grey smear under
     every card. The FAQ rows and download rows only need the (0,1,0) reset, but
     they need it written after their own declarations. */
  .band--deep .card { box-shadow: none; }
  .qa, .dl__a { box-shadow: none; }
  /* The scene unpins on paper (further up this block), so its three panels print as
     a stacked list. Their photographs are sized from the viewport — 46vh of a page
     box is around 516px — which is a reasonable plate on A4; the drop shadow under
     each one is only ink. */
  .pin__media img { box-shadow: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555; }
}
