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

/* ------------------------------------------------------------
   FOLD - a disclosure for depth, not for anything load-bearing
   ------------------------------------------------------------ */
/* Used in four places: the two interface chapters inside the product
   panels, the technology grid and the OEM flow. Together they are worth
   about 3900px on a 375px screen, and all four are "tell me more"
   rather than "help me decide" - so on a PHONE they start closed and
   the buying path gets four screens shorter.

   It is a phone affordance and only a phone affordance. On desktop the
   summary renders as the plain .tag label that block always carried:
   no control, no handle, nothing to click, nothing to tab to. A collapse
   toggle on a screen with room to spare is a button that can only make
   the page worse, so it does not exist there.

   The label lives in the summary rather than being duplicated inside
   the body, which is what keeps desktop identical to what it was: the
   same tag, in the same place, above the same content. The `open`
   attribute is authored into the HTML, so with JS off, in print and to
   a crawler every fold is simply an open section. */
.fold {
  border-top: 1px solid var(--hair);
  margin-top: clamp(40px, 5vw, 72px);
}
.fold > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  /* Both are needed: Safari honours the -webkit- pseudo, everything else
     honours list-style. Without them the native triangle sits in front
     of the label. */
  list-style: none;
  /* Desktop: not a control. The pointer never changes, clicks do not
     land, and site.js takes it out of the tab order - so the only thing
     a visitor sees is the label. */
  cursor: default;
  pointer-events: none;
  padding-top: clamp(16px, 1.8vw, 22px);
}
.fold > summary::-webkit-details-marker {
  display: none;
}
.fold > summary > .tag {
  margin-bottom: 0;
}
/* The phone-only prompt. Hidden here; everything about it is in the
   phone block further down. */
.fold-more {
  display: none;
}
/* The fold owns the rule and the space above its content, so the
   content must not also bring its own. */
.fold > :not(summary) {
  margin-top: 0;
}
.fold > .iface {
  border-top: 0;
  padding-top: clamp(14px, 1.6vw, 22px);
}
/* The grid draws its own top rule, so the fold's would be a second one
   directly under the label. */
.fold > .tech {
  border-top: 0;
}

@media (max-width: 640px) {
  /* Phone: now it IS a control, and it says so. A ghost pill on the
     site's own button ramp, with a chevron that turns over - rather
     than a bare icon a visitor has to interpret. */
  .fold > summary {
    cursor: pointer;
    pointer-events: auto;
    padding: 14px 0;
    min-height: 44px;
    box-sizing: border-box;
  }
  .fold-more {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    flex: none;
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--text);
    padding: 9px 15px;
    min-height: 38px;
    box-sizing: border-box;
    border-radius: 999px;
    white-space: nowrap;
    box-shadow: inset 0 0 0 1px var(--hair-strong);
    transition:
      color var(--mid) var(--ease),
      box-shadow var(--mid) var(--ease),
      background var(--mid) var(--ease);
  }
  .fold-more::before {
    content: "See more";
  }
  .fold[open] .fold-more::before {
    content: "Hide";
  }
  /* Chevron from two borders rather than a sixth sprite symbol: down
     when there is more to see, up when there is not. */
  .fold-more::after {
    content: "";
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-right: 2px solid var(--amber);
    border-bottom: 2px solid var(--amber);
    transform: rotate(45deg);
    transition: transform var(--mid) var(--ease);
  }
  .fold[open] .fold-more::after {
    margin-top: 2px;
    transform: rotate(-135deg);
  }
  .fold > summary:active .fold-more {
    background: rgba(248, 162, 42, 0.12);
    box-shadow: inset 0 0 0 1px rgba(248, 162, 42, 0.55);
  }
}
/* Inside the OEM panel the fold IS the first row, so it drops the rule
   that would otherwise land immediately under the panel's top line. */
.oem > .fold {
  border-top: 0;
  margin-top: 0;
}
/* Opening animates, loading does not: site.js adds this class on the
   click that opens a fold and removes it on the click that closes one,
   so the programmatic open it performs on every desktop load stays
   silent. */
.fold.just-opened > :not(summary) {
  animation: foldIn var(--slow) var(--ease) both;
}
@keyframes foldIn {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ------------------------------------------------------------
   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;
}
/* nowrap is load-bearing. The nav is a shrinkable flex item, and the only
   links that CAN absorb that shrink are the ones containing a space: a
   two-word link collapses to its longest word and goes to two lines while
   every single-word link beside it keeps its size. A nav link should
   never wrap; the breakpoint above is what handles running out of room. */
.nav a {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--muted);
  position: relative;
  padding-block: 5px;
  white-space: nowrap;
  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 hold ----------
   The hero keeps its place for a stretch so that beat two LANDS rather
   than animating on its way off the top of the screen. Two things share
   that stretch: the run-up before the cut fires (220px, set in site.js)
   and the dwell after it, which is what is left.

   90svh, so on an 812px phone that is 731px: 220 of run-up and roughly
   510 of dwell. Both halves matter - too little run-up and the cut feels
   like something you brushed against, too little dwell and the hero is
   released before the phone has finished arriving.

   That second number is sized against the ANIMATION, which is the one
   place a distance and a duration have to be reconciled. The cascade
   lands at 1.5s, so 510px of dwell keeps the hero still for the whole of
   it at anything under about 340px/s - a scroll-and-look, which is what
   people actually do when something changes in front of them. A hard
   continuous flick still outruns it, and always will: no fixed distance
   can hold a moving target for a fixed time. That visitor is leaving
   anyway.

   Not to be confused with the 0.85-screen version this replaced, which
   SCRUBBED the transition across the whole distance. Here the cut is a
   self-contained animation; the distance only decides when it starts and
   how long the result sits there.

   .hold is added by site.js, so with JS off or under reduced motion the
   extra height does not exist and the hero is the plain section it has
   always been. */
.hero-hold {
  position: relative;
}
.hero-hold.hold {
  --hold: 90svh;
  height: calc(100svh + var(--hold));
}
.hero-hold.hold .hero {
  position: sticky;
  top: 0;
}

/* ---------- 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 holds beat one for as long as the visitor leaves it there.
   .run is added once the page has actually moved about 120px, and
   carries everything to the end state on staggered delays; a timer then
   strips both classes so the hero rests on plain, visible CSS.

   DISCRETE, not scrubbed. A previous version tied every value to the
   scroll offset so the cut could be played by hand - which sounds better
   than it looks, because it can be stopped anywhere, and a hero showing
   both beats at half opacity does not read as a transition, it reads as
   a rendering fault. Once it starts here, it finishes.

   Two transitions rather than one animation on the cold layer. The old
   version needed a keyframe because a transition cannot take opacity
   0 -> 1 -> 0 along a single timeline; with .run now landing whenever the
   visitor gets there, the two halves are separate state changes and a
   transition each is simpler. */
/* Three full-bleed layers change opacity at once here, one of them a
   2560px image that also scales and one a 1400x1800 PNG. Without a
   compositor hint the browser repaints all of it on the main thread
   every frame, which on a phone is most of what "glitchy" was.

   Scoped to .play on purpose: promoted layers cost memory, so they exist
   only while the sequence is live and are released the moment the hero
   rests. */
.hero.play .hero-problem,
.hero.play .hero-solution img,
.hero.play .hero-hand {
  will-change: opacity, transform;
}

/* BEAT ONE, and also the state the hero returns to when you scroll back
   up. Every rule carries its own transition, which is what makes the
   reverse animate instead of snapping: going forward the .run rules win
   on specificity and their staggered timing applies, and going back
   these do - shorter, and in the opposite order, so the answer clears
   out before the question comes home. */
.hero.play .hero-problem {
  opacity: 1;
  transition: opacity 0.5s var(--ease) 0.1s;
}
.hero.play .hero-solution img {
  opacity: 0;
  transform: scale(1.05);
  transition:
    opacity 0.5s var(--ease) 0.1s,
    transform 0.6s var(--ease) 0.1s;
}
.hero.play .hero-hand {
  opacity: 0;
  transform: translateY(14%) scale(0.97);
  transition:
    opacity 0.32s var(--ease),
    transform 0.4s var(--ease);
}
/* Turned ON here, because the question rests HIDDEN - see the resting
   state above, where beat two is the default. */
.hero.play .hero-hook {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition:
    opacity 0.45s var(--ease) 0.18s,
    transform 0.45s var(--ease) 0.18s,
    visibility 0s linear 0.18s;
}
.hero.play .hero-answer {
  visibility: hidden;
  transition: visibility 0s linear 0.3s;
}
.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);
  transition:
    opacity 0.3s var(--ease),
    transform 0.3s var(--ease);
}

/* ...and the travel to the end state, which is where the timing lives.
   The whole cascade lands by 1.5s. The visitor is mid-scroll when it
   starts, so a cascade tuned for a hero sitting still would spend most
   of itself below the fold. */
.hero.play.run .hero-problem {
  opacity: 0;
  transition: opacity 0.75s var(--ease) 0.12s;
}
.hero.play.run .hero-solution img {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.8s var(--ease) 0.12s,
    transform 1.1s var(--ease) 0.12s;
}
/* 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.42s var(--ease),
    transform 0.42s var(--ease),
    visibility 0s linear 0.42s;
}
.hero.play.run .hero-answer {
  visibility: visible;
  transition: visibility 0s linear 0.44s;
}
/* 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.5s var(--ease) 0.44s,
    transform 0.5s var(--ease) 0.44s;
}
.hero.play.run .hero-answer h1 {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s var(--ease) 0.52s,
    transform 0.5s var(--ease) 0.52s;
}
.hero.play.run .hero-answer .hero-lead {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s var(--ease) 0.62s,
    transform 0.5s var(--ease) 0.62s;
}
.hero.play.run .hero-answer .hero-save {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s var(--ease) 0.7s,
    transform 0.5s var(--ease) 0.7s;
}
.hero.play.run .hero-answer .acts {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s var(--ease) 0.78s,
    transform 0.5s var(--ease) 0.78s;
}
/* Last in, and the longest travel: 0.75s + 0.75s lands at 1.5s, inside the
   1.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.6s var(--ease) 0.75s,
    transform 0.75s var(--ease) 0.75s;
}

/* ---------- the scroll cue ----------
   Only exists while beat one is on screen, which is exactly the window
   in which the page looks like it has nothing on it. It arrives a beat
   after the question so the two do not land together, and it leaves the
   moment the scroll it was asking for happens.

   A hairline with a travelling dot rather than a bouncing chevron: the
   site is built out of hairlines, and the dot moving DOWN the line says
   which way to go without borrowing an icon from somewhere else. */
.hero-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(18px, 3.4vh, 34px);
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.5s var(--ease),
    visibility 0.5s var(--ease);
}
.hero-cue span {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted);
}
.hero-cue i {
  position: relative;
  display: block;
  width: 1px;
  height: 38px;
  overflow: hidden;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.22),
    rgba(255, 255, 255, 0)
  );
}
/* transform, NOT top. This loops for as long as beat one is on screen,
   and `top` is a layout property - animating it was forcing a reflow on
   every frame the hero was sitting there waiting, which is a strange
   thing to be paying for while nothing is supposed to be happening.
   translateY composites and costs nothing. */
.hero-cue i::after {
  content: "";
  position: absolute;
  top: 0;
  left: -1.5px;
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: var(--amber);
  box-shadow: 0 0 8px 1px rgba(248, 162, 42, 0.7);
  will-change: transform, opacity;
  animation: cueFall 1.9s var(--ease) infinite;
}
@keyframes cueFall {
  0% {
    transform: translateY(-4px);
    opacity: 0;
  }
  22% {
    opacity: 1;
  }
  74% {
    opacity: 1;
  }
  100% {
    transform: translateY(38px);
    opacity: 0;
  }
}
/* The cue goes out with the question, not after it: by the time the cut
   fires, the instruction has already been obeyed and leaving it up would
   be asking twice. With JS off, under reduced motion, and once the hero
   has landed there is nothing left to prompt, so it stays hidden. */
.hero.play .hero-cue {
  opacity: 1;
  visibility: visible;
  transition-delay: 1.1s;
}
.hero.play.run .hero-cue {
  opacity: 0;
  visibility: hidden;
  transition-duration: 0.3s;
  transition-delay: 0s;
}

.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: how many times this one was won, said without a
   sentence. The number comes from data-count in the markup, so the count
   lives with the credential rather than in the stylesheet.

   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 model 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 model's 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;
}

/* Model jump chips in the products intro — colour-coded to match the
   warm/cool identity each model carries below. */
.model-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);
}
.model-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 — two states, present in both
   ------------------------------------------------------------ */
/* EMPTY it repeats the hero's pair - Buy now, then Compare models, same
   order and same treatment - so meeting the bar half way down the page
   offers exactly what the top of the page offered. FILLED it is the
   order summary it has always been. It used to exist only in the second
   state, which meant that on a phone there was nothing on screen you
   could buy with between the end of the hero and the catalogue ten
   screens later.

   Visibility is site.js's: past the hero, and — for the empty state
   only — on the widths where the header nav is hidden, since above
   900px the catalogue is already one click away up there. */
.orderbar {
  position: fixed;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  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);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 14px);
  transition:
    opacity var(--mid) var(--ease),
    transform var(--mid) var(--ease),
    visibility var(--mid) var(--ease);
}
.orderbar.show {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}
/* Nothing sits to the left of the buttons in the empty state, so the
   pill loses the text inset that would otherwise read as a gap. */
.orderbar:not(.filled) {
  gap: 8px;
  padding-left: 10px;
}
.orderbar:not(.filled) .ob-sum,
.orderbar:not(.filled) .ob-clear,
.orderbar:not(.filled) .ob-buy,
.orderbar.filled .ob-go,
.orderbar.filled .ob-cmp {
  display: none;
}
/* Both empty-state buttons are the site's own .btn, so they inherit the
   hero's ramp exactly. All they need here is to refuse to wrap. */
.ob-go,
.ob-cmp {
  white-space: nowrap;
}
/* The count doubles as the disclosure for the panel above it, so it is a
   button - but it is styled to stay the quiet label it always was. The
   only thing announcing it as a control is the chevron. */
.ob-sum {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  color: var(--muted);
  white-space: nowrap;
  padding: 8px 2px 8px 0;
  transition: color var(--mid) var(--ease);
}
.ob-sum b {
  color: var(--amber);
  font-size: 15px;
}
.ob-sum:hover {
  color: var(--text);
}
/* Points UP, because that is where the panel comes from, and turns over
   when it is open. Two borders rather than another sprite symbol - the
   same chevron the folds use. */
.ob-sum::after {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 3px;
  border-right: 2px solid var(--amber);
  border-bottom: 2px solid var(--amber);
  transform: rotate(-135deg);
  transition: transform var(--mid) var(--ease);
}
.orderbar.panel-open .ob-sum::after {
  margin-top: -3px;
  transform: rotate(45deg);
}

/* ---- the order panel ----
   Same pill vocabulary as the bar it lifts out of: same tinted glass,
   same ring, same shadow, plus the gradient hairline the site's raised
   panels carry along their top edge. It is absolutely positioned, so it
   never changes the bar's own layout, and it is capped and scrollable so
   that even a five-line order cannot take over the screen. */
.ob-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 10px);
  overflow: hidden;
  padding: 15px 6px 8px;
  border-radius: 22px;
  background: rgba(26, 18, 13, 0.94);
  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);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.98);
  transform-origin: 50% 100%;
  transition:
    opacity var(--mid) var(--ease),
    transform var(--mid) var(--ease),
    visibility var(--mid) var(--ease);
}
.ob-panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--grad-line);
}
.orderbar.panel-open .ob-panel {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.ob-panel-title {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
  padding: 0 14px 11px;
}
.ob-list {
  list-style: none;
  /* Five units is the most there can be. Capped anyway, because a panel
     that grows past half the viewport stops being furniture. */
  max-height: min(42vh, 288px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The remove button sets the row height, so the row itself carries almost
   no padding: at five units the panel is already 35% of a 812px screen
   and every spare pixel per row is five pixels of the viewport. */
.ob-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 12px;
  padding: 2px 6px 2px 14px;
}
.ob-list li + li {
  box-shadow: inset 0 1px 0 var(--hair);
}
.ob-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ob-qty {
  font-size: 13px;
  font-weight: 700;
  color: var(--amber);
  font-variant-numeric: tabular-nums;
}
.ob-drop {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: var(--dim);
  transition:
    color var(--mid) var(--ease),
    background var(--mid) var(--ease);
}
.ob-drop svg {
  width: 15px;
  height: 15px;
}
.ob-drop:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}
/* Nothing to disclose when the bar is empty. */
.orderbar:not(.filled) .ob-panel {
  display: none;
}
.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;
    justify-content: space-between;
    gap: 10px;
    padding-left: 16px;
    transform: translateY(14px);
  }
  .orderbar.show {
    transform: translateY(0);
  }
  /* Edge to edge. justify-content has nothing to work with in the empty
     state - the summary is hidden, so .ob-acts is the pill's only child -
     which left both buttons bunched at the left with 92px of dead pill
     beside them. The actions take the full width instead and the primary
     absorbs the slack, which is also the bigger thumb target. */
  .orderbar:not(.filled) {
    padding-left: 10px;
  }
  /* The actions take the pill's full width and the primary absorbs the
     slack, so Buy now is both first and the bigger thumb target while
     Compare models keeps its natural size on the right. */
  .orderbar:not(.filled) .ob-acts,
  .orderbar:not(.filled) .ob-go {
    flex: 1;
  }
  .orderbar:not(.filled) .ob-go,
  .orderbar:not(.filled) .ob-cmp {
    justify-content: center;
  }
  /* "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;
  }
}
/* The smallest phones: the pair still fits, but only just, so the pill
   gives back the padding it can spare. */
@media (max-width: 359px) {
  .orderbar:not(.filled) .btn {
    padding: 11px 12px;
  }
}

/* ------------------------------------------------------------
   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.
   `has-bar` means the bar is actually on screen, not that something is
   selected — which matters now that it is present in both states. */
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;
  }
  .ob-drop {
    width: 44px;
    height: 44px;
  }
  .ob-sum {
    padding-block: 11px;
  }
  .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);
  }
}

/* The nav hands over to the drawer at 1120px, not 900px. Measured: nine
   links plus the brand and the WhatsApp button need about 1100px, so
   between 900 and 1100 the nav was being squeezed - links compressed
   below their natural width and, for any link containing a space, wrapped
   onto a second line. The drawer carries every destination anyway, so
   handing over before that point costs nothing. The WhatsApp button keeps
   its label down to 900px, where there is still room for it. */
@media (max-width: 1120px) {
  .nav {
    display: none;
  }
  .iconbtn {
    display: grid;
  }
}

@media (max-width: 900px) {
  .hdr-cta .btn span {
    display: none;
  }
  .hdr-cta .btn {
    padding: 11px 13px;
  }
  /* 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;
  }
  .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;
  }
}
