/* ============================================================
   ezGeyser v2 — "dark cinematic"
   ------------------------------------------------------------
   Same brand as v1: Lato + Hanken Grotesk, same flame palette.
   Everything else is new. v1 followed the Thermal v3 doctrine —
   light-first, solid-shaded surfaces, no outlines, no glows.
   This one is deliberately the opposite discipline:

     · one canvas, near-black and warm, never light
     · light is a SUBJECT, not decoration — it comes from the
       product (a geyser) and appears only where the product is
     · photographs float on the page with no card chrome at all
     · type carries the layout: very large display, very small
       wide-tracked labels, almost nothing in between
     · surfaces are separated by hairlines and space, not boxes

   The rule that keeps this from becoming the "weird background
   light" problem v1 had: a glow must be attached to an object
   that would actually emit or catch light. No free-floating
   blobs behind text or cards.
   ============================================================ */


/* ------------------------------------------------------------
   TOKENS
   ------------------------------------------------------------ */
:root {
  /* Brand — unchanged from v1 */
  --amber: #f8a22a;
  --flame: #e8742a;
  --ember: #d1442c;
  --water: #5fd4ee;
  --teal: #00b090; /* KytherTek's own colour, used only in their block */

  /* The canvas. Warm blacks, not neutral ones — a neutral black
     next to the flame palette reads blue and kills the warmth. */
  --ink-0: #080503;
  --ink-2: #130d09;

  /* Ink on that canvas. All three pass AA on --ink-0; --dim is
     the floor and is never used below 14px. */
  --text: #f4e7dd;
  --muted: #c9b7ab;
  --dim: #a08e81;

  --hair: rgba(255, 255, 255, 0.09);
  --hair-strong: rgba(255, 255, 255, 0.16);
  /* Buttons carry white text, so they use this deeper ramp — white passes
     AA (4.6:1) on its lightest stop #C94D1C, but fails (1.9:1) on the
     decorative ramp's #F8A22A. --grad-flame stays for decorative use. */
  --grad-action: linear-gradient(180deg, #c94d1c 0%, #b23524 55%, #982c1a 100%);
  --grad-line: linear-gradient(90deg, transparent, rgba(232, 116, 42, 0.55), transparent);

  --font-display: "Lato", system-ui, sans-serif;
  --font-body: "Hanken Grotesk", system-ui, sans-serif;

  /* Vertical rhythm. Cinematic pacing means the gaps do as much
     work as the content — these are large on purpose. */
  --bay: clamp(88px, 13vh, 176px);

  --gut: clamp(20px, 5vw, 72px);
  --max: 1360px;
  --r: 14px;
  /* One radius for every raised panel - products, OEM, nothing else. */
  --r-panel: clamp(18px, 2.4vw, 28px);
  /* One padding for every boxed cell. Capabilities, benefits, variants
     and technology all render the same component; three different
     paddings stopped them reading as one. */
  --cell-pad: clamp(26px, 3vw, 42px);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --slow: 0.72s;
  --mid: 0.42s;
}

/* ------------------------------------------------------------
   RESET / BASE
   ------------------------------------------------------------ */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html {
  /* NOT smooth by default. Chrome applies scroll-behavior to its own
     scroll restoration and to fragment navigation on load, so a refresh
     part-way down the page (or with a #hash in the URL) animates its way
     back to position - the whole page visibly slides. Smooth is switched
     on by JS after the first frame, so user-initiated anchor clicks still
     glide but the load never does. */
  scroll-behavior: auto;
  -webkit-text-size-adjust: 100%;
  color-scheme: dark;
  -webkit-tap-highlight-color: transparent;
}
html.smooth-scroll {
  scroll-behavior: smooth;
}
body {
  background: var(--ink-0);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img,
svg,
iframe {
  display: block;
  max-width: 100%;
}
img {
  height: auto;
}
a {
  color: inherit;
  text-decoration: none;
}
button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
table {
  border-collapse: collapse;
}
::selection {
  background: var(--ember);
  color: #fff;
}
:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 3px;
  border-radius: 3px;
}

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gut);
}
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.skip {
  position: fixed;
  top: -60px;
  left: 16px;
  z-index: 200;
  background: var(--amber);
  color: #1a0d02;
  font-weight: 700;
  padding: 10px 16px;
  border-radius: 8px;
  transition: top var(--mid) var(--ease);
}
.skip:focus {
  top: 16px;
}

/* Filmic grain. Cheap, fixed, and the single biggest thing that
   stops a flat dark page from looking like a dark rectangle. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ------------------------------------------------------------
   TYPE
   ------------------------------------------------------------ */
h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.035em;
  line-height: 0.98;
  text-wrap: balance;
}
/* No orphan words dangling under paragraphs where the engine supports it. */
p,
li {
  text-wrap: pretty;
}

.sec-title {
  /* Must stay below the hero display at EVERY width, not just at the
     caps. At clamp(31px,5vw,68px) the 5vw slope outran the hero's 4.6vw,
     so chapter headings were larger than the page headline from roughly
     760px to 1478px - a hierarchy inversion across most laptops. */
  font-size: clamp(28px, 4.3vw, 62px);
  max-width: 16ch;
}
.sec-title em {
  font-style: normal;
  color: var(--flame);
}
.sec-lead {
  color: var(--muted);
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.68;
  max-width: 54ch;
  margin-top: clamp(18px, 2vw, 26px);
}
.note {
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.6;
}
/* Two leads back to back (the KytherTek chapter) stack with a small gap. */
.sec-lead + .sec-lead {
  margin-top: 14px;
}
/* Anchor jumps must land clear of the fixed header. */
[id] {
  scroll-margin-top: 96px;
}
.top-sentinel {
  position: absolute;
  inset: 24px auto auto 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}


/* ------------------------------------------------------------
   BUTTONS
   ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 14.5px;
  letter-spacing: 0.01em;
  padding: 14px 24px;
  /* WCAG 2.5.8 floor. The header button drops its label below 900px and
     was collapsing to 40px tall; min-height holds every button at the
     touch minimum regardless of what its content shrinks to. */
  min-height: 44px;
  box-sizing: border-box;
  border-radius: 999px;
  transition:
    transform var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease),
    background var(--mid) var(--ease),
    color var(--mid) var(--ease);
  will-change: transform;
}
.btn svg {
  width: 18px;
  height: 18px;
  flex: none;
}
.btn-flame {
  background: var(--grad-action);
  color: #fff;
  box-shadow: 0 10px 30px -12px rgba(209, 68, 44, 0.9);
}
.btn-flame:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -14px rgba(232, 116, 42, 0.95);
}
.btn-ghost {
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}
.btn-ghost:hover {
  box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.55);
  color: var(--amber);
}
/* Press feedback — the lift reverses into a slight compress. */
.btn:active {
  transform: translateY(0) scale(0.98);
}
.btn-teal {
  background: var(--teal);
  color: #04241c;
  box-shadow: 0 10px 30px -12px rgba(0, 176, 144, 0.55);
}
.btn-teal:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -14px rgba(0, 176, 144, 0.6);
}
.acts {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: clamp(26px, 3vw, 40px);
}

/* ------------------------------------------------------------
   HEADER
   ------------------------------------------------------------ */
.hdr {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  height: 72px;
  display: flex;
  align-items: center;
  transition:
    background var(--mid) var(--ease),
    backdrop-filter var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
}
.hdr.stuck {
  background: rgba(8, 5, 3, 0.72);
  backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 1px 0 var(--hair);
}
.hdr .wrap {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 44px);
  /* Required, not cosmetic. Below 900px the nav is display:none, so with the
     default packing the brand and the two buttons all collapsed to the left
     and left 124px of dead space on the right of a 375px screen. */
  justify-content: space-between;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
.brand img {
  width: 30px;
  height: 30px;
}
.brand b {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 19px;
  letter-spacing: -0.02em;
}
.nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.8vw, 30px);
  margin-inline-start: auto;
}
.nav a {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--muted);
  position: relative;
  padding-block: 5px;
  transition: color var(--mid) var(--ease);
}
.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--amber);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--mid) var(--ease);
}
.nav a:hover {
  color: var(--text);
}
.nav a:hover::after {
  transform: scaleX(1);
}
/* Scrollspy: the chapter being read stays underlined. */
.nav a.on {
  color: var(--text);
}
.nav a.on::after {
  transform: scaleX(1);
}
.hdr-cta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
.hdr-cta .btn {
  padding: 11px 20px;
  font-size: 13.5px;
}
.iconbtn {
  display: none;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair-strong);
  transition:
    color var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
}
.iconbtn:hover {
  color: var(--amber);
  box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.5);
}
.iconbtn svg {
  width: 20px;
  height: 20px;
}

/* Mobile drawer */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 110;
  background: rgba(0, 0, 0, 0.72);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--mid) var(--ease),
    visibility var(--mid);
}
.scrim.show {
  opacity: 1;
  visibility: visible;
}
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 120;
  width: min(86vw, 340px);
  background: var(--ink-2);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transform: translateX(100%);
  transition: transform var(--slow) var(--ease);
  box-shadow: -30px 0 60px rgba(0, 0, 0, 0.6);
}
.drawer.open {
  transform: none;
}
.drawer-top {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 12px;
}
.drawer-top .iconbtn {
  display: grid;
}
.drawer a:not(.btn) {
  padding: 13px 4px;
  font-size: 16px;
  font-weight: 600;
  color: var(--muted);
  border-bottom: 1px solid var(--hair);
}
.drawer .btn {
  margin-top: 18px;
  justify-content: center;
}

/* ------------------------------------------------------------
   HERO
   ------------------------------------------------------------ */
/* Split product story: a short value proposition beside the actual
   controller and app assets, planned to fit in the first viewport. */
/* ============================================================
   HERO — two-beat stage
   The scene layers sit behind everything and cross-fade from the cold
   frame to the warm one; the phone rises in last as its own layer.
   RESTING STATE IS THE WARM ONE: .hero-problem is transparent by
   default and is only made visible while the sequence plays, so with
   JS off, on a reduced-motion setting, or after the animation has
   finished, the page simply shows the answer. Nothing is ever left
   permanently hidden behind a timeline.
   ============================================================ */
.hero {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-items: center;
  /* Clears the 72px fixed header with room to breathe. The old 92px floor
     was set when a kicker sat above the h1 and absorbed the difference;
     with that gone the heading came within 20px of the header bar. */
  padding-block: clamp(118px, 13vh, 138px) clamp(28px, 5vh, 48px);
  overflow: hidden;
  isolation: isolate;
  /* One scale for both beats. The question and the answer trade places
     in the same slot, so they have to be the same voice at the same
     size - two tokens here would drift the moment either is retuned. */
  --hero-type: clamp(38px, 4.6vw, 68px);
  /* The extra air at a sentence boundary, shared by both beats. Without
     it the question's two sentences were separated by exactly the same
     gap as a mid-sentence line wrap, so the break between them was
     invisible. */
  --hero-sentence-gap: clamp(10px, 1.6vh, 18px);
}
.hero-stage {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.hero-layer,
.hero-layer img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hero-layer img {
  object-fit: cover;
  /* The artwork puts the subject right of centre and keeps the left
     clear for type, so hold the right side as the frame narrows. */
  object-position: 64% center;
}
.hero-problem {
  opacity: 0;
}
/* Reinforces the artwork's own dark left side so the copy never needs
   a slab behind it, and closes the bottom edge into the ticker. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      90deg,
      rgba(8, 5, 3, 0.94) 0%,
      rgba(8, 5, 3, 0.88) 30%,
      rgba(8, 5, 3, 0.55) 55%,
      rgba(8, 5, 3, 0.32) 78%,
      rgba(8, 5, 3, 0.42) 100%
    ),
    linear-gradient(
      180deg,
      rgba(8, 5, 3, 0.72) 0%,
      rgba(8, 5, 3, 0) 26%,
      rgba(8, 5, 3, 0) 62%,
      rgba(8, 5, 3, 0.86) 100%
    );
}
/* The portrait artwork puts its dark zone at the TOP, not the left, so
   the scrim has to turn with it. Bound to the same 760px breakpoint as
   the <source> swap; without this the lead sits over lit plaster and
   measured 1.26:1. */
@media (max-width: 760px) {
  /* The old stops were 0.93 / 0.91 / 0.85 / 0.42 / 0.86: the top 64% sat at
     roughly 90% black, so the scene behind the copy was not a photograph at
     all, just a flat panel - and the jump from 0.85 to 0.42 and back to 0.86
     put a bright stripe across the middle that read as banding rather than
     light. This ramps down once, monotonically, so it looks like a lit room
     falling into shadow. The copy still clears AA; verified per glyph
     against the real portrait pixels, not against the average. */
  .hero-scrim {
    background: linear-gradient(
      180deg,
      rgba(8, 5, 3, 0.88) 0%,
      rgba(8, 5, 3, 0.87) 35%,
      rgba(8, 5, 3, 0.85) 57%,
      rgba(8, 5, 3, 0.4) 78%,
      rgba(8, 5, 3, 0.7) 100%
    );
  }
}
/* The phone layer. Cropped at the wrist in the artwork, so it is
   anchored past the bottom edge and reads as a hand coming into frame. */
.hero-hand {
  position: absolute;
  right: clamp(8px, 5vw, 84px);
  bottom: -3%;
  width: clamp(250px, 33vw, 540px);
  height: auto;
  filter: drop-shadow(0 34px 46px rgba(0, 0, 0, 0.75));
}
.hero-copy {
  position: relative;
  z-index: 1;
  /* 620px broke the Urdu heading over three lines once it gained its object
     ("Ab garam pani, ezGeyser sambhalay."). The extra width buys the
     second line back without pushing the text out of the scrim's dark side:
     52vw still lands inside the 55% stop where the overlay is at 0.55.
     The 640px floor matters: 52vw alone only reaches 640px at a 1231px
     viewport, so every width from 901px to 1231px was getting a NARROWER
     headline column than the <=900px layout hands out - the design got
     worse as the screen got bigger. */
  max-width: clamp(640px, 52vw, 720px);
}
/* The question. Cream, display face, sized as a voice rather than a
   caption. Each question is its own block so the break BETWEEN them is
   guaranteed and  only has to even out a single question. */
.hero-hook {
  max-width: 16ch;
  margin-bottom: clamp(18px, 2.6vh, 28px);
  font-family: var(--font-display);
  font-size: var(--hero-type);
  /* 900, not 700: the h1 it trades places with is Lato Black, and at this
     size two weight steps read as a different voice. Both weights are
     genuinely loaded, so this is a real difference rather than a
     synthesised one. */
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--text);
  transition: color var(--slow) var(--ease);
}
.hero-hook span {
  display: block;
  text-wrap: balance;
}
/* Second sentence gets the same breathing room the English line gets
   under the Urdu answer, so a sentence break reads as a sentence break
   in both beats rather than looking like another wrapped line. */
.hero-hook span + span {
  margin-top: var(--hero-sentence-gap);
}
.hero-hook em {
  color: var(--flame);
  font-style: normal;
}
/* The two beats share one grid cell. align-self:start puts them both at
   the same top edge so the question is replaced exactly where it stood,
   and the taller child (the answer) sets the height from the first
   frame - no reflow when they trade places.
   visibility, not just opacity, does the hiding: an opacity-0 block
   still takes clicks and keyboard focus, so the buttons would be
   tabbable while invisible. visibility also keeps the hidden beat out
   of the accessibility tree, so what a screen reader announces matches
   what is actually on screen. */
.hero-swap {
  display: grid;
}
.hero-swap > * {
  grid-area: 1 / 1;
  align-self: start;
}
/* RESTING STATE: the answer. The question is a beat-one-only element,
   so with JS off or reduced motion the hero opens on the resolution. */
.hero-hook {
  opacity: 0;
  visibility: hidden;
}
.hero h1 {
  font-size: var(--hero-type);
  line-height: 1;
  letter-spacing: -0.04em;
}
/* The Urdu answer is the payoff and carries the size; the English line
   is its quiet echo underneath, so the pair is one heading, not two. */
.hero-ur {
  display: block;
}
.hero-ur em {
  font-style: normal;
  color: var(--flame);
}
/* Every line in the hero now sits on the one shared scale: the Urdu
   question, the Urdu answer and this English line are all the same
   size, so the beats trade places without the type changing gear.
   1em resolves to the h1 font-size, which is var(--hero-type) - it
   cannot drift from the other two. */
.hero-en {
  display: block;
  margin-top: var(--hero-sentence-gap);
  font-size: 1em;
  letter-spacing: -0.04em;
  color: var(--text);
}
.hero-en em {
  font-style: normal;
  color: var(--flame);
}
.hero-lead {
  color: var(--muted);
  font-size: clamp(16px, 1.4vw, 19px);
  line-height: 1.62;
  max-width: 44ch;
  margin-top: clamp(20px, 3vh, 30px);
}
/* Says what the product IS, above a heading that says what it does for you.
   Set in the same voice as the chapter labels (.rail .l) rather than as
   uppercase letterspaced small caps: at 58 characters that treatment turns
   into three cramped lines of shouting. Muted, not flame - the h1 accents
   and the savings figure already own that colour, and a label that competes
   with the headline it introduces is not a label. */
.hero-kicker {
  font-size: clamp(12.5px, 1.05vw, 14px);
  font-weight: 650;
  letter-spacing: 0.01em;
  line-height: 1.5;
  color: var(--muted);
  /* Wide enough to hold the line intact on a desktop: at 38ch it broke
     after "electric and", orphaning "hybrid geyser". Still wraps on a
     phone, where there is no width to hold it either way. */
  max-width: 60ch;
  margin-bottom: clamp(14px, 1.8vh, 20px);
}

/* The savings claim gets its own beat between the promise and the buttons.
   The number is set in the display face at roughly half the h1 so it reads
   as a headline figure without competing with the headline, and the label
   stays muted body text so the two never merge into one grey block. */
.hero-save {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: clamp(18px, 2.4vh, 26px);
}
.hero-save b {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(23px, 2.2vw, 32px);
  letter-spacing: -0.03em;
  line-height: 1;
  /* The same flame the h1 accents use. Amber sat a step warmer and read as
     a third colour in a hero that only has two. */
  color: var(--flame);
}
.hero-save span {
  font-size: clamp(13px, 1.1vw, 15.5px);
  color: var(--muted);
}
.hero .acts {
  margin-top: clamp(24px, 3.4vh, 36px);
}

/* ---------- the sequence ----------
   Beat one: cold frame, Urdu question, nothing else.
   Beat two: the room warms, the question leaves, and the English
   answer takes the same spot, then the hand arrives.
   .play snaps to beat one INSTANTLY (no transition on the start state,
   or it would fade *into* beat one and, because .run lands two frames
   later, retarget before the delay elapsed and never move). .run
   carries everything to the end state on staggered delays, and a timer
   strips both classes so the hero rests on plain, visible CSS. */
/* Quick in, hold, slow out - all in one animation, because a transition
   cannot take a property from 0 to 1 and back again. Safe as an animation
   only because this layer rests HIDDEN: if animations never run it simply
   stays invisible, which is the correct resting state anyway. */
.hero.play .hero-problem {
  animation: beatOne 3.8s var(--ease) both;
}
@keyframes beatOne {
  0% {
    opacity: 0;
  }
  11% {
    opacity: 1;
  }
  53% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
.hero.play .hero-solution img {
  opacity: 0;
  transform: scale(1.05);
}
.hero.play .hero-hand {
  opacity: 0;
  transform: translateY(14%) scale(0.97);
}
.hero.play .hero-hook {
  opacity: 1;
  visibility: visible;
}
.hero.play .hero-answer {
  visibility: hidden;
}
.hero.play .hero-answer .hero-kicker,
.hero.play .hero-answer h1,
.hero.play .hero-answer .hero-lead,
.hero.play .hero-answer .hero-save,
.hero.play .hero-answer .acts {
  opacity: 0;
  transform: translateY(20px);
}

/* ...and the travel to the end state, which is where the timing lives */
.hero.play.run .hero-solution img {
  opacity: 1;
  transform: none;
  transition:
    opacity 1.8s var(--ease) 2s,
    transform 2.4s var(--ease) 2s;
}
/* The question leaves first, lifting slightly as it goes, and its
   visibility flips only once it is fully faded. */
.hero.play.run .hero-hook {
  opacity: 0;
  transform: translateY(-14px);
  visibility: hidden;
  transition:
    opacity 0.8s var(--ease) 2s,
    transform 0.8s var(--ease) 2s,
    visibility 0s linear 2.8s;
}
.hero.play.run .hero-answer {
  visibility: visible;
  transition: visibility 0s linear 2.85s;
}
/* The label lands first and the heading follows it a beat later, so the two
   read as one block being set rather than as two things arriving at once. */
.hero.play.run .hero-answer .hero-kicker {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 2.85s,
    transform 0.9s var(--ease) 2.85s;
}
.hero.play.run .hero-answer h1 {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 2.96s,
    transform 0.9s var(--ease) 2.96s;
}
.hero.play.run .hero-answer .hero-lead {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 3.12s,
    transform 0.9s var(--ease) 3.12s;
}
.hero.play.run .hero-answer .hero-save {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 3.26s,
    transform 0.9s var(--ease) 3.26s;
}
.hero.play.run .hero-answer .acts {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 3.38s,
    transform 0.9s var(--ease) 3.38s;
}
/* Last in, and the longest travel: 3.5s + 1.05s lands at 4.55s, inside the
   4.8s timer that strips .play/.run. Anything later is cut off mid-move. */
.hero.play.run .hero-hand {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.9s var(--ease) 3.5s,
    transform 1.05s var(--ease) 3.5s;
}

.product-media .pool {
  animation: breathe 9s ease-in-out infinite alternate;
}
@keyframes breathe {
  from {
    opacity: 0.82;
    transform: scale(0.985);
  }
  to {
    opacity: 1;
    transform: scale(1.015);
  }
}

/* ------------------------------------------------------------
   RECOGNITION
   Hairline bands rather than boxed cells - this is a credential list
   you skim, so it takes the SEQUENCE vocabulary (open rules) rather
   than the catalogue one. Label left, marks right, minimal prose.
   ------------------------------------------------------------ */
.creds {
  margin-top: clamp(40px, 4.6vw, 68px);
  border-bottom: 1px solid var(--hair);
}
.cred-row {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
  padding-block: clamp(22px, 2.6vw, 32px);
  border-top: 1px solid var(--hair);
}
.cred-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
}
.marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px clamp(28px, 4vw, 52px);
}
/* Every mark ships pre-processed: cropped to its own ink with no canvas
   padding left over, and already white on transparent, all as PNG.
   That is not enough on its own. An equal BOX height does not make an
   equal LOGO, because a 3-line stacked lockup spends its 49px on three
   small lines while a single wordmark spends all of it on one. Measured
   across this set, the graphic inside a uniform box ranged 10.8px to
   49px - a 4.55x spread.
   So each mark carries an optical multiplier in --s, derived from the ink
   it actually lays down (area scales with the square of size, so the
   factor is sqrt of the ratio) and then hand-corrected against a render.
   No max-width: width comes from the aspect ratio, so capping it would
   letterbox the mark. */
.marks img {
  height: calc(clamp(36px, 3.4vw, 50px) * var(--s, 1));
  width: auto;
  object-fit: contain;
  object-position: left center;
  opacity: 0.85;
  transition: opacity var(--mid) var(--ease);
}
.marks img:hover {
  opacity: 1;
}
/* The count badge: we won this one twice, said without a sentence. */
/* "Won twice", said without a sentence. It used to hang off the logo's
   bottom-right corner on absolute coordinates, which put it at a different
   optical distance for every logo width and let it collide with whatever
   sat to the right. Now it is a flex sibling: it sits ON the baseline of
   the mark it belongs to, at a fixed gap, and the row reflows around it. */
.mark-badge {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
.mark-badge::after {
  content: attr(data-count);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 13px;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--amber);
  padding: 7px 11px 8px;
  border-radius: 999px;
  background: rgba(248, 162, 42, 0.13);
  box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.34);
  white-space: nowrap;
}

/* ------------------------------------------------------------
   SOCIALS
   One component with two homes: under the footer lockup, and as
   the last row of the contact channel list. Sized to the 44px
   touch floor, not to the 17px glyph sitting inside it.
   ------------------------------------------------------------ */
.socials {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}
.socials a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--dim);
  box-shadow: inset 0 0 0 1px var(--hair-strong);
  transition:
    color var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease),
    transform var(--mid) var(--ease);
}
.socials a:hover {
  color: var(--flame);
  box-shadow: inset 0 0 0 1px rgba(232, 116, 42, 0.5);
  transform: translateY(-2px);
}
.socials svg {
  width: 17px;
  height: 17px;
}
/* In the contact column it IS a channel row, so it takes the same divider
   and the same horizontal padding as the three above it. */
.socials-wide {
  margin-top: 0;
  padding: clamp(16px, 1.8vw, 22px) clamp(4px, 1vw, 12px);
  border-top: 1px solid var(--hair);
  background: var(--ink-0);
}

/* ------------------------------------------------------------
   DEMO — click to load
   The thumbnail button and the player that replaces it share one
   geometry class, so nothing shifts when the swap happens. The
   appearance/border resets are required: this is a <button>, whose
   UA default is 2px outset, which shades two edges light and shows
   as a grey rule on two sides over a dark thumbnail.
   ------------------------------------------------------------ */
.demo-shell {
  margin-top: clamp(44px, 5vw, 76px);
  max-width: 960px;
}
.video-frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-panel);
  overflow: hidden;
  background: var(--ink-2);
  padding: 0;
  border: none;
  appearance: none;
  -webkit-appearance: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}
.video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* Thumbnail state only. These are full-bleed layers, so none of them
   may survive onto the player - they would swallow the clicks meant
   for YouTube's own controls. */
.video-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--slow) var(--ease);
}
.video-frame:hover img {
  transform: scale(1.03);
}
.video-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8, 5, 3, 0.25), rgba(8, 5, 3, 0.72));
}
.video-frame .play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: clamp(64px, 7vw, 84px);
  height: clamp(64px, 7vw, 84px);
  border-radius: 999px;
  background: var(--grad-action);
  color: #fff;
  display: grid;
  place-items: center;
  z-index: 3;
  pointer-events: none;
  box-shadow: 0 18px 40px -14px rgba(209, 68, 44, 0.9);
  transition: transform var(--mid) var(--ease);
}
.video-frame .play svg {
  width: 30px;
  height: 30px;
  margin-left: 4px;
}
.video-frame:hover .play {
  transform: scale(1.07);
}
.video-frame .vlabel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(18px, 2.4vw, 28px);
  z-index: 3;
  text-align: center;
  pointer-events: none;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.92);
}
.demo-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3, 14px);
  margin-top: clamp(20px, 2.4vw, 28px);
}

/* Scrolling capability ticker under the fold */
.ticker {
  position: relative;
  z-index: 2;
  border-block: 1px solid var(--hair);
  padding-block: 18px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.ticker-track {
  display: flex;
  width: max-content;
  gap: 0;
  animation: slide 44s linear infinite;
}
.ticker-track span {
  display: inline-flex;
  align-items: center;
  gap: 18px;
  padding-inline: 18px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}
.ticker-track span::after {
  content: "";
  width: 1px;
  height: 16px;
  background: var(--hair-strong);
  flex: none;
}
@keyframes slide {
  to {
    transform: translateX(-50%);
  }
}
.ticker:hover .ticker-track {
  animation-play-state: paused;
}

/* ------------------------------------------------------------
   SECTION SHELL — sticky chapter rail on the left
   ------------------------------------------------------------ */
.bay {
  position: relative;
  z-index: 2;
  padding-block: var(--bay);
  /* The ember/pool light layers are deliberately wider than their
     container. `clip` contains them without creating a scroll
     container — `hidden` would, and that silently kills the sticky
     chapter rail inside. */
  overflow-x: clip;
}
/* The tint alternation is gone: it measured 1.02:1 against the base
   canvas - imperceptible as rhythm, noticeable only as an arbitrary
   seam. One flat canvas; hairlines carry information, panels carry
   offers, and that contrast does the pacing. */
.chapter {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  align-items: start;
}
.rail {
  position: sticky;
  top: 108px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.rail .l {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--flame);
  line-height: 1.5;
}
.rail i {
  width: 46px;
  height: 1px;
  background: var(--hair-strong);
  display: block;
  margin-top: 4px;
}

/* ------------------------------------------------------------
   PHOTOGRAPHY — floats, no chrome
   ------------------------------------------------------------ */
/* Full-bleed plate: the photo runs edge to edge and the page goes
   quiet around it. Used exactly twice - after chapter 01 and after
   chapter 03 - so the device reads as rhythm, not accident. */
.plate {
  position: relative;
  z-index: 2;
  /* No own margin: the neighbouring sections' padding supplies the
     quiet, and stacking both produced 144-280px voids. */
  margin-block: 0;
}
.plate img {
  width: 100%;
  max-height: min(76vh, 720px);
  object-fit: cover;
  border-radius: 0;
  box-shadow: none;
}
.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8, 5, 3, 0.62) 0%, rgba(8, 5, 3, 0) 28%, rgba(8, 5, 3, 0.72) 100%);
}
.plate figcaption {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(18px, 3vw, 34px);
  z-index: 2;
  color: var(--muted);
  font-size: 12.5px;
  letter-spacing: 0.02em;
  text-align: center;
  width: max-content;
  max-width: min(56ch, calc(100% - 2 * var(--gut)));
}

/* ------------------------------------------------------------
   PROBLEM — a numbered listing, not cards
   ------------------------------------------------------------ */
.listing {
  list-style: none;
  margin-top: clamp(44px, 5vw, 76px);
}
.listing li {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(16px, 3vw, 46px);
  align-items: baseline;
  padding-block: clamp(24px, 3vw, 38px);
  border-top: 1px solid var(--hair);
  transition: background var(--mid) var(--ease);
}
.listing li:last-child {
  border-bottom: 1px solid var(--hair);
}
.listing .n {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 13px;
  letter-spacing: 0.12em;
  color: var(--dim);
}
.listing h3 {
  font-size: clamp(19px, 1.8vw, 25px);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.listing p {
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.65;
}

/* ------------------------------------------------------------
   CAPABILITIES — sparse grid, hairlines only
   ------------------------------------------------------------ */
/* Hairline grids (.caps/.variants/.benefits): dividers are
   per-cell borders pulled -1px and clipped, NOT a painted container
   background under a 1px gap. The painted-background version leaks a
   grey sliver at the container's right edge whenever Chrome snaps a
   fractional track width to device pixels — a stray vertical line. */
.caps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  overflow: hidden;
  margin-top: clamp(44px, 5vw, 76px);
  border-block: 1px solid var(--hair);
}
.cap {
  background: var(--ink-0);
  border-top: 1px solid var(--hair);
  border-left: 1px solid var(--hair);
  margin: -1px 0 0 -1px;
  padding: var(--cell-pad);
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: background var(--slow) var(--ease);
}
.cap .ic {
  width: 26px;
  height: 26px;
  color: var(--flame);
}
.cap .ic svg {
  width: 100%;
  height: 100%;
}
.cap h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.005em;
  line-height: 1.3;
}
.cap p {
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.65;
}

/* ------------------------------------------------------------
   STEPS — oversized ghost numerals
   ------------------------------------------------------------ */
.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 2.6vw, 40px);
  margin-top: clamp(44px, 5vw, 76px);
}
.step {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  gap: 14px;
  padding-top: 26px;
  border-top: 1px solid var(--hair-strong);
  position: relative;
}
/* Solid numerals in the same flame the rest of the page accents with,
   not outlined ghosts. */
.step .num {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(30px, 4vw, 56px);
  line-height: 0.8;
  letter-spacing: -0.05em;
  color: var(--flame);
}
.step h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.005em;
}
.step p {
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.65;
}

/* ------------------------------------------------------------
   PRODUCTS — each one presented as an object under a light
   ------------------------------------------------------------ */
/* Each series is one contained world: the panel holds the product photo,
   its variants with their Add buttons, and the interface that defines it
   (the app for Premium, the on-unit display for Deluxe). The panel edge
   carries the series' temperature — warm ring for Premium, cool ring for
   Deluxe — so the two offerings read as distinct at a glance. */
.product {
  position: relative;
  background: var(--ink-2);
  border-radius: var(--r-panel);
  padding: clamp(24px, 4vw, 64px);
  overflow: hidden;
}
.product + .product {
  margin-top: clamp(48px, 7vw, 96px);
}
#products > .wrap:last-child {
  margin-top: clamp(44px, 5vw, 76px);
}
.product::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
}
#premium {
  box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.12);
}
#premium::before {
  background: var(--grad-line);
}
#deluxe {
  box-shadow: inset 0 0 0 1px rgba(95, 212, 238, 0.1);
}
#deluxe::before {
  background: linear-gradient(90deg, transparent, rgba(95, 212, 238, 0.45), transparent);
}
.product-head {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: clamp(30px, 5vw, 78px);
  align-items: center;
}
.product-media {
  position: relative;
  isolation: isolate;
}
/* Ground light: the object sits in a pool of its own colour.
   Attached to the product, which is the whole justification. */
.product-media .pool {
  position: absolute;
  z-index: -1;
  /* The glow must stay behind the photo's footprint. With a negative
     bottom inset it spilled 59px below the photo — across the variants'
     top hairline, drowning the border's left end and smearing a maroon
     bar onto the canvas. Positive bottom inset + blur keeps the halo
     at the photo's edges only. */
  inset: 8% -9% 4% -9%;
  background: radial-gradient(
    50% 46% at 50% 62%,
    rgba(232, 116, 42, 0.34) 0%,
    rgba(209, 68, 44, 0.14) 44%,
    rgba(209, 68, 44, 0) 74%
  );
  filter: blur(18px);
}
.product-media.cool .pool {
  background: radial-gradient(
    50% 46% at 50% 62%,
    rgba(95, 212, 238, 0.22) 0%,
    rgba(95, 212, 238, 0.08) 46%,
    rgba(95, 212, 238, 0) 76%
  );
}
.product-media img {
  width: 100%;
  border-radius: var(--r);
  box-shadow: 0 46px 84px -34px rgba(0, 0, 0, 0.92);
}
.tag {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 20px;
}
.tag svg {
  width: 15px;
  height: 15px;
}
.tag.cool {
  color: var(--water);
}
.product h2 {
  font-size: clamp(28px, 3.6vw, 48px);
  max-width: 18ch;
}
.product .sec-lead {
  max-width: 52ch;
}

.variants {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  overflow: hidden;
  border-block: 1px solid var(--hair);
  margin-top: clamp(40px, 5vw, 72px);
}
.variants.two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.variant {
  background: var(--ink-0);
  border-top: 1px solid var(--hair);
  border-left: 1px solid var(--hair);
  margin: -1px 0 0 -1px;
  padding: var(--cell-pad);
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: box-shadow var(--mid) var(--ease);
}
/* A selected unit shows it at a glance, not just in the bar. The ring
   is 2px and the cell's own borders recolour, so it stays readable now
   that the grid's hairlines are cell borders. */
.variant.picked {
  box-shadow: inset 0 0 0 2px rgba(248, 162, 42, 0.38);
  border-color: rgba(248, 162, 42, 0.38);
}
/* Inside a product panel the cells sit on the panel's own surface. */
.product .variant {
  background: var(--ink-2);
}
/* Fuel badges. Icon INK must match optically, not the boxes: a Material
   glyph fills ~18.5 of its 24-unit box (so a 20px box shows ~15px of
   ink), while the hybrid mark's tight viewBox fills 100% of its height.
   Sizing the hybrid to 15px tall makes all three marks the same visual
   size; the 44px circle gives the wide combined mark room to breathe. */
.variant .fuel {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
}
.variant .fuel svg {
  width: 20px;
  height: 20px;
}
.variant .fuel .ico-hybrid {
  width: auto;
  height: 15px;
}
.fuel-gas {
  background: rgba(232, 116, 42, 0.14);
  color: var(--amber);
}
.fuel-electric {
  background: rgba(95, 212, 238, 0.14);
  color: var(--water);
}
.fuel-hybrid {
  background: rgba(136, 217, 130, 0.14);
  color: #88d982;
}
.variant b {
  font-size: 15.5px;
  font-weight: 700;
}
.variant span:not(.fuel) {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.6;
}

/* Unit selection. The Add control swaps into a quantity stepper.
   Both are JS-only affordances and vanish cleanly without JS. */
.pick {
  margin-top: auto;
  padding-top: 18px;
}
/* The buying action — a real button, not a whisper. Same action ramp
   as the site's primary CTAs so it reads as "this is the thing to do". */
.pick-add {
  display: flex;
  width: 100%;
  justify-content: center;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: #fff;
  padding: 11px 20px;
  border-radius: 999px;
  background: var(--grad-action);
  box-shadow: 0 10px 30px -12px rgba(209, 68, 44, 0.9);
  transition:
    transform var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
  will-change: transform;
}
.pick-add:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -14px rgba(232, 116, 42, 0.95);
}
.pick-add:active {
  transform: translateY(0) scale(0.98);
}
.pick-add[hidden] {
  display: none;
}
.qty {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  padding-inline: 4px;
  border-radius: 999px;
  background: rgba(248, 162, 42, 0.1);
  box-shadow: inset 0 0 0 1.5px rgba(248, 162, 42, 0.6);
}
.qty[hidden] {
  display: none;
}
.qty-btn {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  font-size: 16px;
  font-weight: 700;
  color: var(--amber);
  border-radius: 999px;
  transition: background var(--mid) var(--ease);
}
.qty-btn:hover {
  background: rgba(248, 162, 42, 0.12);
}
.qty output {
  min-width: 26px;
  text-align: center;
  font-weight: 800;
  font-size: 14px;
}

.spec {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 30px;
  margin-top: clamp(28px, 3vw, 40px);
  list-style: none;
}
.spec li {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--muted);
}
.spec svg {
  width: 16px;
  height: 16px;
  color: var(--flame);
  flex: none;
}
.spec-more {
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.65;
  max-width: 74ch;
  margin-top: 20px;
}

/* Series jump chips in the products intro — colour-coded to match the
   warm/cool identity each series carries below. */
.series-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: clamp(22px, 2.6vw, 32px);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--muted);
  padding: 10px 18px;
  min-height: 44px;
  box-sizing: border-box;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--hair-strong);
  transition:
    color var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease),
    transform var(--mid) var(--ease);
}
.chip:hover {
  color: var(--text);
  transform: translateY(-1px);
  box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.5);
}
.chip .dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--amber);
  flex: none;
}
.chip .dot.cool {
  background: var(--water);
}
.series-jump + .note {
  margin-top: 18px;
}

/* The interface chapter under each product */
.iface {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.12fr);
  gap: clamp(30px, 5vw, 76px);
  align-items: center;
  /* Lives inside the product panel now — panel-scale spacing, not
     section-scale. */
  margin-top: clamp(40px, 5vw, 72px);
  padding-top: clamp(40px, 5vw, 72px);
  border-top: 1px solid var(--hair);
}
.iface.flip > .iface-media {
  order: -1;
}
.iface h3 {
  font-size: clamp(23px, 2.6vw, 34px);
  max-width: 16ch;
}
.iface p {
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.7;
  max-width: 48ch;
  margin-top: 18px;
}
.checks {
  list-style: none;
  margin-top: 26px;
  display: grid;
  gap: 16px;
}
.checks li {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.6;
}
.checks svg {
  width: 22px;
  height: 22px;
  color: var(--flame);
  margin-top: 1px;
}
.iface-media {
  position: relative;
  isolation: isolate;
}
.iface-media img {
  width: 100%;
  border-radius: var(--r);
  box-shadow: 0 46px 84px -34px rgba(0, 0, 0, 0.9);
}

/* ------------------------------------------------------------
   COMPARE
   ------------------------------------------------------------ */
.cmp-wrap {
  margin-top: clamp(44px, 5vw, 76px);
  overflow-x: auto;
  scrollbar-width: thin;
}
table.cmp {
  width: 100%;
  min-width: 520px;
  font-size: 14.5px;
}
table.cmp caption {
  text-align: left;
  color: var(--dim);
  font-size: 12.5px;
  padding-bottom: 14px;
}
table.cmp th,
table.cmp td {
  text-align: left;
  padding: 15px 16px;
  border-bottom: 1px solid var(--hair);
}
table.cmp thead th {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
  border-bottom-color: var(--hair-strong);
  padding-bottom: 12px;
}
/* Column identity: Deluxe reads cool, Premium reads warm — the same
   coding the products section already uses. */
table.cmp thead th:nth-child(2) {
  color: var(--water);
}
table.cmp thead th:nth-child(3) {
  color: var(--amber);
}
/* The capability column is <th scope="row">, not <td>, so each Yes/No cell
   is associated with the capability it belongs to. These two rules undo the
   UA's bold-and-centred default for <th> so it looks exactly as it did. */
table.cmp tbody th {
  color: var(--text);
  font-weight: inherit;
}
table.cmp td:not(:first-child),
table.cmp th:not(:first-child) {
  width: 128px;
  text-align: center;
}
table.cmp tbody tr:hover th,
table.cmp tbody tr:hover td {
  background: rgba(255, 255, 255, 0.022);
}
.yes,
.no {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
}
.yes {
  background: rgba(232, 116, 42, 0.18);
  color: var(--amber);
}
.no {
  color: #6b5c52;
}
.yes svg,
.no svg {
  width: 15px;
  height: 15px;
}

/* ------------------------------------------------------------
   BENEFITS — editorial two-column
   ------------------------------------------------------------ */
.benefits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  overflow: hidden;
  border-block: 1px solid var(--hair);
  margin-top: clamp(44px, 5vw, 76px);
}
.benefit {
  background: var(--ink-0);
  border-top: 1px solid var(--hair);
  border-left: 1px solid var(--hair);
  margin: -1px 0 0 -1px;
  padding: var(--cell-pad);
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 10px 22px;
  align-items: start;
  transition: background var(--slow) var(--ease);
}
.benefit svg {
  width: 26px;
  height: 26px;
  color: var(--flame);
  grid-row: span 2;
}
.benefit h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.005em;
  align-self: center;
}
.benefit p {
  color: var(--muted);
  font-size: 14.5px;
  /* 1.65, matching every other card paragraph; this was the lone 1.68. */
  line-height: 1.65;
}

/* ------------------------------------------------------------
   TECHNOLOGY
   ------------------------------------------------------------ */
/* Technology is a CATALOGUE - six parallel items you scan, exactly like
   capabilities and benefits - so it uses their boxed-cell treatment.
   It was the only one of the three drawn as open rules, which read as
   drift rather than intent. */
.tech {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  overflow: hidden;
  border-block: 1px solid var(--hair);
  margin-top: clamp(44px, 5vw, 76px);
}
.tech-item {
  display: flex;
  flex-direction: column;
  gap: 13px;
  background: var(--ink-0);
  border-top: 1px solid var(--hair);
  border-left: 1px solid var(--hair);
  margin: -1px 0 0 -1px;
  padding: var(--cell-pad);
}
.tech-item .ic {
  /* align-self is load-bearing: .tech-item is a column flex container, so
     a width:auto child stretches to the full column. An SVG centres its
     ink inside its box, so that stretch pushed the hybrid mark 120px
     right of the other five icons. flex-start keeps the box at content
     width and every icon on the same left edge. */
  align-self: flex-start;
  min-width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  color: var(--flame);
}
.tech-item .ic svg {
  width: 26px;
  height: 26px;
}
/* The hybrid mark is 2.04:1, so an explicit box matching that ratio makes
   its ink fill the box exactly - no letterboxing, and ~20px of ink to
   match the Material glyphs beside it (a 26px box shows ~20px of ink). */
.tech-item .ic .ico-hybrid {
  width: 41px;
  height: 20px;
}
.tech-item h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.005em; /* was inheriting the display face's -0.035em */
  line-height: 1.3;
}
.tech-item p {
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.65;
}
.pill {
  align-self: flex-start;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--water);
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(95, 212, 238, 0.12);
}

/* ------------------------------------------------------------
   OEM
   ------------------------------------------------------------ */
.oem {
  background: var(--ink-2);
  border-radius: var(--r-panel);
  padding: clamp(34px, 5vw, 72px);
  margin-top: clamp(44px, 5vw, 76px);
  position: relative;
  overflow: hidden;
  /* Same panel recipe as the product panels: ring + top line. */
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}
.oem::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--grad-line);
}
.oem-flow {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 2.6vw, 40px);
  counter-reset: oem;
}
/* A numbered sequence (1 -> 4), so it carries the same open-rule
   treatment as the problem list and the how-it-works steps rather than
   no treatment at all. */
.oem-flow li {
  display: flex;
  flex-direction: column;
  gap: 11px;
  counter-increment: oem;
  padding-top: 22px;
  border-top: 1px solid var(--hair-strong);
}
.oem-flow .ic {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: rgba(232, 116, 42, 0.14);
  color: var(--amber);
}
.oem-flow .ic svg {
  width: 20px;
  height: 20px;
}
.oem-flow b {
  font-size: 15.5px;
  font-weight: 700;
}
.oem-flow b::before {
  content: counter(oem) " · ";
  color: var(--flame);
}
.oem-flow span {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.6;
}

/* ------------------------------------------------------------
   WHO BUILDS IT — KytherTek's colours on ezGeyser's layout.
   Same chapter grid, hairlines and spec strip as every other
   section; only the accent hue changes, so it reads as "their
   brand, our house style".
   ------------------------------------------------------------ */
/* KytherTek chapter: same canvas as every other section - the teal
   accents alone mark the guest brand. */
/* The chapter label is the first teal thing on screen, so it is what marks
   the handover to the guest brand. The teal used to be set on .rail .n,
   which is the numeral - and the numeral was never rendered. */
.kt .rail .l {
  color: var(--teal);
}
.kt .rail i {
  background: rgba(0, 176, 144, 0.3);
}
.kt .sec-title em {
  color: var(--teal);
}
.kt .spec svg {
  color: var(--teal);
}
.kt-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(20px, 3vw, 36px);
  margin-top: clamp(30px, 3.4vw, 48px);
}
.kt-logo {
  display: grid;
  place-items: center;
  background: #fff;
  border-radius: var(--r);
  padding: 18px 28px;
  transition:
    transform var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
}
.kt-logo img {
  width: 168px;
  height: auto;
}
.kt-logo:hover {
  transform: translateY(-3px);
  box-shadow: 0 24px 50px -18px rgba(0, 176, 144, 0.55);
}

/* ------------------------------------------------------------
   CONTACT
   ------------------------------------------------------------ */
.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: stretch;
  margin-top: clamp(44px, 5vw, 76px);
}
.channels {
  display: flex;
  flex-direction: column;
  border-block: 1px solid var(--hair);
}
.channel + .channel {
  border-top: 1px solid var(--hair);
}
.channel {
  background: var(--ink-0);
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 20px;
  gap: 18px;
  align-items: center;
  padding: clamp(20px, 2.2vw, 28px) clamp(4px, 1vw, 12px);
  transition: background var(--mid) var(--ease);
  /* The grid stretches .channels to the map's height. Without flex:1 the
     rows keep their natural height and the leftover strip at the bottom
     shows the container's grey gap-paint — a bare grey bar on the page.
     With it, the rows absorb the extra height and the columns match. */
  flex: 1;
}
a.channel:hover {
  background: var(--ink-2);
}
.channel .ic {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: rgba(232, 116, 42, 0.14);
  color: var(--amber);
}
.channel .ic svg {
  width: 20px;
  height: 20px;
}
.channel .ic.wa {
  background: rgba(37, 211, 102, 0.14);
  color: #25d366;
}
.channel b {
  display: block;
  font-size: 15.5px;
  font-weight: 700;
}
.channel span {
  display: block;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.5;
}
.channel .arw {
  color: var(--dim);
  justify-self: end;
  transition:
    transform var(--mid) var(--ease),
    color var(--mid) var(--ease);
}
.channel .arw svg {
  width: 18px;
  height: 18px;
}
a.channel:hover .arw {
  color: var(--amber);
  transform: translate(3px, -3px);
}
.map {
  border-radius: var(--r);
  overflow: hidden;
  min-height: 340px;
  position: relative;
  /* Hairline frame, not a floating shadow card - the map lives in the
     lines vocabulary beside the channel rows, matching its section. */
  box-shadow: 0 0 0 1px var(--hair-strong);
}
.map iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  /* Google's embed has no dark tile set; this keeps it from being
     a white rectangle punched through the page. */
  filter: invert(0.9) hue-rotate(180deg) saturate(0.7) brightness(0.92);
}

/* ------------------------------------------------------------
   CLOSING CALL
   ------------------------------------------------------------ */
.close {
  position: relative;
  z-index: 2;
  text-align: center;
  padding-block: var(--bay);
  overflow: hidden;
}
.close::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -46%;
  width: min(1100px, 130vw);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(
    closest-side,
    rgba(232, 116, 42, 0.2) 0%,
    rgba(209, 68, 44, 0.08) 46%,
    rgba(209, 68, 44, 0) 72%
  );
  pointer-events: none;
}
.close h2 {
  /* Longer closing line now — sized down a step and given more measure
     so it lands in two to three balanced lines, not five. */
  font-size: clamp(28px, 4.4vw, 60px);
  max-width: 24ch;
  margin-inline: auto;
}
.close h2 em {
  font-style: normal;
  color: var(--flame);
}
/* Roman-Urdu echo - bookends the hero's voice at the finale.
   Written as p.close-echo inside .close so it outranks the .close p
   lead recipe below, which otherwise clobbers it (0,1,1 vs 0,1,0). */
.close p.close-echo {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--flame);
  max-width: none;
  margin: 0 auto 18px;
}
.close p {
  color: var(--muted);
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.68;
  max-width: 52ch;
  margin: clamp(18px, 2vw, 26px) auto 0;
}
.close .acts {
  justify-content: center;
}

/* ------------------------------------------------------------
   FOOTER
   ------------------------------------------------------------ */
.foot {
  position: relative;
  z-index: 2;
  border-top: 1px solid var(--hair);
  padding-block: clamp(48px, 6vw, 76px) 34px;
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 3vw, 54px);
}
/* The proper horizontal lockup, not the favicon plus a typeset word: it
   already carries the wordmark and the Urdu descender line, so pairing it
   with a <b>ezGeyser</b> would set the name twice in two different faces. */
.foot-mark {
  display: block;
  height: 40px;
  width: auto;
}
.foot-brand p {
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.65;
  max-width: 34ch;
  margin-top: 14px;
}
.foot h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 16px;
}
/* The gap gives up exactly what the links take on as padding, so the row
   pitch is unchanged at 34px while each target grows past the 24px
   minimum. Visual rhythm identical, hit area bigger. */
.foot ul {
  list-style: none;
  display: grid;
  gap: 5px;
}
.foot li a,
.foot li span {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding-block: 3px;
  color: var(--muted);
  font-size: 14px;
  transition: color var(--mid) var(--ease);
}
.foot li a:hover {
  color: var(--amber);
}
.foot li svg {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--dim);
}
.foot-base {
  margin-top: clamp(36px, 4vw, 58px);
  padding-top: 22px;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  justify-content: space-between;
  color: var(--dim);
  font-size: 12.5px;
}

/* ------------------------------------------------------------
   BACK TO TOP — appears once you are a screen deep
   ------------------------------------------------------------ */
.totop {
  position: fixed;
  right: clamp(16px, 3vw, 32px);
  bottom: calc(clamp(16px, 3vw, 32px) + env(safe-area-inset-bottom, 0px));
  z-index: 85;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: var(--text);
  background: rgba(26, 18, 13, 0.88);
  backdrop-filter: blur(12px) saturate(130%);
  box-shadow:
    0 18px 40px -16px rgba(0, 0, 0, 0.85),
    inset 0 0 0 1px var(--hair-strong);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition:
    opacity var(--mid) var(--ease),
    transform var(--mid) var(--ease),
    visibility var(--mid) var(--ease),
    color var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
}
.totop.show {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.totop:hover {
  color: var(--amber);
  box-shadow:
    0 18px 40px -16px rgba(0, 0, 0, 0.85),
    inset 0 0 0 1px rgba(248, 162, 42, 0.5);
}
.totop svg {
  width: 20px;
  height: 20px;
}
/* The order bar owns the bottom centre; on narrow screens it spans the
   full width, so lift the button clear of it rather than overlapping. */
@media (max-width: 560px) {
  body.has-bar .totop {
    bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  }
}

/* ------------------------------------------------------------
   ORDER BAR — exists only while units are selected
   ------------------------------------------------------------ */
.orderbar {
  position: fixed;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 90;
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 26px);
  max-width: calc(100vw - 24px);
  padding: 10px 10px 10px 24px;
  border-radius: 999px;
  background: rgba(26, 18, 13, 0.9);
  backdrop-filter: blur(14px) saturate(130%);
  box-shadow:
    0 24px 60px -18px rgba(0, 0, 0, 0.85),
    inset 0 0 0 1px var(--hair-strong);
  animation: barin var(--slow) var(--ease);
}
.orderbar[hidden] {
  display: none;
}
@keyframes barin {
  from {
    opacity: 0;
    transform: translate(-50%, 16px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}
.ob-sum {
  font-size: 14px;
  color: var(--muted);
  white-space: nowrap;
}
.ob-sum b {
  color: var(--amber);
  font-size: 15px;
}
.ob-acts {
  display: flex;
  align-items: center;
  gap: 10px;
}
.ob-clear {
  font-size: 13px;
  font-weight: 600;
  color: var(--dim);
  padding: 8px 6px;
  transition: color var(--mid) var(--ease);
}
.ob-clear:hover {
  color: var(--text);
}
.orderbar .btn {
  padding: 11px 20px;
  font-size: 13.5px;
}
@media (max-width: 560px) {
  .orderbar {
    left: 12px;
    right: 12px;
    transform: none;
    justify-content: space-between;
    gap: 10px;
    padding-left: 16px;
  }
  /* "3 units" is enough at phone width — the trailing word made the
     Buy button wrap to two lines. */
  .ob-sum .ob-tail {
    display: none;
  }
  .orderbar .btn {
    padding: 11px 16px;
  }
  @keyframes barin {
    from {
      opacity: 0;
      transform: translateY(16px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

/* ------------------------------------------------------------
   REVEAL
   ------------------------------------------------------------ */
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity var(--slow) var(--ease),
    transform var(--slow) var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.reveal.in {
  opacity: 1;
  transform: none;
}
/* Held for the first painted frame only. Content that was already on screen
   when the document opened must not replay its entrance - on a refresh that
   restores you part-way down the page, it would animate the thing you are
   already looking at. */
.reveal-instant .reveal {
  transition: none;
}
/* No JS, no problem — content must never be stuck invisible, and
   interactive-only affordances disappear cleanly. */
html:not(.js) .reveal {
  opacity: 1;
  transform: none;
}
html:not(.js) .pick,
html:not(.js) .orderbar,
html:not(.js) .totop,
html:not(.js) .js-only {
  display: none;
}

/* ------------------------------------------------------------
   RESPONSIVE
   ------------------------------------------------------------ */
/* The floating bar must never permanently hide the footer's last line. */
body.has-bar .foot {
  padding-bottom: 140px;
}

/* Fingers need more than mouse pointers do. */
@media (pointer: coarse) {
  .qty-btn {
    width: 42px;
    height: 42px;
  }
  .pick-add {
    padding: 14px 22px;
  }
  .foot li a {
    padding-block: 6px;
  }
}

@media (max-width: 1080px) {
  .caps,
  .tech {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: clamp(28px, 4vw, 44px);
  }
  .step {
    grid-row: auto;
    grid-template-rows: none;
  }
  .oem-flow {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .foot-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Three ~200px columns is too cramped to read — the Premium trio
   stacks well before the phone breakpoint. */
@media (max-width: 760px) {
  .variants:not(.two) {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 900px) {
  .nav {
    display: none;
  }
  .hdr-cta .btn span {
    display: none;
  }
  .hdr-cta .btn {
    padding: 11px 13px;
  }
  .iconbtn {
    display: grid;
  }
  /* No min-height:auto here any more. The hero is a full-bleed layered
     stage now, so letting it size to its text meant a webfont swap changed
     its height and shoved the ticker below it. Holding the viewport keeps
     everything under the hero still while the page loads. */
  .hero-copy {
    max-width: 640px;
  }
  .product-head,
  .iface,
  .contact-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .iface.flip > .iface-media {
    order: 0;
  }
  .chapter {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(20px, 4vw, 30px);
  }
  .rail {
    position: static;
    flex-direction: row;
    align-items: center;
    gap: 14px;
  }
  .rail i {
    margin: 0;
    flex: 1;
    max-width: 90px;
  }
  /* One column, stacked. The two-column form spent 56px of a 375px screen
     on an empty gutter and squeezed the paragraph into 279px; here the
     numeral sits over the heading and the prose gets the full measure. */
  .listing li {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }
  .listing .n,
  .listing h3,
  .listing p {
    grid-column: 1;
  }

  /* The comparison table used to be a 520px-wide horizontal scroller inside
     a 335px column, so the entire Premium IoT column sat off-screen with
     nothing to suggest it was there. Narrowing the value columns lets the
     whole table fit, which beats any amount of swipe affordance. */
  .cmp-wrap {
    overflow-x: visible;
  }
  table.cmp {
    min-width: 0;
    font-size: 13px;
  }
  table.cmp th,
  table.cmp td {
    padding: 13px 8px;
  }
  table.cmp td:not(:first-child),
  table.cmp th:not(:first-child) {
    width: 74px;
  }
  table.cmp thead th {
    font-size: 10px;
    letter-spacing: 0.1em;
  }
}

@media (max-width: 640px) {
  .hero {
    min-height: 100vh;
    min-height: 100svh;
    align-items: start;
    /* Same reason as the base rule: the kicker that used to hold the
       heading clear of the header bar is gone, so the padding has to. */
    padding-block: 114px 24px;
  }
  /* Retune the shared scale once; both beats follow. */
  .hero {
    --hero-type: clamp(28px, 7.6vw, 38px);
  }
  .hero-hook {
    max-width: 18ch;
    margin-bottom: 16px;
  }
  .hero h1 {
    line-height: 1.02;
  }
  .hero-lead {
    max-width: 35ch;
    margin-top: 18px;
    font-size: 15.5px;
    line-height: 1.55;
  }
  /* This is the one line of hero copy that lands where the doorway comes out
     of the scrim: measured, the backdrop beneath it is 20x brighter than
     under any other element, and swings 30x across the label's own width.
     A worst-pixel contrast ratio still passes there, but text crossing a
     light boundary is hard to read whatever the ratio says - so on a phone
     it takes the full text colour instead of the muted one. */
  .hero-save span {
    color: var(--text);
    font-weight: 600;
  }
  .hero .acts {
    flex-wrap: nowrap;
    gap: 8px;
    margin-top: 22px;
  }
  .hero .btn {
    padding: 12px 15px;
    font-size: 13px;
    white-space: nowrap;
  }
  .hero .btn svg {
    width: 16px;
    height: 16px;
  }
  .caps,
  .tech,
  .steps,
  .benefits,
  .oem-flow {
    grid-template-columns: minmax(0, 1fr);
  }
  /* label above the marks once the row is too narrow to sit beside them */
  .cred-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }
  .variants:not(.two),
  .variants.two {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(82%, 1fr);
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    border-block: 0;
    padding-bottom: 12px;
  }
  .variant {
    margin: 0;
    border: 1px solid var(--hair);
    border-radius: var(--r);
    scroll-snap-align: start;
  }
  .variants::-webkit-scrollbar {
    display: none;
  }
  .foot-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .plate img {
    max-height: 58vh;
  }
}

@media (max-width: 360px) {
  /* The tiniest phones are the one place content genuinely can outgrow
     the viewport, and min-height never caps - so this simply lets it
     grow rather than releasing the floor. */
  .hero {
    min-height: 100svh;
  }
  .hero .acts {
    flex-wrap: wrap;
  }
  .hero .btn {
    width: 100%;
    justify-content: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  html,
  html.smooth-scroll {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal {
    opacity: 1;
    transform: none;
  }
}
