/* ==========================================================================
   SAVVYTECH — campaign landing page
   Layout language referenced from kyndryl.com/in/en:
     floating glass pill nav · inset rounded content frame · bottom-anchored
     hero · eyebrow + accent rule · two-tone headings · numbered colour bands
     · carousel progress rails
   Colours sampled from the SAVVYTECH logo artwork.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand — sampled directly from assets/img/savvytech-logo.png.
     These are ACCENT colours. The reference page is ~75% white/neutral with
     the brand colour on only ~4% of the surface (eyebrow rules, one heading
     line, active states, one full-bleed band). Keep it that way: do not
     reach for --blue for section backgrounds. */
  --navy:        #03104d;   /* brand navy: "SAVVY" wordmark, dark surfaces, primary CTA.
                              Was #01104d, sampled from the logo artwork; set to the
                              specified #03104d — a 2-point shift, invisible, and it
                              keeps one navy in the palette instead of two. */
  --blue:        #0061f9;   /* "TECH" gradient, dark end */
  --blue-bright: #0094fc;   /* "TECH" gradient, light end + tagline dashes */
  --cyan:        #4ad4ff;

  /* Neutrals do the structural work. Warm greys, matching the reference —
     a cool blue-grey here is what made the whole page read as "blue". */
  --ink:      #1c1c1e;   /* headings */
  --ink-mid:  #3d3c3c;   /* body copy, per reference */
  --ink-soft: #6e6d6b;

  --paper:  #ffffff;
  --tint:   #f2f1ee;     /* reference light band, rgb(242,241,238) */
  --tint-2: #f9f9f9;
  --line:   #e4e3df;     /* warm hairline */
  --frame-bg: #0f0f11;   /* neutral near-black gutter around the frame */
  --dark:     #17171a;   /* neutral dark surface: drawer, footer, hero fallback */

  --ok: #17b978;
  --warn: #ff9f1c;
  --alert: #ff5470;

  --font-display: 'Sora', 'Segoe UI', Arial, sans-serif;
  --font-body: 'Inter', 'Segoe UI', Arial, sans-serif;

  /* Kyndryl-ish scale: very large, very light */
  --fs-hero:   clamp(2.375rem, 5.6vw, 4.75rem);
  --fs-mega:   clamp(2.5rem, 7vw, 6rem);
  --fs-h2:     clamp(1.875rem, 3.6vw, 3.25rem);
  --fs-h3:     clamp(1.25rem, 1.9vw, 1.75rem);
  --fs-lead:   clamp(1rem, 1.25vw, 1.25rem);
  --fs-body:   1rem;
  --fs-sm:     0.9375rem;
  --fs-xs:     0.8125rem;
  --fs-eyebrow: 0.6875rem;

  --frame-inset: 12px;
  --frame-radius: 18px;
  --container: 1440px;
  --gutter: 20px;
  --nav-h: 60px;

  --shadow-pill: 0 2px 10px rgba(1, 16, 77, .08), 0 10px 34px rgba(1, 16, 77, .10);
  --shadow-card: 0 2px 8px rgba(1, 16, 77, .05), 0 18px 48px rgba(1, 16, 77, .08);

  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  /* overshoots ~9% before settling — for something travelling a distance, so
     it arrives with weight instead of just stopping */
  --ease-spring: cubic-bezier(.34, 1.42, .38, 1);
}

@media (min-width: 768px)  { :root { --gutter: 34px; --frame-inset: 14px; --nav-h: 64px; } }
@media (min-width: 1200px) { :root { --gutter: 48px; --frame-inset: 16px; --nav-h: 68px; } }

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + var(--frame-inset) * 2 + 16px);
}

body {
  margin: 0;
  /* neutral near-black gutter, as in the reference */
  background: var(--frame-bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}
body.nav-open { overflow: hidden; }

img, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: -0.032em;
  text-wrap: balance;
}
h3, h4 { font-weight: 500; line-height: 1.22; letter-spacing: -0.02em; }
p { margin: 0; }

::selection { background: var(--blue); color: #fff; }
:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 3px; border-radius: 3px; }

.skip-link {
  position: absolute; left: 50%; top: 10px; transform: translate(-50%, -200%);
  z-index: 200; background: #fff; color: var(--navy);
  padding: 10px 18px; border-radius: 999px; font-weight: 600; font-size: var(--fs-sm);
  box-shadow: var(--shadow-pill);
  transition: transform .2s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   3. The inset rounded frame (Kyndryl's signature container)
   -------------------------------------------------------------------------- */
.frame {
  margin: var(--frame-inset);
  border-radius: var(--frame-radius);
  /* clip, not hidden: `hidden` makes this a scroll container, which becomes
     the scrollport for every position:sticky descendant and silently breaks
     page-level sticky (the plan matrix header). `clip` clips the same way,
     respects border-radius, and creates no scroll container. */
  overflow: clip;
  background: var(--paper);
  isolation: isolate;
}

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: 980px; }

.band { padding-block: clamp(3.5rem, 7vw, 7rem); position: relative; }
.band--tight { padding-block: clamp(2.5rem, 4.5vw, 4rem); }
.band--paper { background: var(--paper); }
.band--tint  { background: var(--tint); }
.band--tint2 { background: var(--tint-2); }
/* First band on a sub-page (pricing.html). index.html opens with the hero,
   whose own content is pushed clear of the fixed nav; a page that opens
   straight into a band has to make that room itself. */
.band--page-top { padding-top: calc(var(--nav-h) + var(--frame-inset) * 2 + clamp(2rem, 4vw, 3.5rem)); }
/* var(--navy), not var(--dark). The class was always called `navy` but painted
   the NEUTRAL dark (#17171a) — this finally makes the name true.
   `--dark` is still what the mobile drawer uses, and it stays neutral: only the
   surfaces asked for went navy (this band, the footer, the primary CTA). */
.band--navy  { background: var(--navy); color: #fff; }
.band--navy h2, .band--navy h3 { color: #fff; }

/* --------------------------------------------------------------------------
   4. Type helpers
   -------------------------------------------------------------------------- */
/* eyebrow + short accent underline, straight from the reference */
.eyebrow {
  display: block;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-mid);
}
.eyebrow::after {
  content: ""; display: block;
  width: 22px; height: 3px; margin-top: .5rem;
  border-radius: 2px;
  background: var(--blue);
}
.band--navy .eyebrow { color: rgba(226, 236, 255, .72); }
/* the rule follows the accent, which goes cyan on a dark surface — brand blue
   on #17171a is too dark to register as an accent at 3px */
.band--navy .eyebrow::after { background: var(--cyan); }

/* two-tone stacked heading: one line ink, one line brand */
.two-tone { font-size: var(--fs-h2); }
.two-tone span { display: block; }
.two-tone .accent { color: var(--blue); }
.band--navy .two-tone .accent { color: var(--cyan); }

.lead { font-size: var(--fs-lead); line-height: 1.55; color: var(--ink-mid); max-width: 62ch; }
.band--navy .lead { color: rgba(226, 236, 255, .78); }
.muted { color: var(--ink-soft); }

.band-head { max-width: 820px; }
.band-head .eyebrow { margin-bottom: 1.25rem; }
.band-head :is(h1, h2) + .lead { margin-top: 1.375rem; }   /* h1 for contact.html */

/* --------------------------------------------------------------------------
   5. Buttons — thin border, small radius (reference style)
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55em;
  font-family: var(--font-body);
  font-size: var(--fs-sm); font-weight: 600;
  padding: .8125rem 1.375rem;
  border: 1px solid var(--ink);
  border-radius: 4px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color .25s var(--ease), color .25s var(--ease),
              border-color .25s var(--ease), transform .25s var(--ease);
}
.btn:hover { background: var(--ink); color: #fff; }
.btn:active { transform: translateY(1px); }

/* A disabled button must LOOK disabled. `contactForms()` disables the submit
   while a POST is in flight to stop a double send, and without this the
   button was still fully brand-navy with a live hover state — it read as
   "click me again", which is the one thing it must not say. */
.btn:disabled,
.btn[aria-disabled="true"] { opacity: .55; cursor: default; pointer-events: none; }
.btn:disabled:active { transform: none; }

.btn--solid { background: var(--navy); border-color: var(--navy); color: #fff; }
.btn--solid:hover { background: var(--blue); border-color: var(--blue); }

.btn--brand { background: var(--navy); border-color: var(--navy); color: #fff; }
.btn--brand:hover { background: var(--blue); border-color: var(--blue); color: #fff; }

.btn--onDark { border-color: rgba(255, 255, 255, .5); color: #fff; }
.btn--onDark:hover { background: #fff; color: var(--navy); border-color: #fff; }

.btn--lg { padding: 1rem 1.75rem; font-size: 1rem; }
.btn-row { display: flex; flex-wrap: wrap; gap: .75rem; }

/* chevron link, e.g. "Read the eBook >" */
.chev {
  display: inline-flex; align-items: center; gap: .45em;
  font-size: var(--fs-sm); font-weight: 500; color: var(--ink);
}
.chev svg { width: 15px; height: 15px; transition: transform .3s var(--ease); }
.chev:hover { color: var(--blue); }
.chev:hover svg { transform: translateX(4px); }
.band--navy .chev { color: #fff; }
.band--navy .chev:hover { color: var(--cyan); }

/* --------------------------------------------------------------------------
   6. Floating glass pill nav
   -------------------------------------------------------------------------- */
.nav-wrap {
  position: fixed;
  top: var(--frame-inset); left: var(--frame-inset); right: var(--frame-inset);
  z-index: 100;
  pointer-events: none;
}
.nav {
  pointer-events: auto;
  height: var(--nav-h);
  display: flex; align-items: center; gap: 1rem;
  padding-inline: clamp(1rem, 2vw, 1.75rem);
  border-radius: 14px;
  background: rgba(255, 255, 255, .72);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  box-shadow: var(--shadow-pill);
  transition: background-color .35s var(--ease), box-shadow .35s var(--ease);
}
/* over the dark hero the pill goes dark-glass */
.nav-wrap[data-over="true"] .nav {
  background: rgba(1, 10, 48, .34);
  box-shadow: none;
}
.nav-wrap[data-over="true"] .nav__link,
.nav-wrap[data-over="true"] .icon-btn { color: #fff; }
.nav-wrap[data-over="true"] .icon-btn { border-color: rgba(255, 255, 255, .38); }
.nav-wrap[data-over="true"] .brand__crop { filter: brightness(0) invert(1); }

/* --- logo lockup ---
   The supplied PNG is 2172x724, but the artwork sits in two bands inside it
   and 69% of the file is transparent padding:
     wordmark "SAVVYTECH"  x 97   y 241  1979 x 169   (aspect 11.7101)
     tagline line          x 98   y 455  1977 x  33
   So the box is sized to the *artwork* and the image is scaled and offset
   inside it. Default crop is the wordmark only, which lets SAVVYTECH render
   at full size in a short nav bar; the tagline is set as real text elsewhere
   on the page so it stays legible.
   Use .brand__crop--full for the complete lockup (needs ~60px of height
   before the tagline is readable).
   If the logo is ever re-exported tightly cropped, drop the img offsets and
   let the image fill the box. */
.brand { display: inline-flex; align-items: center; flex: none; }
.brand__crop {
  --wordmark-h: 17px;
  display: block; position: relative; overflow: hidden;
  height: var(--wordmark-h);
  width: calc(var(--wordmark-h) * 11.7101);   /* 1979 / 169 */
  transition: filter .35s var(--ease);
}
.brand__crop img {
  position: absolute;
  width:  calc(100% * 2172 / 1979);
  height: calc(100% * 724 / 169);
  left: calc(-100% * 97 / 1979);
  top:  calc(-100% * 241 / 169);
  max-width: none;
}
/* full lockup, wordmark + tagline */
.brand__crop--full { width: calc(var(--wordmark-h) * 8.0121); }   /* 1979 / 247 */
.brand__crop--full img {
  height: calc(100% * 724 / 247);
  top:  calc(-100% * 241 / 247);
}
@media (min-width: 768px)  { .brand__crop { --wordmark-h: 19px; } }
@media (min-width: 1200px) { .brand__crop { --wordmark-h: 21px; } }

/* --- links ---
   Segmented pill row, matching the reference's link-navigation-bar: inactive
   links are plain text in an invisible pill, the section you are currently
   scrolled into is a filled dark pill (theirs: rgb(22,22,22), radius 34px,
   padding 8px 16px). Padding and radius sit on EVERY link, not only
   .is-active, so nothing reflows as the active item changes while scrolling. */
/* --- the active marker is a sliding underline, not a filled pill ---------
   One shared rule under the whole strip, positioned from --x / --w which
   navUnderline() writes from the live geometry of whichever link is active —
   or hovered, which is what makes it slide rather than blink.

   Square ends on purpose: `border-radius: 0` against a nav full of 999px
   pills is the whole point of the treatment, and 3px is thick enough to read
   as a deliberate rule rather than a text-decoration underline. */
.nav__links {
  display: none; position: relative;
  margin-left: auto; align-items: center; gap: .1875rem;
  /* Symmetric, NOT padding-bottom alone. Bottom-only padding left the strip
     centred in the nav but pushed the labels 5px above the Log in / Sign up
     buttons — measured. Equal padding keeps the labels on the buttons' centre
     line and still leaves the rule somewhere to sit. */
  padding-block: 10px;
}
.nav__link {
  font-size: var(--fs-sm); font-weight: 500; color: var(--ink);
  padding: .4375rem .8125rem;
  border-radius: 999px;
  white-space: nowrap;
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
.nav__link:hover { color: var(--navy); background: rgba(0, 0, 0, .04); }
/* the label itself only gets weight — the rule below carries the state */
.nav__link.is-active { color: var(--navy); font-weight: 600; background: transparent; }

.nav__links::after {
  content: ""; position: absolute; z-index: 0;
  left: 0; bottom: 0;
  width: var(--nu-w, 0); height: 3px;
  border-radius: 0;
  background: var(--navy);
  transform: translateX(var(--nu-x, 0)) scaleX(var(--nu-s, 1));
  transform-origin: left;
  /* travel is sprung, the draw-in and the fade are not — a bouncing opacity
     reads as a flicker */
  transition: transform .45s var(--ease-spring), width .45s var(--ease-spring),
              opacity .3s var(--ease);
  opacity: 0;
}
/* only shown once JS has measured something real; before that there is no
   marker at all rather than one parked at zero */
.nav__links[data-nu]::after { opacity: 1; }
/* no active link and nothing hovered — index.html at the top of the page */
.nav__links[data-nu="none"]::after { opacity: 0; }

.nav-wrap[data-over="true"] .nav__link:hover { color: #fff; background: rgba(255, 255, 255, .1); }
.nav-wrap[data-over="true"] .nav__link.is-active { color: #fff; background: transparent; }
/* navy on the dark hero would disappear */
.nav-wrap[data-over="true"] .nav__links::after { background: var(--cyan); }

/* The slide IS the effect, so with motion off the rule simply appears under
   the active item. */
@media (prefers-reduced-motion: reduce) {
  .nav__links::after { transition: opacity .3s var(--ease); }
}

.nav__actions { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
@media (min-width: 1100px) {
  .nav__links { display: flex; }
  .nav__actions { margin-left: 1.5rem; }
}

/* circular icon buttons, as on the reference */
.icon-btn {
  width: 38px; height: 38px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color .25s var(--ease), border-color .25s var(--ease), color .25s var(--ease);
}
.icon-btn svg { width: 17px; height: 17px; }
.icon-btn:hover { border-color: var(--blue); color: var(--blue); }
.nav-wrap[data-over="true"] .icon-btn:hover { background: rgba(255, 255, 255, .18); border-color: #fff; color: #fff; }

/* Header actions: Log in (outline pill) + Sign up (solid pill).

   Modelled on the reference's own header button, which is NOT its page-button
   style: measured on kyndryl.com the nav CTA is a fully-rounded pill 44px tall,
   16px side padding, an 8px gap and a 20px white CIRCLE holding the arrow —
   while its in-page buttons are the thin-border, 4px-radius shape our `.btn`
   copies. Two different components from the same site, so `.nav__cta` is
   deliberately not a `.btn` variant.

   The type is ours, not theirs (15px/600 rather than 14px/500), so the header
   matches the rest of our buttons rather than importing a second type scale.

   Only one FILLED thing in the bar — see .nav__login below. */
/* Log in is an ACTION, not a destination, so it takes the button shape rather
   than the nav-link shape — as bare text it sat in the same visual class as
   "Vendors" and "FAQ" and read as one more menu item. Outline pill + solid
   pill is exactly the pairing the drawer already uses, so the two breakpoints
   now tell the same story. Still only one FILLED thing in the bar. */
.nav__login {
  display: none; align-items: center;
  height: 42px; padding: 0 18px;
  border: 1px solid var(--line); border-radius: 999px;
  background: transparent;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink);
  white-space: nowrap;
  transition: border-color .25s var(--ease), color .25s var(--ease);
}
/* same hover as .icon-btn — the nav already has one idiom for "outlined thing
   you can press", and this is another one */
.nav__login:hover { border-color: var(--blue); color: var(--blue); }

/* On hover the badge travels from the right end of the pill to the left end
   and the label slides across to make room for it. Three numbers drive the
   whole thing and they are declared here once so they cannot disagree:
   the badge is --cta-b wide and sits --cta-p from whichever end it is at, with
   --cta-p again as the breathing space between it and the label.

   The pill's own width does not change, at rest or on hover. Padding-right
   reserves the badge's slot (p + b + p) even though the badge is out of flow,
   and padding-left is one --cta-p; on hover those roles swap by moving the
   label, not by changing the box. */
.nav__cta {
  --cta-b: 26px;                                  /* badge diameter */
  --cta-p: 8px;                                   /* end inset and inner gap */
  --cta-slot: calc(var(--cta-b) + var(--cta-p));   /* what the badge occupies */

  position: relative;
  display: none; align-items: center;
  height: 42px;
  padding-left: 18px;
  padding-right: calc(var(--cta-slot) + var(--cta-p));
  border-radius: 999px;
  background: var(--navy); color: #fff;
  font-size: var(--fs-sm); font-weight: 600;
  white-space: nowrap;
  transition: background-color .25s var(--ease);
}
.nav__cta:hover { background: var(--blue); }

.nav__cta-t {
  position: relative;
  transition: transform .5s var(--ease-spring);
  /* the label follows the badge rather than leading it, so the badge reads as
     travelling past it instead of the two sliding in lockstep */
  transition-delay: 60ms;
}

/* the badge inverts against the pill, so it keeps working on both the light
   and the over-hero variant without a second rule for the arrow itself */
.nav__cta-ico {
  position: absolute; top: 50%; right: var(--cta-p);
  width: var(--cta-b); height: var(--cta-b);
  display: grid; place-items: center;
  border-radius: 50%;
  background: #fff; color: var(--navy);
  transform: translateY(-50%);
  /* `right` rather than a transform, because a transform percentage resolves
     against the badge's own 26px and the travel distance is the PILL's width
     minus the two slots — which plain CSS cannot express without either
     container queries (they would collapse this auto-width pill) or a
     JS-written custom property. One 26px out-of-flow box is nothing to lay
     out per frame, and this way the effect needs no JS at all. */
  transition: right .5s var(--ease-spring),
              background-color .25s var(--ease), color .25s var(--ease);
}
.nav__cta-ico svg {
  width: 14px; height: 14px;
  transition: transform .5s var(--ease-spring);
}

/* Keyboard users get the same thing — the pill is a link, so :focus-visible is
   the only way in without a pointer. */
.nav__cta:hover .nav__cta-ico,
.nav__cta:focus-visible .nav__cta-ico {
  right: calc(100% - var(--cta-slot));  /* left edge lands on --cta-p */
}
.nav__cta:hover .nav__cta-t,
.nav__cta:focus-visible .nav__cta-t {
  /* clears the badge's new slot: it now ends at p + b, and the label needs
     another p of air after that */
  transform: translateX(calc(var(--cta-slot) + var(--cta-p) - 18px));
}
/* a small lead into the direction of travel, so the arrow does not read as
   cargo being carried */
.nav__cta:hover .nav__cta-ico svg,
.nav__cta:focus-visible .nav__cta-ico svg { transform: translateX(-1.5px); }

/* The travel IS the effect, so with motion off it simply does not happen —
   the badge stays where it is and only the colours respond. */
@media (prefers-reduced-motion: reduce) {
  .nav__cta-t, .nav__cta-ico, .nav__cta-ico svg { transition: none; }
  .nav__cta:hover .nav__cta-ico,
  .nav__cta:focus-visible .nav__cta-ico { right: var(--cta-p); }
  .nav__cta:hover .nav__cta-t,
  .nav__cta:focus-visible .nav__cta-t { transform: none; }
  .nav__cta:hover .nav__cta-ico svg,
  .nav__cta:focus-visible .nav__cta-ico svg { transform: none; }
}

@media (min-width: 620px) {
  .nav__login, .nav__cta { display: inline-flex; }
}

/* over the dark hero: the pill flips to white-on-dark so it stays the single
   brightest thing in the bar */
.nav-wrap[data-over="true"] .nav__login { color: #fff; border-color: rgba(255, 255, 255, .38); }
.nav-wrap[data-over="true"] .nav__login:hover { color: var(--cyan); border-color: var(--cyan); }
.nav-wrap[data-over="true"] .nav__cta { background: #fff; color: var(--navy); }
.nav-wrap[data-over="true"] .nav__cta:hover { background: var(--cyan); color: var(--navy); }
.nav-wrap[data-over="true"] .nav__cta-ico { background: var(--navy); color: #fff; }

/* burger

   Was `display: grid`, inheriting `place-items: center` from .icon-btn. Grid
   auto-rows STRETCH by default, so the three bars were pushed to 12.8px apart
   and spanned y 5.5–32.5 of a 38px button instead of sitting in a compact
   12.5px stack — the icon read as three loose lines. It also broke the close
   state: the X translates each outer bar toward the middle, but the translate
   was the intended 5.5px while the real distance to travel was 12.8px, so the
   arms stopped 14.5px apart and never formed a cross.

   Flex column with an explicit gap makes the spacing real, and --pitch is the
   single source of truth for both the stack and the X, so the two cannot drift
   apart again. */
.burger {
  --bar: 1.5px;
  --pitch: 5.5px;                       /* bar height + gap = travel for the X */
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: calc(var(--pitch) - var(--bar));
}
@media (min-width: 1100px) { .burger { display: none; } }
.burger span {
  display: block; width: 17px; height: var(--bar);
  border-radius: 2px; background: currentColor;
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(var(--pitch)) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(calc(var(--pitch) * -1)) rotate(-45deg); }

/* drawer */
.drawer {
  position: fixed; inset: calc(var(--nav-h) + var(--frame-inset) * 2) var(--frame-inset) var(--frame-inset);
  z-index: 99;
  background: var(--dark);
  border-radius: var(--frame-radius);
  padding: 1.75rem clamp(1.25rem, 4vw, 2rem);
  overflow-y: auto;
  opacity: 0; visibility: hidden; transform: translateY(-10px);
  transition: opacity .3s var(--ease), transform .35s var(--ease), visibility .3s;
}
.drawer[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
.drawer a {
  display: block; color: #fff;
  font-family: var(--font-display); font-size: 1.375rem; font-weight: 300;
  padding: .8125rem 0; border-bottom: 1px solid rgba(255, 255, 255, .1);
}
/* Log in / Sign up pair, stacked full-width — the drawer is a phone-width
   column, so side-by-side would give two cramped buttons. Replaces the single
   `.btn-row` the old "Start free" CTA used. */
.drawer__cta { display: grid; gap: .625rem; margin-top: 1.75rem; }
.drawer__cta a {
  /* .drawer a sets display:block and outranks .btn (0,1,1 vs 0,1,0), which
     killed the button's own flex centring and left the labels ranged left */
  display: flex; align-items: center; justify-content: center;
  border-bottom: 0; font-family: var(--font-body);
  font-size: var(--fs-sm); padding: .8125rem 1.375rem;
}
/* the drawer surface is --dark, where the page's ink-on-white buttons are all
   but invisible: `.btn` borders in --ink (#1c1c1e) on --dark (#17171a), and
   `.btn--brand` fills with the same near-black. Both are re-pointed here
   rather than globally, because they are correct everywhere else. */
.drawer__cta .btn { border-color: rgba(255, 255, 255, .5); color: #fff; }
.drawer__cta .btn:hover { background: rgba(255, 255, 255, .12); }
.drawer__cta .btn--brand { background: #fff; border-color: #fff; color: var(--navy); }
.drawer__cta .btn--brand:hover { background: var(--cyan); border-color: var(--cyan); color: var(--navy); }
@media (min-width: 1100px) { .drawer { display: none; } }

/* --------------------------------------------------------------------------
   7. Hero — editorial: one line, one rotating word, over the office photograph

   Replaced the full-bleed video hero (three slides on a 6s timer, dots, four
   scrims, a play/pause control). Two reasons, in order:

   · The video was 4.7MB ahead of the first meaningful paint, on a page whose
     pitch is "modern, fast". That is now zero bytes of media before the
     photograph, which is 170KB.
   · The carousel made the reader wait 12s to see all three messages. The
     rotating word says four in the space of one line, and nothing below it
     moves while it does.

   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(88vh, 820px);
  display: grid; align-items: center;
  padding: calc(var(--nav-h) + var(--frame-inset) * 2 + clamp(2.5rem, 6vw, 5rem))
           0 clamp(3rem, 6vw, 5rem);
  /* `right center` is load-bearing. The photo is 16:9 and the band is wider
     than that, so cover crops the sides — anchoring right keeps both figures
     in frame as the band narrows AND pushes the empty wall under the copy.
     If the hero ever becomes taller than 16:9 the crop flips to vertical and
     the wood floor takes over; re-check the scrim if you change the height. */
  background: var(--tint) url("../assets/img/hero-office.jpg") right center / cover no-repeat;
  overflow: clip;
}
/* Warm-white scrim, the inverse of the usual case: the copy is dark ink, so
   legibility means LIFTING the backdrop, not darkening it.
   Measured over the band the copy occupies (left 46%, y 30-80% of the source)
   the photo runs mean 129 but dips to 20 in the wood floor at bottom-left,
   which is exactly where the buttons land. Sized to that minimum, not the
   mean: at 58% over a backdrop of 20 the composite is ~156, about 6:1 against
   --ink. Tinted rgb(242,241,238) — the page's own --tint — rather than pure
   white, so it meets the band's warm grey instead of adding a cooler one. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(90deg,
    rgba(242, 241, 238, .90) 0%,
    rgba(242, 241, 238, .84) 34%,
    rgba(242, 241, 238, .58) 48%,
    rgba(242, 241, 238, .18) 66%,
    rgba(242, 241, 238, .04) 100%);
}
.hero > .container { position: relative; z-index: 1; }

/* Narrow viewports invert both halves of the problem above.
   1. The band goes TALLER than the photo, so cover crops horizontally instead
      of vertically and `right center` keeps only the rightmost ~27% — the
      window and the two figures, with the empty wall gone entirely. Pulling
      the anchor to 34% keeps one figure and some wall.
   2. The copy runs full width here, not down the left, so a horizontal scrim
      protects the wrong edge — it is near-transparent exactly where the text
      now ends. Swapped for a near-uniform vertical one. */
@media (max-width: 899px) {
  .hero { background-position: 34% center; }
  .hero::before {
    background: linear-gradient(180deg,
      rgba(242, 241, 238, .90) 0%,
      rgba(242, 241, 238, .84) 55%,
      rgba(242, 241, 238, .76) 100%);
  }
}

.hero__title {
  font-size: clamp(2.25rem, 6vw, 4.75rem);
  font-weight: 200; line-height: 1.02; letter-spacing: -.04em;
  margin-top: 1.5rem; color: var(--ink);
}
/* The swapping word sits in a fixed-height clipper so the line beneath it
   never moves. That shift is the standard failure of rotating-word heroes —
   without the clip, every swap nudges the paragraph and buttons. */
.hero__rot {
  display: inline-grid; overflow: hidden;
  height: 1.02em;
  /* Descenders sit BELOW the line box, so a clip set to the line height alone
     slices the g in "asset register" and drops it onto the line beneath. The
     padding gives the clip room; the matching negative margin gives the space
     straight back, so the headline's layout is unchanged.

     content-box is required, and is the whole reason this works. The sheet
     sets `box-sizing: border-box` globally, under which `height` IS the border
     box — so the padding would eat INTO the 1.02em line instead of extending
     the clip below it, and the descender would still be sliced. Measured: the
     padding made no difference at all until this line was added. */
  box-sizing: content-box;
  padding-bottom: .28em;
  margin-bottom: -.28em;
  /* baseline, NOT bottom. `vertical-align: bottom` aligns the MARGIN box, and
     the margin above is negative — so the box was pushed down by exactly the
     padding and the word sat 13.2px below "Your" at desktop size. An
     inline-grid exposes its first item's baseline, and every word here shares
     one font and size, so baseline alignment holds whichever is showing. */
  vertical-align: baseline;
}
.hero__rot b {
  grid-area: 1 / 1; font-weight: 200; color: var(--blue);
  /* 155%, not 105%: the clip is taller than the word by that padding, so a
     smaller travel leaves an incoming word peeking inside it before its turn */
  transform: translateY(155%); opacity: 0;
  transition: transform .55s var(--ease-out), opacity .4s var(--ease-out);
}
.hero__rot b.is-on { transform: none; opacity: 1; }
.hero__rot b.is-out { transform: translateY(-155%); opacity: 0; }

.hero__sub {
  margin-top: 1.75rem; font-size: var(--fs-lead);
  color: var(--ink-mid); line-height: 1.55; max-width: 50ch;
}
.hero .btn-row { margin-top: 2.25rem; }

/* one word, shown plainly */
@media (prefers-reduced-motion: reduce) {
  .hero__rot { height: auto; }
  .hero__rot b { transform: none; opacity: 1; transition: none; }
  .hero__rot b:not(:first-child) { display: none; }
}

/* --------------------------------------------------------------------------
   8. Highlight cards  (.hl-*)

   Replaced the old `.highlights` list (three bordered rows, eyebrow + big
   heading + chevron). Pattern from nivra-studio.framer.website section [03]:
   sticky heading column, cards scrolling past on the right, each with an icon
   badge, a /0N index, a paragraph and tag pills.

   THREE THINGS CARRY THE MOTION
   1. Layered entrance. The card does not arrive as one block — badge, title,
      index, body, each tag and the link arrive in sequence over ~700ms. A
      block fade reads as "a thing appeared"; a layered one reads as "a thing
      assembled", which is most of the perceived quality.
   2. Reversible. Everything returns to its enter state on the way back up, so
      scrolling up animates too. reveal() cannot do this — it uses add() +
      unobserve(), one-shot by design — which is why hlCards() is separate.
   3. Continuous feedback. The rail in the heading column fills segment by
      segment, so something responds to the scroll the whole way through the
      stack, not only when a card crosses the line.

   Card stagger is ONE custom property, --cd, and every child's delay is
   calc(var(--cd) + var(--d)). One number moves the whole nine-step sequence,
   so the cards cannot drift out of step with each other.
   -------------------------------------------------------------------------- */
.hl { display: grid; gap: clamp(2.5rem, 5vw, 3.5rem); }
@media (min-width: 1024px) {
  .hl {
    grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
    gap: clamp(3rem, 6vw, 6.5rem);
    align-items: start;
  }
  /* align-self:start is what makes sticky possible in a grid — the default
     `stretch` gives the item full height and nothing to slide within */
  .hl__aside {
    position: sticky;
    top: calc(var(--nav-h) + var(--frame-inset) * 2 + clamp(1.5rem, 3vw, 2.5rem));
  }
}

/* progress rail — same idiom, and now the same mechanism, as .rails: the fill
   is scaled from a per-frame --rp rather than toggled.

   scaleX on a pseudo-element is compositor-only. The first version animated
   `background-size`, which is a paint-triggering property — fine for a class
   toggle, wasteful once the value is rewritten every frame. */
.hl-rails { display: flex; gap: .625rem; margin-top: clamp(1.75rem, 3vw, 2.5rem); }
.hl-rails i {
  position: relative; overflow: hidden;
  flex: 1; height: 3px; border-radius: 2px;
  background: var(--line);
}
.hl-rails i::after {
  content: ""; position: absolute; inset: 0;
  background: var(--blue);
  transform: scaleX(var(--rp, 0));
  transform-origin: left center;
}

.hl__list { display: grid; gap: clamp(1rem, 2vw, 1.5rem); }

.hl-card {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: clamp(1.5rem, 3vw, 2.25rem);

  /* --z is written per frame by hlZoom(): 0 at the viewport edges, 1 dead
     centre. The card scales up as it arrives and back down as it leaves.

     transform is therefore NOT transitioned and NOT used by the entrance.
     A transitioned property cannot also be driven per frame — the transition
     keeps chasing a value that moved again on the next frame, and the result
     lags the scroll. The entrance is carried by opacity here plus the layered
     children below, which is why the card no longer needs its own translateY. */
  --z: 0;
  transform: scale(calc(.95 + .05 * var(--z)));

  /* the 5px glass ring. box-shadow, not outline or border, because it must not
     take part in layout — a 5px border would move every child inward and a
     5px outline would collide with its neighbour's ring in the 1rem gap. */
  box-shadow: 0 0 0 5px rgba(255, 255, 255, .62);

  opacity: 0;
  transition: opacity .75s var(--ease-out),
              border-color .35s var(--ease), box-shadow .45s var(--ease);
  transition-delay: var(--cd, 0ms);
}
.hl-card.is-in { opacity: 1; }
/* no translateY on hover: it would have to share `transform` with the scroll
   zoom, and only one of the two can own that property. The ring and the shadow
   carry the hover instead. */
.hl-card:hover {
  border-color: transparent;
  box-shadow: 0 0 0 5px rgba(255, 255, 255, .95),
              0 18px 44px -18px rgba(1, 16, 77, .28);
}

/* every child starts displaced and returns to rest on its own delay */
.hl-card__ico,
.hl-card__title,
.hl-card__n,
.hl-card__body,
.hl-tag,
.hl-card .chev {
  opacity: 0;
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
  transition-delay: calc(var(--cd, 0ms) + var(--d, 0ms));
}
.hl-card.is-in .hl-card__ico,
.hl-card.is-in .hl-card__title,
.hl-card.is-in .hl-card__n,
.hl-card.is-in .hl-card__body,
.hl-card.is-in .hl-tag,
.hl-card.is-in .chev { opacity: 1; transform: none; }

.hl-card__top { display: flex; align-items: flex-start; gap: 1rem; }
.hl-card__ico {
  --d: 60ms;
  width: 44px; height: 44px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 97, 249, .08); color: var(--blue);
  transform: scale(.72) rotate(-12deg);
}
.hl-card__ico svg { width: 20px; height: 20px; }
.hl-card__title {
  --d: 120ms;
  font-family: var(--font-display); font-size: clamp(1.25rem, 2vw, 1.625rem);
  font-weight: 400; letter-spacing: -0.02em; line-height: 1.15;
  color: var(--ink); margin: .375rem 0 0;
  transform: translateY(14px);
}
/* the /0N index, from the reference — sits opposite the title and gives the
   stack a countable rhythm */
.hl-card__n {
  --d: 180ms;
  margin-left: auto; flex: none;
  font-family: var(--font-display); font-size: .9375rem; font-weight: 300;
  color: var(--ink-soft); padding-top: .5rem;
  transform: translateX(12px);
}
.hl-card__body {
  --d: 200ms;
  margin-top: 1.125rem; font-size: var(--fs-sm);
  color: var(--ink-mid); line-height: 1.6; max-width: 56ch;
  transform: translateY(12px);
}
.hl-tags { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.375rem; }
.hl-tag {
  font-size: var(--fs-xs); color: var(--ink-mid);
  background: var(--tint); border-radius: 999px;
  padding: .375rem .75rem; white-space: nowrap;
  transform: translateY(10px) scale(.94);
}
/* tags ripple rather than arriving together */
.hl-tag:nth-child(1) { --d: 260ms; }
.hl-tag:nth-child(2) { --d: 310ms; }
.hl-tag:nth-child(3) { --d: 360ms; }
.hl-tag:nth-child(4) { --d: 410ms; }
.hl-tag:nth-child(5) { --d: 460ms; }

.hl-card .chev { --d: 480ms; margin-top: 1.375rem; transform: translateY(10px); }
/* the arrow keeps moving on hover, so the card stays alive after it lands */
.hl-card .chev svg { transition: transform .35s var(--ease); }
.hl-card .chev:hover svg { transform: translateX(5px); }

/* Cards sit on --tint, so their own background must be paper to lift off it.
   Motion is the whole point here, so with it switched off the honest thing is
   to show the resolved state and animate nothing at all. */
@media (prefers-reduced-motion: reduce) {
  .hl-card,
  .hl-card__ico, .hl-card__title, .hl-card__n,
  .hl-card__body, .hl-tag, .hl-card .chev {
    opacity: 1; transform: none; transition: none;
  }
  /* the ring stays — it is a border treatment, not motion */
  .hl-rails i::after { transform: scaleX(1); }
}

/* --------------------------------------------------------------------------
   9. Mega statement band — converted to the reference's customer-story band
   (kyndryl.com/in/en #customer-stories). Measured there at 1206x517 (2.33:1,
   a 21x9 image crop): copy anchored top-left, eyebrow + 16x2 accent rule,
   28px/36px weight-300 headline, two outline buttons 32px apart, and a
   DIRECTIONAL scrim rather than a centred one.
   -------------------------------------------------------------------------- */
.mega {
  --mega-img: url("../assets/img/savvy.jpg");
  position: relative; overflow: hidden;
  /* savvy.jpg is 1942x809 (2.40:1), re-encoded from the supplied 1.8MB PNG at
     JPEG q88 — 236KB for the same picture, because a smooth dark gradient is
     the worst case for PNG. savvy.png is kept alongside it as the master.
     The blue stays underneath as a fallback, so the band still reads if the
     file is missing or slow.
     Swapping the image is this one line. Because min-height caps the band at
     520px, its aspect runs from ~1:1 on a phone to 4.8:1 at 2560 — a
     replacement wants its subject in the top half and near the horizontal
     centre to survive both crops. */
  background: var(--blue);
  color: #fff;
  /* 42vw tracks the reference's 2.33:1 at desktop; the clamp stops the band
     collapsing on phones and running away on very wide screens. */
  min-height: clamp(340px, 42vw, 520px);
  /* Vertically centred, still left-aligned. The reference anchors this band's
     copy to the TOP (40px down, measured), which is why it used to be plain
     padding — but that only reads well when the photo's subject sits low. With
     the subject at mid-height the top anchor left the copy floating above it.
     `align-items: center` on the grid, and the padding becomes symmetric so
     the copy still has room to breathe when the band is at its 340px floor.
     .mega__in is a .container, so its own `margin-inline: auto` keeps the
     column centred horizontally while the text inside stays ranged left. */
  display: grid; align-items: center;
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
}
/* Scrim over the copy column. The reference's diagonal (`to right bottom`,
   transparent by 60%) works because its photo is already dark on the left;
   savvy.jpg is a bright sky, so the diagonal left the end of the headline
   sitting on white cloud. A horizontal ramp darkens the whole copy column
   evenly instead and still clears the subject on the right. */
.mega::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to right, rgba(0, 0, 0, .82) 0%, rgba(0, 0, 0, .55) 32%, rgba(0, 0, 0, 0) 68%);
}
/* The photograph, MIRRORED. A background-image cannot be transformed, so it
   lives on its own layer that can carry one — which is the only reason this is
   a separate pseudo-element rather than a background on .mega itself.

   Position is mirrored with it: the original `0% 100%` anchored the image's
   left edge, and after scaleX(-1) the equivalent anchor is `100% 100%`.

   The z-index ladder is explicit and has to stay that way. ::after comes later
   in paint order than ::before, so without it the photo would sit ON TOP of
   the scrim and the copy would lose its backdrop entirely. */
.mega::after {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: var(--mega-img, none) 100% 100% / cover no-repeat;
  transform: scaleX(-1);
}
.mega__in { position: relative; z-index: 2; }
/* on-dark eyebrow: same idiom as the rest of the page, rule resized to the
   reference's 16x2 with its 8px/24px rhythm */
.mega .eyebrow { color: #fff; }
.mega .eyebrow::after {
  width: 16px; height: 2px; margin: 8px 0 24px; background: var(--cyan);
}
.mega h2 {
  /* 1.75rem = the reference's 28px, with 36px line-height (1.286) */
  font-size: clamp(1.5rem, 2.35vw, 1.75rem);
  line-height: 1.286; font-weight: 300; letter-spacing: -0.005em;
  color: #fff; margin: 0; max-width: 875px;
}
.mega__btns {
  /* 32px column gap is the reference figure; the smaller row gap only applies
     once the buttons wrap on narrow screens, where 32px reads as a break */
  display: flex; flex-wrap: wrap; gap: 14px 32px; margin-top: 16px;
}

/* --------------------------------------------------------------------------
   10. Cards
   -------------------------------------------------------------------------- */
.cards { display: grid; gap: clamp(1rem, 2vw, 1.5rem); margin-top: clamp(2rem, 4vw, 3rem); }
.cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cards--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 900px) { .cards--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .cards--3, .cards--2 { grid-template-columns: minmax(0, 1fr); } }

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: clamp(1.375rem, 2.2vw, 1.875rem);
  display: flex; flex-direction: column;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease), border-color .3s var(--ease);
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-card); border-color: #c9d3e4; }
.card__ico { color: var(--blue); margin-bottom: 1rem; }
.card__ico svg { width: 26px; height: 26px; }
.card h3 { font-size: var(--fs-h3); }
.card p { margin-top: .75rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6; }

.band--tint .card { background: var(--paper); border-color: transparent; }
.band--navy .card {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .12);
  color: #fff;
}
.band--navy .card:hover { background: rgba(255, 255, 255, .08); border-color: rgba(255, 255, 255, .24); box-shadow: none; }
.band--navy .card p { color: rgba(226, 236, 255, .72); }
.band--navy .card__ico { color: var(--cyan); }

/* --------------------------------------------------------------------------
   11. Numbered accordion rows
   Hairline-separated rows on the section background. The accent shows only on
   the number, the chevron and a 2px bar on the open row — no colour flooding.
   -------------------------------------------------------------------------- */
.groups { margin-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--line); }
.group { border-bottom: 1px solid var(--line); position: relative; }
.group::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--blue);
  transform: scaleY(0); transform-origin: top;
  transition: transform .35s var(--ease);
}
.group[data-open="true"]::before { transform: scaleY(1); }

.group__btn {
  width: 100%; border: 0; background: transparent; cursor: pointer;
  display: flex; align-items: center; gap: clamp(.875rem, 2vw, 1.5rem);
  padding: clamp(1.125rem, 2.2vw, 1.625rem) clamp(.5rem, 1.4vw, 1rem);
  text-align: left; color: var(--ink);
  transition: color .25s var(--ease);
}
.group__btn:hover { color: var(--blue); }
.group__no {
  flex: none;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  color: var(--blue);
  font-family: var(--font-display); font-size: .75rem; font-weight: 500;
  transition: border-color .25s var(--ease);
}
.group[data-open="true"] .group__no { border-color: var(--blue); }
.group__name {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.9vw, 1.5rem);
  font-weight: 400; letter-spacing: -0.02em; line-height: 1.25;
}
.group__count { margin-left: auto; flex: none; font-size: var(--fs-xs); color: var(--ink-soft); display: none; }
@media (min-width: 720px) { .group__count { display: block; } }
.group__chev {
  flex: none; width: 30px; height: 30px; margin-left: .75rem;
  display: grid; place-items: center;
  color: var(--blue);
  transition: transform .35s var(--ease);
}
.group__chev svg { width: 18px; height: 18px; }
.group[data-open="true"] .group__chev { transform: rotate(180deg); }

.group__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .4s var(--ease); }
.group[data-open="true"] .group__panel { grid-template-rows: 1fr; }
.group__panel > div { overflow: hidden; }
.group__list {
  list-style: none; margin: 0;
  padding: 0 clamp(.5rem, 1.4vw, 1rem) clamp(1.375rem, 2.4vw, 2rem) clamp(3.25rem, 4.4vw, 4rem);
  display: grid; gap: .875rem;
}
@media (min-width: 760px) { .group__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 2rem; } }
.group__list li { display: flex; gap: .75rem; align-items: flex-start; }
.group__list svg { flex: none; width: 17px; height: 17px; margin-top: .2em; color: var(--blue); }
.group__list b { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.group__list span { display: block; font-size: var(--fs-xs); color: var(--ink-mid); line-height: 1.45; margin-top: .1rem; }

/* --------------------------------------------------------------------------
   12. Showcase carousel (product mockups) with progress rails
   -------------------------------------------------------------------------- */
.showcase { margin-top: clamp(2rem, 4vw, 3rem); }

/* ---- per-slide background wash -------------------------------------------
   The band changes mood as the pinned carousel advances. Three stacked layers
   crossfading, not one layer whose background-image swaps — background-image
   cannot be transitioned, so a swap would hard-cut.

   Colour discipline: these are 10-14% alpha WASHES over the tint band, not
   solid colour. They read as a shift in light rather than three coloured
   backgrounds, which is what keeps the section inside the accent budget the
   rest of the page is measured against. If you raise the alphas much past
   ~.15 the band stops being a neutral surface and starts competing with
   .mega for the eye. The band's own text colour never has to change, which is
   the other reason to keep them this light.
   --------------------------------------------------------------------------- */
/* clip, NOT hidden — exactly the trap `.frame` documents. `overflow:hidden`
   makes this a scrollport, which becomes the containing block for every
   sticky descendant and silently kills .showcase__pin: the pin scrolls away
   with the content instead of holding. `clip` contains the wash the same way
   and creates no scroll container. */
#inside { position: relative; overflow: clip; }
#inside > .container { position: relative; z-index: 1; }

.sc-wash { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.sc-wash i {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 1s var(--ease-out);
}
.sc-wash i.is-on { opacity: 1; }
/* one per slide — different hue AND different origin, so the light appears to
   move across the band rather than just change colour in place */
.sc-wash i:nth-child(1) { background: radial-gradient(68% 58% at 78% 16%, rgba(0, 97, 249, .10), transparent 70%); }
.sc-wash i:nth-child(2) { background: radial-gradient(64% 58% at 26% 78%, rgba(74, 212, 255, .14), transparent 72%); }
.sc-wash i:nth-child(3) { background: radial-gradient(76% 64% at 58% 42%, rgba(1, 16, 77, .10), transparent 70%); }

@media (prefers-reduced-motion: reduce) {
  .sc-wash i { transition: none; }
}

/* ---- scroll-driven slides ------------------------------------------------
   Above 900px `.showcase` stops being a box and becomes a scroll TRACK: tall
   enough to give the pinned stage roughly one screenful of travel per slide.
   `.showcase__pin` sticks inside it, so the section stays put while the reader
   scrolls through slide 1 -> 2 -> 3, and back 3 -> 2 -> 1 on the way up.

   Only above 900px. Below that the slide is a single column and the stage is
   already ~750px against an ~810px viewport — a pinned track would leave no
   room to see it move, so the carousel stays on a timer there instead.
   --------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .showcase { height: calc(var(--slides, 3) * 82vh); }
  .showcase__pin {
    position: sticky;
    top: calc(var(--nav-h) + var(--frame-inset) * 2 + clamp(1rem, 2.5vw, 2.25rem));
  }
}
/* All slides share one grid cell, so the stage is as tall as the TALLEST slide
   and every slide is stretched to that height. The previous version left slide
   1 in flow and absolutely positioned the rest against it (`:not(:first-child)
   { position:absolute; inset:0 }`), which sized the stage by slide 1
   specifically — any later slide taller than it would have overflowed the band
   with no warning. */
.showcase__stage { display: grid; }
.showcase__item {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden;
  transition: opacity .5s var(--ease-out), visibility .5s;
}
.showcase__item[data-active="true"] { opacity: 1; visibility: visible; }
.showcase__grid { display: grid; gap: clamp(1.5rem, 3vw, 2.75rem); align-items: center; }

@media (min-width: 900px) {
  .showcase__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* Stretching the slide is not enough on its own — the grid INSIDE it still
     sized to its own content, so a slide with a shorter mock (the dashboard is
     271px against the two list mocks' 348px) placed its copy 39px higher and
     ended its card 77px short. Switching to it looked like the whole layout
     jumped. These three rules pin every slide to identical geometry, so
     changing slide is a pure crossfade with nothing moving. */
  .showcase__grid { height: 100%; }
  .mock { height: 100%; display: flex; flex-direction: column; }
  /* the body absorbs the surplus and distributes its rows into it, rather than
     the card growing with a block of dead space at the bottom */
  .mock__body { flex: 1; align-content: space-between; }
}
.showcase__copy h3 { font-size: var(--fs-h3); margin-top: 1rem; }
.showcase__copy p { margin-top: .875rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6; }
.showcase__copy .btn-row { margin-top: 1.5rem; }

.rails { display: flex; gap: .75rem; margin-top: clamp(1.75rem, 3vw, 2.5rem); }
.rails button {
  flex: 1; height: 3px; padding: 0; border: 0; border-radius: 2px;
  background: var(--line); cursor: pointer; position: relative; overflow: hidden;
}
/* The fill is scaled, not toggled: --rp is written per frame from the scroll
   position, so segment k grows 0 -> 1 as you move through slide k. Earlier
   segments sit at 1, later ones at 0, exactly one is mid-fill.
   scaleX on a pseudo-element is compositor-only — no layout, no paint — which
   is what makes it safe to drive every frame. */
.rails button::after {
  content: ""; position: absolute; inset: 0;
  background: var(--blue);
  transform: scaleX(var(--rp, 0));
  transform-origin: left center;
}
/* the transition belongs to the timed mode only — see scrollMode() */
.rails.is-timed button::after { transition: transform .6s var(--ease-out); }
/* no-JS fallback: --rp never gets written, so fall back to the selected state */
.no-js .rails button[aria-selected="true"]::after { transform: scaleX(1); }

/* mock UI */
.mock {
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--paper); box-shadow: var(--shadow-card);
}
.mock__bar {
  display: flex; align-items: center; gap: .5rem;
  padding: .6875rem .875rem;
  background: var(--tint-2); border-bottom: 1px solid var(--line);
  font-size: var(--fs-xs); font-weight: 600;
}
.mock__bar svg { width: 15px; height: 15px; color: var(--blue); }
.mock__pill {
  margin-left: auto; font-size: .5625rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--blue); background: rgba(0, 97, 249, .09);
  padding: .1875rem .4375rem; border-radius: 999px;
}
.mock__body { padding: .875rem; display: grid; gap: .5rem; }
.mock__tabs { display: flex; gap: .3125rem; margin-bottom: .1875rem; }
.mock__tab {
  font-size: .625rem; font-weight: 600; padding: .25rem .5625rem;
  border-radius: 999px; background: var(--tint); color: var(--ink-soft);
}
.mock__tab[data-on="true"] { background: var(--navy); color: #fff; }
.row {
  display: flex; align-items: center; gap: .625rem;
  padding: .5625rem .6875rem;
  border: 1px solid var(--line); border-radius: 8px;
}
.row__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.row__dot--hi { background: var(--alert); }
.row__dot--mid { background: var(--blue); }
.row__dot--low { background: var(--ok); }
.row__dot--warn { background: var(--warn); }
.row__main { min-width: 0; }
.row__name { font-size: var(--fs-xs); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row__meta { font-size: .625rem; color: var(--ink-soft); margin-top: .0625rem; }
.row__tag {
  margin-left: auto; flex: none; font-size: .5625rem; font-weight: 600;
  padding: .125rem .4375rem; border-radius: 999px;
  background: var(--tint); color: var(--ink-soft);
}
.row__tag--ok { background: rgba(23, 185, 120, .12); color: #0f8a58; }
.row__tag--warn { background: rgba(255, 159, 28, .14); color: #b26a00; }
.row__tag--info { background: rgba(0, 97, 249, .1); color: var(--blue); }
.mock__foot {
  padding: .5625rem .875rem; border-top: 1px solid var(--line);
  background: var(--tint-2); font-size: .625rem; font-weight: 600; color: var(--ink-soft);
  display: flex; align-items: center; gap: .375rem;
}
.mock__foot svg { width: 13px; height: 13px; color: var(--blue); }

/* health score */
.score { display: flex; align-items: center; gap: 1rem; padding: .25rem .125rem .625rem; }
.score__ring {
  --pct: 92; width: 82px; height: 82px; flex: none; border-radius: 50%;
  display: grid; place-items: center; position: relative;
  background: conic-gradient(var(--blue) 0 calc(var(--pct) * 1%), var(--tint) calc(var(--pct) * 1%) 100%);
}
.score__ring::before { content: ""; position: absolute; inset: 8px; border-radius: 50%; background: #fff; }
.score__val { position: relative; font-family: var(--font-display); font-size: 1.375rem; font-weight: 400; color: var(--navy); }
.score__k { font-size: .625rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.score__t { font-size: var(--fs-xs); color: var(--ink-mid); margin-top: .3125rem; line-height: 1.45; }
.metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4375rem; }
.metric { border: 1px solid var(--line); border-radius: 8px; padding: .5625rem .5rem; text-align: center; }
.metric b { display: block; font-family: var(--font-display); font-size: 1rem; font-weight: 500; color: var(--navy); }
.metric span { display: block; font-size: .5625rem; color: var(--ink-soft); margin-top: .125rem; line-height: 1.3; }

/* --------------------------------------------------------------------------
   13. How it works — sticky narrative + progress rail  (.how-*)

   The one section that carries the whole proposition, so it gets the page's
   only scroll-linked treatment. Two moving parts, both accent-only:

   · the heading column sticks while the four steps scroll past it
   · a hairline rail threads the step numerals; each segment fills with accent
     as its step crosses the reading line, and the numeral fills with it

   The rail is built from PER-STEP segments (.how__step::before), not one
   absolutely-positioned bar whose height JS computes. That was the first
   approach and it needs the badge geometry re-measured on every resize and
   every font swap. Segments need no measurement at all: each one spans from
   its own badge to the next, so the rail stays continuous at any width, and
   the fill is a background-size transition rather than a layout property.

   Reading line is 38% down the viewport, below the scroll-spy's 22% — a step
   should light up as it arrives at reading position, not as it clears the nav.
   -------------------------------------------------------------------------- */
.how { display: grid; gap: clamp(2.5rem, 5vw, 3.5rem); }

@media (min-width: 1024px) {
  .how {
    grid-template-columns: minmax(0, .88fr) minmax(0, 1.12fr);
    gap: clamp(3rem, 6vw, 6.5rem);
    align-items: start;
  }
  /* clears the fixed nav, then a breath. align-self:start is what lets a grid
     item be sticky at all — the default `stretch` makes the item full-height,
     so there is nothing for it to slide within. */
  .how__aside {
    position: sticky;
    top: calc(var(--nav-h) + var(--frame-inset) * 2 + clamp(1.5rem, 3vw, 2.5rem));
  }
}

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

.how__step {
  position: relative;
  padding: 0 0 clamp(2rem, 4vw, 3.25rem) 62px;
}
.how__step:last-child { padding-bottom: 0; }

/* the rail segment: from under this step's badge down to the next one */
.how__step::before {
  content: "";
  position: absolute; left: 19px; top: 48px; bottom: 0; width: 1px;
  background-color: var(--line);
  background-image: linear-gradient(var(--blue), var(--blue));
  background-repeat: no-repeat;
  background-position: top;
  background-size: 1px 0%;
  transition: background-size .65s var(--ease-out);
}
.how__step:last-child::before { display: none; }
.how__step.is-on::before { background-size: 1px 100%; }

.how__num {
  position: absolute; left: 0; top: 0;
  width: 39px; height: 39px; border-radius: 999px;
  display: grid; place-items: center;
  font-family: var(--font-display); font-size: .875rem; font-weight: 400;
  /* paper, not transparent: the badge has to mask the rail behind it, and this
     band is tint */
  background: var(--paper);
  border: 1px solid var(--line);
  color: var(--ink-mid);
  transition: background-color .45s var(--ease), border-color .45s var(--ease),
              color .45s var(--ease), transform .45s var(--ease-out);
}
.how__step.is-on .how__num {
  background: var(--blue); border-color: var(--blue); color: #fff;
  transform: scale(1.06);
}

.how__step h3 {
  font-size: var(--fs-h3);
  /* muted until the step is reached, then full ink. #3d3c3c on the tint band
     is ~10:1, so the "inactive" state is still comfortably readable — the
     shift is a nicety, never the difference between legible and not. */
  color: var(--ink-mid);
  transition: color .45s var(--ease);
}
.how__step.is-on h3 { color: var(--ink); }
.how__step p {
  margin-top: .625rem;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6;
  max-width: 56ch;
}

@media (max-width: 1023px) {
  .how__step { padding-left: 54px; }
  .how__step::before { left: 17px; top: 42px; }
  .how__num { width: 35px; height: 35px; font-size: .8125rem; }
}

/* The rail is a progress indicator, so it has nothing to say when motion is
   off — show every step in its resolved state and stop there. */
@media (prefers-reduced-motion: reduce) {
  .how__step::before, .how__num { transition: none; }
}

/* --------------------------------------------------------------------------
   14. FAQ
   -------------------------------------------------------------------------- */
.faq { margin-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  width: 100%; background: none; border: 0; cursor: pointer;
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 1.25rem 0; text-align: left;
  font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 400;
  letter-spacing: -0.02em; color: var(--ink);
}
.faq__q .ico { margin-left: auto; flex: none; width: 26px; height: 26px; display: grid; place-items: center; color: var(--blue); transition: transform .35s var(--ease); }
.faq__q .ico svg { width: 18px; height: 18px; }
.faq__q[aria-expanded="true"] .ico { transform: rotate(180deg); }
.faq__a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .4s var(--ease); }
.faq__item[data-open="true"] .faq__a { grid-template-rows: 1fr; }
.faq__a > div { overflow: hidden; }
.faq__a p { padding-bottom: 1.375rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.65; max-width: 74ch; }

/* --------------------------------------------------------------------------
   15. Closing CTA split
   -------------------------------------------------------------------------- */
.close-cta { background: var(--tint); color: var(--ink); position: relative; }
.close-cta__inner { position: relative; display: grid; gap: clamp(1.75rem, 3.5vw, 3rem); align-items: center; }
@media (min-width: 940px) { .close-cta__inner { grid-template-columns: minmax(0, 1.3fr) minmax(0, .7fr); } }
.close-cta h2 { font-size: var(--fs-h2); font-weight: 250; }
.close-cta .lead { margin-top: 1.125rem; }
@media (min-width: 940px) { .close-cta .btn-row { justify-content: flex-end; } }

/* --------------------------------------------------------------------------
   16. Footer
   -------------------------------------------------------------------------- */
.foot { background: var(--navy); color: #fff; padding-top: clamp(2.5rem, 5vw, 4rem); }
.foot__top { display: grid; gap: clamp(2rem, 4vw, 3.5rem); padding-bottom: clamp(2rem, 4vw, 3rem); }
@media (min-width: 900px) { .foot__top { grid-template-columns: minmax(0, 1.1fr) minmax(0, 2fr); } }
.foot .brand__crop { filter: brightness(0) invert(1); --wordmark-h: 26px; }
.foot__blurb { margin-top: 1.125rem; font-size: var(--fs-sm); color: rgba(226, 236, 255, .62); line-height: 1.6; max-width: 42ch; }
.foot__cols { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }
@media (min-width: 620px) { .foot__cols { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.foot h4 { font-family: var(--font-body); font-size: var(--fs-eyebrow); font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: rgba(226, 236, 255, .46); }
.foot ul { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: .75rem; }
.foot a { font-size: var(--fs-sm); color: rgba(226, 236, 255, .78); }
.foot a:hover { color: var(--cyan); }
.foot__bottom {
  border-top: 1px solid rgba(255, 255, 255, .1);
  padding-block: 1.25rem 1.5rem;
  display: flex; flex-wrap: wrap; gap: .625rem 1.5rem; align-items: center;
  font-size: var(--fs-xs); color: rgba(226, 236, 255, .5);
}
.foot__bottom nav { display: flex; flex-wrap: wrap; gap: 1.125rem; margin-left: auto; }

/* --------------------------------------------------------------------------
   17. "Jump to" floating pill
   -------------------------------------------------------------------------- */
.jump {
  position: fixed; z-index: 90;
  left: calc(var(--frame-inset) + 12px);
  bottom: calc(var(--frame-inset) + 12px);
  display: none;
}
.jump__btn {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .625rem 1.125rem;
  font-size: var(--fs-xs); font-weight: 600;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(14px);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: var(--shadow-pill);
  cursor: pointer;
  color: var(--ink);
}
.jump__btn svg { width: 14px; height: 14px; transition: transform .3s var(--ease); }
.jump[data-open="true"] .jump__btn svg { transform: rotate(180deg); }
.jump__menu {
  position: absolute; bottom: calc(100% + .5rem); left: 0;
  min-width: 232px;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow-pill);
  padding: .4375rem;
  display: grid;
  opacity: 0; visibility: hidden; transform: translateY(6px);
  transition: opacity .25s var(--ease), transform .3s var(--ease), visibility .25s;
}
.jump[data-open="true"] .jump__menu { opacity: 1; visibility: visible; transform: none; }
.jump__menu a { padding: .5rem .75rem; border-radius: 8px; font-size: var(--fs-xs); color: var(--ink); }
.jump__menu a:hover { background: var(--tint); color: var(--blue); }
@media (min-width: 1100px) { .jump { display: block; } }

/* --------------------------------------------------------------------------
   18. Reveal
   -------------------------------------------------------------------------- */
[data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity .7s var(--ease-out), transform .8s var(--ease-out); }
[data-reveal].in { opacity: 1; transform: none; }
.no-js [data-reveal] { opacity: 1; transform: none; }

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


/* --------------------------------------------------------------------------
   19. Horizontal stack tabs  (.hst-*)
   Ported from the reference's `.horizontal-stack-tabs`: a two-column card with
   a copy panel on the left and a row of collapsed colour bands on the right.
   Each band is flex:1 collapsed and flex:7 active, animated on the flex basis,
   so the active panel slides open horizontally. Below 1024px the whole card
   becomes a vertical accordion.

   Two deliberate departures from the reference:
   - the hst- prefix, because the page sections already use .band
   - one set of markup instead of the reference's duplicated
     .band-desktop / .band-mobile subtrees, so the copy lives in one place
   -------------------------------------------------------------------------- */
.hst { padding-block: clamp(3.5rem, 7vw, 7rem); }

.hst-card {
  max-width: 1512px; margin-inline: auto;
  border-radius: 22px; overflow: hidden;
  display: grid; grid-template-columns: 1.05fr 1.95fr;
  background: var(--paper);
}

/* --- left copy panel --- */
.hst-left { background: var(--tint); padding: 56px 48px; position: relative; }
.hst-left .eyebrow { margin-bottom: 1.25rem; }
.hst-title {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.875rem, 3.1vw, 2.75rem);
  line-height: 1.06; letter-spacing: -0.03em;
  color: var(--ink); margin: 0;
}
.hst-title .accent { color: var(--blue); display: block; }
.hst-note {
  margin-top: 1.125rem; font-size: var(--fs-sm);
  color: var(--ink-mid); line-height: 1.55; max-width: 34ch;
}
.hst-arrows { position: absolute; left: 48px; bottom: 36px; display: flex; gap: 18px; }
.hst-arrows button {
  width: 38px; height: 38px; border-radius: 999px; border: 0;
  background: transparent; cursor: pointer;
  display: grid; place-items: center; color: var(--ink);
  transition: background-color .25s var(--ease);
}
.hst-arrows button:hover { background: rgba(0, 0, 0, .06); }
.hst-arrows svg { width: 18px; height: 18px; }

/* --- the bands --- */
.hst-bands {
  /* Collapsed strips are a FIXED width rather than the reference's flex ratio.
     With a ratio the strip is container/10, so it changes with the viewport
     while the badge offset does not — the badge drifts toward the strip's right
     edge as the card narrows. A fixed strip lets the badge be centred exactly
     at every width (see .hst-badge). */
  --strip: clamp(72px, 5.4vw, 96px);
  min-height: 440px; display: flex; overflow: hidden;
  border-top-right-radius: 22px; border-bottom-right-radius: 22px;
}
.hst-band {
  position: relative; overflow: hidden;
  cursor: pointer;
  flex: 0 0 var(--strip);
  /* flex-grow is a number, so it interpolates — the active band expands into
     the leftover space smoothly. Don't animate width or flex-basis here: a
     percentage-based badge offset would then swing outward mid-transition. */
  transition: flex-grow .5s ease;
}
.hst-band.is-active { flex-grow: 1; }
.hst-band:focus-visible { outline: 2px solid #fff; outline-offset: -4px; }

/* colour ladder, light to dark left-to-right. The brand navy family, so this
   reads as a deep neutral panel rather than a block of vivid blue.
   Prefer changing these four values over adding blue elsewhere. */
.hst-bands .hst-band:nth-child(1) { background: #1b3f86; }
.hst-bands .hst-band:nth-child(2) { background: #14306e; }
.hst-bands .hst-band:nth-child(3) { background: #0a2159; }
.hst-bands .hst-band:nth-child(4) { background: #01104d; }

.hst-badge {
  --badge: 46px;
  position: absolute; top: 22px; z-index: 3;
  /* centred in the collapsed strip by construction, at any --strip value.
     Derived from a fixed length, so it holds still while the band animates. */
  left: calc((var(--strip) - var(--badge)) / 2);
  width: var(--badge); height: var(--badge); border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 15px; font-weight: 500;
  line-height: 1; text-align: center;
  color: #fff; background: rgba(255, 255, 255, .16);
  transition: background-color .3s var(--ease);
}
.hst-band.is-active .hst-badge { background: rgba(255, 255, 255, .26); }

.hst-panel {
  position: absolute; inset: 0;
  /* Content is indented to the badge's LEFT edge, so the number and the title
     share a left edge and the block reads as one column. It does not need to
     clear the badge horizontally: the 84px top padding already starts the copy
     below the badge (which ends at 68px). The earlier rail did clear it
     sideways — strip + 4px — which pushed the title ~112px in from the band
     edge and cost the copy column about 76px of width for no alignment gain.
     Derived from --strip, so it tracks the badge at every viewport. */
  --rail: calc((var(--strip) - 46px) / 2);
  padding: 84px 36px 48px 0;
  /* flex-start, not space-between: the reference pushed a title to the top and
     a logo to the bottom, but our second block is body copy. Splitting it to
     the far end of a 440px panel left a 93px void under the title and the copy
     stopped reading as one unit. */
  display: flex; flex-direction: column; justify-content: flex-start;
  gap: clamp(1.25rem, 2vw, 1.75rem);
  opacity: 0; pointer-events: none;
  transition: opacity .3s ease;
}
.hst-band.is-active .hst-panel { opacity: 1; pointer-events: auto; }
/* fade every panel out while the width animates, then fade the new one in */
.hst-bands.is-switching .hst-panel { opacity: 0 !important; }

.hst-head, .hst-body { margin-left: var(--rail); }
.hst-h {
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 2.1vw, 2rem);
  font-weight: 400; line-height: 1.06; letter-spacing: -0.025em;
  color: #fff; margin: 0; max-width: 22ch;
}
.hst-mchev { display: none; }
/* Alpha lives in the colour, not in `opacity`. Element opacity on a list item
   also dims its nested tick, compounding with the tick's own value — the ticks
   were landing at an effective .72 and looked washed out. */
.hst-desc {
  font-size: clamp(.9375rem, 1.15vw, 1.0625rem);
  line-height: 1.6;
  color: rgba(255, 255, 255, .88);
  margin: 0 0 1.375rem; max-width: 44ch;
}
.hst-fns {
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: .8125rem 1.5rem;
  /* auto-fit rather than a hard 2, so a column is never narrower than a
     function name — the longest is "DevOps & Apps Management" at ~199px.
     Drops to one column by itself when the band is narrow. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.hst-fns li {
  display: flex; align-items: flex-start; gap: .625rem;
  font-size: var(--fs-sm); color: rgba(255, 255, 255, .96); line-height: 1.45;
}
/* cyan tick rather than dim white — gives the eye an anchor to scan down, and
   separates the marker from the label it sits next to */
.hst-fns svg {
  flex: none; width: 16px; height: 16px; margin-top: .2em;
  color: var(--cyan);
}

/* --- 1025-1200px: the active band gets tight once three fixed strips are
   subtracted, so pull the padding in and drop the list to one column --- */
@media (min-width: 1025px) and (max-width: 1200px) {
  .hst-left { padding: 44px 34px; }
  .hst-arrows { left: 34px; bottom: 28px; }
  .hst-panel { padding: 76px 36px 36px 0; }
  .hst-fns { grid-template-columns: minmax(0, 1fr); }
}

/* --- below 1024px: vertical accordion, one row per band --- */
@media (max-width: 1024px) {
  .hst-card { grid-template-columns: 1fr; border-radius: 18px; }
  .hst-left { padding: 40px 22px; border-radius: 18px 18px 0 0; }
  .hst-arrows { display: none; }

  .hst-bands { flex-direction: column; min-height: 0; border-radius: 0 0 18px 18px; }
  .hst-band {
    flex: none; width: 100%; transition: none;
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    column-gap: .875rem; align-items: start;
    padding: 1.125rem 1.25rem;
  }

  .hst-badge {
    position: static; grid-column: 1; grid-row: 1;
    width: 40px; height: 40px; font-size: 14px;
  }

  .hst-panel {
    position: static; grid-column: 2; grid-row: 1;
    padding: 0; gap: 0; display: block;
    opacity: 1; pointer-events: auto;
  }
  .hst-bands.is-switching .hst-panel { opacity: 1 !important; }

  .hst-head {
    margin-left: 0; min-height: 40px;
    display: flex; align-items: center; gap: .75rem;
  }
  .hst-h { font-size: 1.0625rem; max-width: none; }

  .hst-mchev {
    display: grid; place-items: center;
    margin-left: auto; flex: none;
    color: #fff; opacity: .8;
    transition: transform .35s var(--ease);
  }
  .hst-mchev svg { width: 18px; height: 18px; }
  .hst-band.is-active .hst-mchev { transform: rotate(180deg); }

  /* collapsing body */
  .hst-body {
    margin-left: 0;
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows .4s var(--ease);
  }
  .hst-band.is-active .hst-body { grid-template-rows: 1fr; }
  .hst-body > .hst-body-inner {
    overflow: hidden; opacity: 0;
    transition: opacity .3s var(--ease);
  }
  .hst-band.is-active .hst-body > .hst-body-inner { opacity: 1; }
  .hst-desc { padding-top: .875rem; margin-bottom: .875rem; }
  .hst-fns { grid-template-columns: minmax(0, 1fr); padding-bottom: .375rem; max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hst-band { transition: none; }
}

/* --------------------------------------------------------------------------
   20. Oversized section watermark  (.swm)
   Ported from the reference's `.cmp-section-header__title-text`. An outsized
   lowercase line, coloured as the band that FOLLOWS it, sitting flush at the
   end of the preceding band — so the section boundary slices through the type
   and the next band's colour appears to rise up as giant letters.

   Measured from the reference: font-weight 700, lowercase, nowrap,
   line-height exactly .75, margin-left -0.06em, and a font-size fitted per
   phrase so the line always fills the container (theirs ran 117.5 / 129.6 /
   135.4px for three different phrases at the same width). The fit is done in
   js/landing.js -> swmFit.
   -------------------------------------------------------------------------- */

/* flex, so the line is a shrink-to-content item that can be measured, and so
   there is no inline baseline gap beneath it. As a plain block child it would
   report the CONTAINER width and the fitter would scale by ~1.0 — i.e. do
   nothing. overflow hidden clips the -0.06em bleed without a scrollbar. */
.swm-wrap {
  display: flex;
  overflow: hidden;
  margin-top: clamp(2rem, 5vw, 4rem);
}
.swm {
  flex: 0 0 auto;
  margin: 0 0 0 -0.06em;      /* pull the side bearing optically flush */
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: lowercase;   /* DOM keeps sentence case, for screen readers */
  white-space: nowrap;
  line-height: .75;
  letter-spacing: -0.01em;
  color: var(--tint);          /* = the colour of the band that follows */
  /* pre-fit fallback, so there is no flash of 16px text before the script runs */
  font-size: 11.4vw;
  -webkit-user-select: none; user-select: none;
}

/* the band the watermark sits in gives up its bottom padding, so the type is
   flush to the boundary and gets sliced by the next band */
.band--swm-end { padding-bottom: 0; }

/* Below 768px the fitted size drops to ~30px for a 20-character phrase, which
   is too small to read as a graphic device — it just looks like odd grey text.
   Hidden rather than shrunk. */
@media (max-width: 767px) {
  .swm-wrap { display: none; }
  .band--swm-end { padding-bottom: clamp(3.5rem, 7vw, 7rem); }
}

/* --------------------------------------------------------------------------
   21. Plan comparison matrix  (.mx-*)
   Replaces the three pricing cards. The cards had measurable faults: ragged
   heights (445/479/479px, CTAs at three different offsets), a "featured" card
   with the same white background as its neighbours, and "Everything in
   Starter" carrying the comparison in prose.

   border-collapse is `separate`, not `collapse`: with `collapse` Chrome drops
   the borders on sticky cells. Every rule below is therefore a per-cell border.
   -------------------------------------------------------------------------- */
.mx-wrap {
  border: 1px solid var(--line); border-radius: 14px;
  overflow: clip; background: #fff;
  margin-top: clamp(2rem, 4vw, 3rem);
}
/* No inner scroller on desktop. An 80vh nested scrollport left only 288px for
   rows — the header alone ate 48% of it, so 4 of 15 rows were visible and you
   had 910px of nested scrolling to get through. The table now flows in the page
   and the header sticks below the site nav instead. */
.mx-scroll { overflow: visible; }

.mx { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; }
.mx col.mx-c-feat { width: 34%; }
.mx col.mx-c-plan { width: 22%; }

/* ---- sticky header ---- */
.mx thead th {
  position: sticky; top: calc(var(--frame-inset) + var(--nav-h) + 4px); z-index: 3;
  padding: 0; vertical-align: top; text-align: left;
  background: var(--tint);
  border-bottom: 1px solid var(--line);
}
.mx thead th.mx-intro { z-index: 4; }

.mx-intro-in { padding: 1.375rem 1.5rem; }
.mx-intro-in h3 {
  font-family: var(--font-display); font-size: 1.375rem; font-weight: 300;
  letter-spacing: -.03em; margin-top: .625rem; color: var(--ink);
}
.mx-intro-in p { font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.5; margin-top: .625rem; }

/* plan card inside each header cell */
.mx-card {
  padding: 1.25rem 1.25rem 1.375rem; height: 100%;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--line);
}
.mx-name { font-family: var(--font-display); font-size: 1.375rem; font-weight: 600; letter-spacing: -.02em; }
.mx-for  { font-size: var(--fs-sm); color: var(--ink-mid); margin-top: .3rem; font-weight: 400; }
.mx-price {
  display: flex; align-items: baseline; gap: .28em; margin-top: .875rem;
  font-family: var(--font-display); font-size: 2.25rem; font-weight: 250;
  letter-spacing: -.04em; line-height: 1; color: var(--ink);
}
/* unit sized in em so it scales with the figure instead of being pinned */
.mx-price small {
  font-family: var(--font-body); font-size: .30em; font-weight: 500;
  letter-spacing: 0; color: var(--ink-soft);
}
.mx-card .btn { margin-top: 1rem; width: 100%; }

/* Badge row is reserved in EVERY card so the plan names, prices and CTAs share
   baselines. The pill needs line-height:1 or it outgrows the reserved row and
   pushes the recommended column's name down. */
.mx-badgerow { height: 20px; margin-bottom: .5rem; display: flex; align-items: center; }
.mx-badge {
  display: inline-block; line-height: 1;
  font-size: .5625rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--navy); background: var(--cyan);
  padding: .25rem .625rem; border-radius: 999px; white-space: nowrap;
}

/* ---- body ---- */
.mx-group th {
  position: sticky; left: 0;
  padding: .5625rem 1.5rem; text-align: left;
  background: var(--tint-2);
  font-size: .625rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* sticky, so the capability name stays put while the table scrolls sideways */
.mx tbody th.mx-feat {
  position: sticky; left: 0; z-index: 1;
  padding: .75rem 1.5rem; text-align: left; font-weight: 500;
  background: #fff; border-bottom: 1px solid var(--line);
}
.mx-feat b { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); line-height: 1.35; }
.mx-feat span { display: block; font-size: .75rem; color: var(--ink-soft); line-height: 1.45; margin-top: .2rem; }
.mx tbody td {
  padding: .75rem 1rem; text-align: center; vertical-align: middle;
  border-left: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.mx tbody tr:last-child th, .mx tbody tr:last-child td { border-bottom: 0; }

.mx-yes {
  display: inline-grid; place-items: center; width: 22px; height: 22px;
  border-radius: 999px; background: rgba(0, 97, 249, .10); color: var(--blue);
}
.mx-yes svg { width: 13px; height: 13px; }
.mx-no { display: inline-block; width: 10px; height: 1px; background: #cfcdc8; vertical-align: middle; }

/* recommended column — the tint carries the whole way down, so the column
   reads as one unit instead of the emphasis stopping at the header */
.mx .is-rec { background: rgba(0, 97, 249, .045); }
.mx thead th.is-rec { background: var(--navy); border-bottom-color: var(--navy); }
.mx .is-rec .mx-card { border-left-color: var(--navy); }
.mx .is-rec .mx-name { color: #fff; }
.mx .is-rec .mx-for { color: rgba(226, 236, 255, .78); }
.mx .is-rec .mx-price { color: #fff; }
.mx .is-rec .mx-price small { color: rgba(226, 236, 255, .72); }
.mx tbody tr:hover th.mx-feat, .mx tbody tr:hover td { background: rgba(0, 0, 0, .014); }
.mx tbody tr:hover td.is-rec { background: rgba(0, 97, 249, .065); }

.mx-foot {
  display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; align-items: center;
  padding: 1rem 1.5rem; border-top: 1px solid var(--line); background: var(--tint-2);
  font-size: .8125rem; color: var(--ink-soft);
}
.mx-foot .k { display: inline-flex; align-items: center; gap: .45rem; }
.mx-foot .k .mx-yes { width: 18px; height: 18px; }
.mx-foot .k .mx-yes svg { width: 11px; height: 11px; }
.mx-foot__note { margin-left: auto; }


/* --- scroll-spy active state in the "Jump to" menu and the mobile drawer --- */
.jump__menu a.is-active {
  background: #161616; color: #fff; font-weight: 600;
}
.jump__menu a.is-active:hover { background: #161616; color: #fff; }
.drawer a.is-active { color: var(--cyan); }

/* --------------------------------------------------------------------------
   22. Audience cards  (.aud-*)
   The deck frames the three user types as two inside the company (the owner
   who buys, the employees who use it daily) and one outside it (vendors
   selling through the marketplace) — "the only function built for people
   outside the company". The middle card is the one the page previously never
   mentioned at all.
   -------------------------------------------------------------------------- */
.aud { display: grid; gap: clamp(1rem, 2vw, 1.5rem); margin-top: clamp(2rem, 4vw, 3rem); align-items: stretch; }
@media (min-width: 900px) { .aud { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.aud-card {
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid transparent;
  border-radius: 12px;
  padding: clamp(1.5rem, 2.4vw, 2rem);
  position: relative;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.aud-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-card); }

/* a thin rule marks whether this audience sits inside or outside the company */
.aud-card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  border-radius: 12px 12px 0 0;
  background: var(--blue);
}
.aud-card--outside::before { background: var(--cyan); }

.aud-role {
  font-size: var(--fs-eyebrow); font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--blue);
}
.aud-card--outside .aud-role { color: #0090c4; }
.aud-who {
  font-family: var(--font-display); font-size: var(--fs-h3);
  font-weight: 400; letter-spacing: -.02em; color: var(--ink);
  margin-top: .625rem;
}
.aud-desc { margin-top: .75rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6; }
.aud-hr { height: 1px; background: var(--line); margin-block: 1.25rem; }
.aud-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; flex: 1; }
.aud-list li { display: flex; gap: .625rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.45; }
.aud-list svg { flex: none; width: 16px; height: 16px; margin-top: .2em; color: var(--blue); }
.aud-card--outside .aud-list svg { color: #0090c4; }
.aud-card .chev { margin-top: 1.5rem; }

/* --------------------------------------------------------------------------
   23. Device frame  (.dev-*) — the employee's view of the platform

   Replaced `.ph-*`, a phone drawn entirely in CSS and HTML: a fake status bar,
   eight fake app tiles, a fake tab bar. It was ~30 lines of markup pretending
   to be software. This is a real screenshot of the real app in a frame, which
   is both more honest and less code.

   Two pieces of motion, both cheap:
   · the frame settles out of a slight lean as it arrives, so it reads as an
     object in space rather than a picture pasted on the page
   · a slow glint travels across the glass on a long cycle — a device mockup
     that never catches the light looks like a flat rectangle

   No reduced-motion block needed here: section 18 already clamps every
   animation and transition globally under that query.
   -------------------------------------------------------------------------- */
/* The band-head lives INSIDE the left column, not above the split. With the
   heading spanning full width above, the left column held only the checklist —
   much shorter than the 640px phone — so `align-items: center` opened a large
   dead gap between the lead and the first tick. Moving the heading into the
   column makes the two sides comparable in height and lets the phone show at
   full size without the text drifting away from it.
   The head keeps its own `max-width: 820px`, which the column is narrower than
   anyway, so the title's line breaks are unchanged. */
.ph-split { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; margin-top: clamp(2rem, 4vw, 3rem); }
@media (min-width: 900px) { .ph-split { grid-template-columns: minmax(0, 1fr) auto; } }
/* the checklist now follows the heading rather than starting the column */
.ph-split .band-head + .checklist { margin-top: clamp(1.75rem, 3.5vw, 2.75rem); }

.dev {
  width: 300px; max-width: 100%; margin-inline: auto;
  /* on the wrapper, not the frame — a perspective set on the transformed
     element itself flattens the rotation to almost nothing */
  perspective: 1400px;
}
.dev__frame {
  position: relative;
  border-radius: 42px;
  padding: 11px;
  background: linear-gradient(160deg, #2a3350 0%, #0b1330 100%);
  box-shadow: 0 42px 80px -34px rgba(1, 16, 77, .58),
              inset 0 0 0 1px rgba(255, 255, 255, .12);
  transform: rotateY(-11deg) rotateX(4deg);
  transform-origin: 60% 50%;
  transition: transform 1.1s var(--ease-out);
}
/* .in is set by reveal() on .dev itself, so the lean resolves in step with the
   opacity fade that [data-reveal] already handles */
.dev.in .dev__frame { transform: none; }

.dev__screen {
  position: relative;
  border-radius: 32px;
  overflow: hidden;
  background: #0b1330;
  /* the screenshot carries its own status bar, so the frame deliberately has
     no notch of its own — drawing one would double up */
}
.dev__screen img { display: block; width: 100%; height: auto; }

/* side buttons, just enough to read as hardware */
.dev__btn {
  position: absolute; width: 3px; border-radius: 3px;
  background: rgba(255, 255, 255, .17);
}
.dev__btn--power { right: -3px; top: 128px; height: 58px; }
.dev__btn--vol { left: -3px; top: 104px; height: 40px; }

/* the glint. Inset well beyond the screen so the gradient is fully off-frame
   at both ends of the travel, then held still for most of the cycle — a
   constant sweep reads as a loading shimmer, not a reflection. */
.dev__sheen {
  position: absolute; inset: -40% -70%;
  pointer-events: none;
  background: linear-gradient(102deg,
    transparent 40%, rgba(255, 255, 255, .30) 50%, transparent 60%);
  transform: translateX(-125%);
  animation: dev-sheen 9s var(--ease) 1.4s infinite;
}
@keyframes dev-sheen {
  0%        { transform: translateX(-125%); }
  38%, 100% { transform: translateX(125%); }
}

/* --------------------------------------------------------------------------
   24. Checklist
   Used by #team. This class name exists in ../website/css/styles.css too, but
   the two stylesheets are independent — it was never defined here, so with the
   global `svg { display: block }` every tick became a block and pushed its text
   onto the next line. Flex makes the tick a flex item and puts it back inline.
   -------------------------------------------------------------------------- */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.checklist li {
  display: flex; gap: .75rem; align-items: flex-start;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.55;
}
.checklist svg { flex: none; margin-top: .2em; color: var(--blue); }
.checklist b { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------------------
   25. How-it-works page — the ticket thread (.thr-*), .mo, .jb, .ship

   Replaced `.dg`, a box-and-arrow SVG of the operating model. The figure
   described the model's SHAPE; the thread shows it happening — every actor
   appears in the order they actually act, which is the part a reader needs.

   MOTION IS SCROLL-DRIVEN, NOT A ONE-SHOT ENTRANCE
   The first version faded the whole block in once with CSS
   transition-delay staggers. That plays at its own speed regardless of how
   fast you scroll, and does nothing on the way back up. Here:

   · the spine's fill height is a custom property written per frame from the
     reader's position, so the line tracks the scroll exactly
   · each event lights when its own dot crosses a reading line 72% down the
     viewport, and unlights on the way back up — the same reversible pattern
     as .how__step and .hl-card
   · within an event, dot -> time -> body -> chip still stagger, but off the
     event's own state rather than a page-wide timeline

   The result is that scrolling slowly plays it slowly. That is the whole
   difference between "animated" and "animated smoothly".
   -------------------------------------------------------------------------- */
.thr {
  list-style: none; margin: clamp(2.25rem, 4vw, 3.25rem) 0 0; padding: 0 0 0 3.25rem;
  position: relative;
}
/* spine: a hairline with an accent fill scaled from --p (0..1).

   z-index is load-bearing. `::after` on a positioned parent is the LAST child
   in the box tree, so without it the accent fill paints after the <li>s and
   draws straight over every dot. The spine has to sit BEHIND them — the dots
   are opaque precisely so they interrupt the line.

   left 14.75px + 1.5px width centres on 15.5px, which is the middle of the
   31px dot (padding-left 3.25rem, dot offset -3.25rem, so the dot's left edge
   is at 0 and its centre at 15.5). Change the dot size or the padding and this
   number moves with them. */
.thr::before,
.thr::after {
  content: ""; position: absolute; z-index: 0;
  left: 14.75px; top: .75rem; bottom: .75rem; width: 1.5px;
}
.thr::before { background: var(--line); }
.thr::after {
  background: var(--blue);
  transform: scaleY(var(--p, 0)); transform-origin: top;
}

/* above the spine, so the opaque dots mask it */
.thr-ev { position: relative; z-index: 1; padding-block: clamp(1rem, 2vw, 1.5rem); }

/* every animated part shares one transition and reads its own offset from --d,
   so an event's internal rhythm is four numbers, not four rules */
.thr-ev__dot,
.thr-ev__t,
.thr-ev__b,
.thr-ev__chip {
  opacity: 0; transform: translateY(12px);
  transition: opacity .65s var(--ease-out), transform .65s var(--ease-out),
              background-color .5s var(--ease), border-color .5s var(--ease), color .5s var(--ease);
  transition-delay: var(--d, 0ms);
}
.thr-ev.is-on .thr-ev__dot,
.thr-ev.is-on .thr-ev__t,
.thr-ev.is-on .thr-ev__b,
.thr-ev.is-on .thr-ev__chip { opacity: 1; transform: none; }

.thr-ev__dot {
  --d: 0ms;
  position: absolute; left: -3.25rem; top: calc(clamp(1rem, 2vw, 1.5rem) + 1px);
  width: 31px; height: 31px; border-radius: 50%;
  display: grid; place-items: center;
  /* opaque, so it masks the spine running behind it */
  background: var(--paper); border: 1px solid var(--line);
  color: var(--ink-soft);
}
.thr-ev__dot svg { width: 15px; height: 15px; }
.thr-ev.is-on .thr-ev__dot { border-color: var(--blue); color: var(--blue); }
/* the platform's own steps read as the system acting, the owner's as yours */
.thr-ev--sys.is-on .thr-ev__dot { background: var(--navy); border-color: var(--navy); color: var(--cyan); }
.thr-ev--own.is-on .thr-ev__dot { background: var(--blue); border-color: var(--blue); color: #fff; }

.thr-ev__t { --d: 70ms; font-size: var(--fs-xs); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.thr-ev__b { --d: 130ms; margin-top: .375rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6; max-width: 62ch; }
.thr-ev__b b { color: var(--ink); font-weight: 600; }
.thr-ev__chip {
  --d: 200ms;
  display: inline-block; margin-top: .75rem;
  font-size: var(--fs-xs); color: var(--ink-mid);
  background: var(--tint); border-radius: 999px; padding: .3125rem .6875rem;
}

/* Nothing to track without motion: show the thread resolved and fill the
   spine, rather than leaving it half-drawn at whatever --p happened to be. */
@media (prefers-reduced-motion: reduce) {
  .thr::after { transform: scaleY(1); }
  .thr-ev__dot, .thr-ev__t, .thr-ev__b, .thr-ev__chip { opacity: 1; transform: none; }
}




/* --- the ticket queue (.tq-*) -------------------------------------------
   Two example tickets now share one thread component. The chooser is styled
   as a QUEUE — rows you open — rather than as a tab bar, for two reasons: a
   second thread stacked under the first would repeat the shape, and a pill
   switcher is what contact.html already uses. A list of tickets you open is
   what the product itself looks like, so the control is the product.

   Only one thread is on screen at a time, which is what keeps this from
   reading as the same section twice.
   ------------------------------------------------------------------------- */
.tq { margin-top: clamp(2rem, 4vw, 3rem); }

.tq__h {
  margin: 0 0 .875rem;
  font-size: .75rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}

.tq__list { display: grid; gap: .625rem; }
@media (min-width: 900px) { .tq__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* a queue row. The left edge is the selected marker — a 3px rule that grows
   from nothing, so choosing a ticket reads as opening it rather than as
   toggling a control. */
.tq__i {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: .875rem;
  align-items: start; text-align: left;
  padding: clamp(.9375rem, 2vw, 1.125rem);
  font: inherit;
  background: var(--paper);
  border: 1px solid var(--line); border-radius: 16px;
  cursor: pointer;
  transition: border-color .3s var(--ease), box-shadow .4s var(--ease),
              background-color .3s var(--ease);
}
@media (min-width: 620px) {
  .tq__i { grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; }
}
.tq__i::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--blue);
  transform: scaleY(0); transform-origin: center;
  transition: transform .4s var(--ease-out);
}
.tq__i:hover { border-color: rgba(0, 97, 249, .35); }
.tq__i[aria-selected="true"] {
  border-color: transparent;
  background: #fff;
  box-shadow: 0 18px 44px -22px rgba(3, 16, 77, .3);
}
.tq__i[aria-selected="true"]::before { transform: none; }

.tq__ico {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 10px;
  background: var(--tint); color: var(--navy);
  transition: background-color .3s var(--ease), color .3s var(--ease);
}
.tq__ico svg { width: 17px; height: 17px; }
.tq__i[aria-selected="true"] .tq__ico { background: var(--navy); color: #fff; }

.tq__c { min-width: 0; }
.tq__c b { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); line-height: 1.4; }
.tq__c em {
  display: block; margin-top: .25rem;
  font-style: normal; font-size: .75rem; color: var(--ink-soft); line-height: 1.4;
}
.tq__meta {
  grid-column: 2; margin-top: .5rem;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft);
  white-space: nowrap;
}
@media (min-width: 620px) { .tq__meta { grid-column: auto; margin-top: 0; } }
.tq__i[aria-selected="true"] .tq__meta { color: var(--blue); }

/* Same animated-height trick as the contact tabs: the two threads are 6 and 7
   events long, so without it switching yanks everything below the section. */
.tq__stage {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  transition: height .45s var(--ease-out);
}
.tq__p[hidden] { display: none; }
.tq__p {
  opacity: 0; transform: translateY(10px);
  transition: opacity .4s var(--ease-out), transform .4s var(--ease-out);
}
.tq__p[data-on="true"] { opacity: 1; transform: none; }

/* the one-line verdict under each thread — the reason the ticket is here */
.tq__out {
  margin: clamp(1.25rem, 2.5vw, 1.75rem) 0 0;
  padding-left: 3.25rem;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.65; max-width: 62ch;
}
.tq__out b { color: var(--ink); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .tq__i::before, .tq__stage, .tq__p { transition: none; }
}

/* --- the first month: one horizontal axis, driven by scroll --------------
   Every other block on how-it-works.html runs down the page — the ticket
   thread above, the four function columns below, the ships list after them.
   Stacking a fourth vertical list here is what made this section read as a
   repeat. A month is a span, so it gets the page's only horizontal axis:
   `Day one` on the left, `End of month one` on the right, four dots between.

   Nothing here animates on a clock. `--p` (0..1) comes straight from the
   reader's scroll position, so the fill travels at exactly the speed they
   scroll and runs backwards when they scroll up — same contract as `.thr`.
   ------------------------------------------------------------------------- */
.mo { --p: 0; margin-top: clamp(2.25rem, 4.5vw, 3.25rem); }

.mo__ends {
  display: flex; justify-content: space-between;
  margin: 0 0 .875rem;
  font-size: .7rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft);
}

.mo__rail { position: relative; height: 2px; background: var(--line); border-radius: 2px; }
.mo__fill {
  position: absolute; inset: 0;
  background: var(--blue); border-radius: inherit;
  transform: scaleX(var(--p)); transform-origin: left;
}
/* the leading edge. Kept off `.mo__fill` on purpose: that element is scaled,
   so any child of it would be stretched by the same factor. Positioning a
   separate cap from --p keeps it round at every progress value. */
.mo__rail::after {
  content: ""; position: absolute; top: 50%; left: calc(var(--p) * 100%);
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  border-radius: 50%; background: var(--blue);
  box-shadow: 0 0 0 4px rgba(0, 97, 249, .18), 0 0 22px 6px rgba(0, 97, 249, .55);
  opacity: 0; transition: opacity .45s var(--ease);
}
.mo.is-live .mo__rail::after { opacity: 1; }

.mo__steps { list-style: none; margin: -1px 0 0; padding: 0; display: grid; }

.mo-step { position: relative; }

/* dot sits ON the rail: the list's -1px top margin puts each step's top edge
   at the rail's centre line, so a -50% shift is all the centring needed */
.mo-step__dot {
  position: absolute; top: 0; left: 0;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--paper); border: 2px solid var(--line);
  transform: translateY(-50%) scale(.7);
  transition: transform .55s var(--ease-out), background-color .55s var(--ease),
              border-color .55s var(--ease), box-shadow .55s var(--ease);
}
.mo-step.is-on .mo-step__dot {
  background: var(--blue); border-color: var(--blue);
  transform: translateY(-50%) scale(1);
  box-shadow: 0 0 0 6px rgba(0, 97, 249, .12);
}

/* the drop from dot to copy — draws itself as the step lights */
.mo-step__stem {
  position: absolute; top: .625rem; left: 5.25px;
  width: 1.5px; height: 1.875rem; background: var(--line);
  transform: scaleY(0); transform-origin: top;
  transition: transform .6s var(--ease-out), background-color .6s var(--ease);
}
.mo-step.is-on .mo-step__stem { transform: scaleY(1); background: var(--blue); }

.mo-step__c {
  opacity: .32; transform: translateY(16px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.mo-step.is-on .mo-step__c { opacity: 1; transform: none; }

.mo-step__tag {
  display: block; font-size: .72rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--blue);
}
.mo-step h3 { margin-top: .5rem; font-size: var(--fs-h3); max-width: 18ch; }
.mo-step p {
  margin-top: .75rem; font-size: var(--fs-sm);
  color: var(--ink-mid); line-height: 1.65; max-width: 42ch;
}

@media (min-width: 900px) {
  .mo__steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(1.25rem, 2.6vw, 2.25rem); }

  /* A descending cascade, not a flat row. #jobs directly below is also four
     columns, and two flat 4-column grids back to back are exactly the
     repetition this section was redesigned to escape. Stepping each block
     lower reads as forward motion through the month and gives each dot its
     own drop length off the axis. */
  .mo-step { padding-top: calc(3rem + var(--i, 0) * 1.75rem); }
  .mo-step__stem { height: calc(1.875rem + var(--i, 0) * 1.75rem); }
  /* Two of the four headings wrap to a second line at this width. Left
     alone, a one-line heading pulls its paragraph up and breaks the
     cascade's even rhythm, so every heading reserves two lines. */
  .mo-step h3 { min-height: 2.44em; }        /* 2 x the 1.22 line-height */
  @supports (min-height: 2lh) { .mo-step h3 { min-height: 2lh; } }
}

/* Stacked: the long rail is meaningless once the dots share an x, so it goes
   and each step carries its own hairline. No vertical spine — that is the
   ticket thread's shape and it is directly above this. */
@media (max-width: 899.98px) {
  .mo__ends, .mo__rail { display: none; }
  /* Padding both sides of the hairline, not just below it: with a top-only
     value each rule butts straight onto the paragraph above and crosses its
     last line. The last step drops the tail so the band keeps its own rhythm. */
  .mo-step { padding: clamp(1.5rem, 5vw, 2rem) 0; }
  .mo-step:last-child { padding-bottom: 0; }
  .mo-step::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0;
    height: 1px; background: var(--line);
  }
  .mo-step__stem { display: none; }
  .mo-step__dot { top: 0; }
}

/* No scroll story for readers who asked for none — everything simply on. */
@media (prefers-reduced-motion: reduce) {
  .mo__fill { transform: scaleX(1); }
  .mo-step__dot { transform: translateY(-50%) scale(1); }
  .mo-step__stem { transform: scaleY(1); }
  .mo-step__c { opacity: 1; transform: none; }
}
/* --- the four jobs (.jb) -----------------------------------------------
   Replaced `.fn`, which listed four of the twenty functions in detail. Both
   `#ships` on this page and `#platform` on the landing page already list all
   twenty, so that section was a third enumeration on one reader journey.

   ALIGNMENT IS THE POINT HERE. The copy wraps unevenly — "Keep people working"
   takes two lines where "Reduce the risk" takes one — so each card is a grid
   with FOUR NAMED ROWS of its own, and `grid-template-rows` on the list makes
   those rows share a track height across all four columns. Heading, paragraph
   and function line therefore start on the same baseline in every column,
   whatever the text does. Reserving a min-height per element (what `.fn` did)
   only ever fixes the one case you measured.
   ------------------------------------------------------------------------- */
.jb {
  list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0;
  display: grid; gap: clamp(1.5rem, 3vw, 2.25rem);
}
@media (min-width: 760px)  { .jb { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1180px) { .jb { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

@media (min-width: 760px) {
  .jb { align-items: stretch; }
  .jb__i { display: grid; grid-template-rows: auto auto 1fr auto; }
}

/* SUBGRID is what actually shares the row tracks between the cards. Without
   it each card is its own grid, so a two-line heading pushes only ITS
   paragraph down — measured 29px out of line against the other three. With
   it, photo / heading / paragraph / function line each get one track sized by
   the tallest card, and all four columns agree.

   Behind @supports because the fallback is acceptable rather than broken:
   headings still align at the top and the function lines are still pinned to
   the bottom by `align-self: end`; only the paragraph floats. */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 1180px) {
    .jb { grid-template-rows: repeat(4, auto); }
    .jb__i { grid-row: span 4; grid-template-rows: subgrid; }
  }
  /* two across: four tracks per card row, so eight in total */
  @media (min-width: 760px) and (max-width: 1179.98px) {
    .jb { grid-template-rows: repeat(8, auto); }
    .jb__i { grid-row: span 4; grid-template-rows: subgrid; }
  }
}

.jb__i {
  --d: 0ms;
  opacity: 0; transform: translateY(24px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
  transition-delay: var(--d);
}
.jb.is-in .jb__i { opacity: 1; transform: none; }
.jb__i:nth-child(2) { --d: 110ms; }
.jb__i:nth-child(3) { --d: 220ms; }
.jb__i:nth-child(4) { --d: 330ms; }

/* --- the photograph --------------------------------------------------- */
.jb__ph {
  display: block; position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 14px; overflow: hidden;
  background: var(--navy);
  margin-bottom: clamp(.875rem, 2vw, 1.125rem);
}
.jb__ph img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  /* settles from slightly over-scaled rather than fading — the frame stays
     put and only the picture inside it moves, which is what stops four
     photographs arriving at once from feeling busy */
  transform: scale(1.08);
  transition: transform 1.1s var(--ease-out);
  transition-delay: var(--d);
}
.jb.is-in .jb__i img { transform: scale(1); }
/* a slow push-in on hover, in the same direction the reveal came from */
@media (hover: hover) {
  .jb__ph:hover img, .jb__i:hover img { transform: scale(1.05); transition-duration: .7s; }
}

/* the job name, sitting in the corner of its own photograph */
.jb__ph b {
  position: absolute; left: 0; bottom: 0; z-index: 1;
  background: #fff; color: var(--navy);
  font-family: var(--font-display); font-weight: 400;
  font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  padding: .375rem .6875rem;
  border-radius: 0 12px 0 0;
  /* rides in from under the photo edge, after its picture has settled */
  transform: translateY(100%);
  transition: transform .7s var(--ease-spring);
  transition-delay: calc(var(--d) + 260ms);
}
.jb.is-in .jb__i .jb__ph b { transform: none; }

/* --- the copy ---------------------------------------------------------- */
.jb__i h3 { font-size: var(--fs-h3); }
.jb__i p {
  margin-top: .625rem;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6;
}
/* pinned to the card's last row, so the four function lines sit on one line
   across the row no matter how long the paragraph above each one runs */
.jb__i em {
  align-self: end;
  margin-top: clamp(.875rem, 2vw, 1.125rem);
  padding-top: .75rem;
  border-top: 1px solid var(--line);
  font-style: normal; font-size: .75rem; line-height: 1.5;
  letter-spacing: .01em; color: var(--ink-soft);
}

/* Reveal and settle are the whole effect, so with motion off everything is
   simply already in place. */
@media (prefers-reduced-motion: reduce) {
  .jb__i { opacity: 1; transform: none; transition: none; }
  .jb__ph img, .jb__ph b { transform: none; transition: none; }
}
/* and the same for JS off, since nothing would add .is-in */
.no-js .jb__i { opacity: 1; transform: none; }
.no-js .jb__ph img, .no-js .jb__ph b { transform: none; }

/* --- what ships first, on the dark band (.ship) --------------------------
   All twenty functions in ONE flat numbered list, in build-priority order:
   01–07 the day-one set, 08–11 the enforcement wave, 12–20 the remainder.
   The order is the argument, so 01–20 running unbroken is what carries it —
   an earlier draft split this into three labelled groups and that read as
   three separate sections rather than one ranking.
   ------------------------------------------------------------------------- */
.ship {
  list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0;
  counter-reset: s;
  display: grid; gap: 0;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
@media (min-width: 900px) { .ship { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(2rem, 4vw, 3.5rem); } }
.ship li {
  counter-increment: s;
  display: grid; grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: .25rem 1rem; align-items: baseline;
  padding-block: 1.125rem;
  border-bottom: 1px solid rgba(255, 255, 255, .14);
}
/* decimal-leading-zero, NOT a literal "0" prefix. The original rule was
   `content: "0" counter(s)`, which was correct for seven items and would
   print "010" onwards now that the list runs to twenty. */
.ship li::before {
  content: counter(s, decimal-leading-zero);
  grid-row: span 2;
  font-family: var(--font-display); font-size: .9375rem; font-weight: 300;
  color: var(--cyan);
}
.ship b { font-size: var(--fs-h3); font-family: var(--font-display); font-weight: 400; color: #fff; letter-spacing: -.02em; }
.ship span { font-size: var(--fs-sm); color: rgba(226, 236, 255, .74); line-height: 1.5; }
.ship__note {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  font-size: var(--fs-sm); color: rgba(226, 236, 255, .74);
  line-height: 1.6; max-width: 70ch;
}
.ship__note b { color: #fff; font-weight: 600; }

/* ==========================================================================
   26. CONTACT PAGE (.ce-* / .cf-*)  contact.html
   One enquiry form, one inbox. Replaced `.ct-*`, which was a centred hero,
   one form and three cards — two of which were "to be supplied" placeholders.

   It was tabbed by audience for a while (buyer / vendor / referral partner,
   three sets of fields and three recipients). The vendor and referral routes
   were removed on the product owner's instruction, and one tab is not a tab
   — so `.ce__tabs`, `.ce__tab`, `.ce__stage`, `.ce__p` and ceTabs() were all
   deleted with them. The sliding-pill technique they used is not lost: it is
   still on `.nav__links::after` and `.mp__tabs::before`, both documented.
   ========================================================================== */

/* --- the card -----------------------------------------------------------
   The card is the form's only frame — `.cf` deliberately has no border of its
   own, or a second box inside this one would read as a box in a box. The
   reveal is the generic `[data-reveal]` on the wrapper, so there is no
   per-panel opacity state to manage any more. */
.ce { margin-top: clamp(2rem, 4vw, 3rem); }

.ce__card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  box-shadow: var(--shadow-card);
}

.ce__route {
  display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: .75rem;
  margin: 0 0 clamp(1.25rem, 2.5vw, 1.75rem);
  padding-bottom: clamp(1rem, 2vw, 1.375rem);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6;
}
.ce__route svg { width: 18px; height: 18px; margin-top: .2em; color: var(--blue); }
.ce__route b { color: var(--ink); font-weight: 600; }

/* --- the form ----------------------------------------------------------
   No card of its own any more: the panel IS the card, so a second border
   inside it would read as a box in a box. */
.cf { display: grid; gap: 1.125rem; }
.cf__row { display: grid; gap: 1.125rem; }
@media (min-width: 620px) { .cf__row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.cf__f { margin: 0; display: grid; gap: .4375rem; }
.cf__f label { font-size: .8125rem; font-weight: 600; color: var(--ink); letter-spacing: .01em; }
/* the mark is aria-hidden in the markup — the input carries the real
   `required`, so this must never be the only signal */
.cf__f label b { color: var(--blue); font-weight: 600; }

.cf input, .cf select, .cf textarea {
  width: 100%; font: inherit; font-size: var(--fs-sm); color: var(--ink);
  background: var(--tint);
  border: 1px solid transparent; border-radius: 12px;
  padding: .8125rem .9375rem;
  transition: background-color .25s var(--ease), border-color .25s var(--ease),
              box-shadow .25s var(--ease);
}
.cf textarea { resize: vertical; min-height: 7rem; line-height: 1.6; }
.cf ::placeholder { color: var(--ink-soft); opacity: 1; }
.cf input:hover, .cf select:hover, .cf textarea:hover { border-color: var(--line); }
.cf input:focus-visible, .cf select:focus-visible, .cf textarea:focus-visible {
  outline: none; background: #fff; border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(0, 97, 249, .14);
}
/* :user-invalid, not :invalid — the latter paints every required field red
   before the reader has typed a character */
.cf input:user-invalid, .cf textarea:user-invalid, .cf select:user-invalid {
  border-color: #d33; background: rgba(211, 51, 51, .04);
}
/* iOS Safari zooms the page when a focused control is under 16px, and this
   site's root font size is fluid — it resolves under 16 at phone widths, so
   1rem would not clear the threshold either. Absolute value on purpose. */
@media (max-width: 899.98px) {
  .cf input, .cf select, .cf textarea { font-size: 16px; }
}

.cf__sel { position: relative; display: block; }
.cf__sel select { appearance: none; padding-right: 2.5rem; cursor: pointer; }
.cf__sel svg {
  position: absolute; right: .9375rem; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--ink-soft); pointer-events: none;
}

.cf__hint { margin: -.375rem 0 0; font-size: .8125rem; color: var(--ink-soft); }
.cf__go { width: 100%; justify-content: center; margin-top: .25rem; }
@media (min-width: 620px) { .cf__go { width: auto; justify-self: start; padding-inline: 2rem; } }

.cf__msg { margin: 0; font-size: var(--fs-sm); line-height: 1.5; }
.cf__msg:empty { display: none; }
.cf__msg--ok { color: #0a7d3c; }
.cf__msg--err { color: #c22; }

/* --- direct contact ---------------------------------------------------- */
.ce-d {
  display: grid; gap: clamp(1rem, 2vw, 1.25rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}
/* Three cards, not four — the vendor one went with the vendor form. 2-up is
   deliberately skipped: three items in two columns leaves an orphan on row
   two, and below 700px three columns give each card under 170px, which is
   narrower than "Singapore — serving Singapore and Southeast Asia" needs. So
   it stacks until there is room for all three abreast. */
@media (min-width: 700px) { .ce-d { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.ce-d__i {
  --d: 0ms;
  background: var(--paper);
  border: 1px solid var(--line); border-radius: 18px;
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  opacity: 0; transform: translateY(18px);
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out),
              border-color .35s var(--ease), box-shadow .45s var(--ease);
  transition-delay: var(--d);
}
.ce-d.is-in .ce-d__i { opacity: 1; transform: none; }
.ce-d__i:nth-child(2) { --d: 90ms; }
.ce-d__i:nth-child(3) { --d: 180ms; }
.ce-d__i:hover { border-color: transparent; box-shadow: 0 18px 44px -20px rgba(3, 16, 77, .26); }
.no-js .ce-d__i { opacity: 1; transform: none; }

.ce-d__ico {
  display: grid; place-items: center;
  width: 40px; height: 40px; margin-bottom: .875rem;
  border-radius: 12px; background: var(--tint); color: var(--navy);
}
.ce-d__ico svg { width: 18px; height: 18px; }
.ce-d__i h3 { font-size: 1rem; }
.ce-d__i p { margin-top: .375rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.55; }
.ce-d__i a { color: var(--ink-mid); }
.ce-d__i a:hover { color: var(--blue); }

/* --- the free-plan aside ----------------------------------------------- */
.ce-free {
  display: grid; gap: 1rem;
  margin-top: clamp(1.5rem, 3vw, 2rem);
  padding: clamp(1.5rem, 3.5vw, 2.25rem);
  border-radius: 20px;
  background: linear-gradient(135deg, #0a2a6b, var(--navy));
  color: #fff;
}
@media (min-width: 900px) {
  .ce-free { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) auto; align-items: center; gap: 2rem; }
}
.ce-free__n { margin: 0; display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; }
.ce-free__n b {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2.25rem, 5vw, 3rem); line-height: 1; letter-spacing: -.03em;
}
.ce-free__n span {
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--cyan);
}
.ce-free__b { margin: 0; font-size: var(--fs-sm); color: rgba(226, 236, 255, .85); line-height: 1.65; }
.ce-free__c { margin: 0; }
.ce-free__c .btn { border-color: #fff; color: #fff; }
.ce-free__c .btn:hover { background: #fff; color: var(--navy); }

/* --- what happens next -------------------------------------------------
   Scroll-driven, like the thread and the first-month axis: each step lights
   as it crosses a reading line and unlights on the way back up. */
.ce-s {
  list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0;
  counter-reset: cs;
  display: grid; gap: 0;
  border-top: 1px solid var(--line);
}
@media (min-width: 900px) {
  .ce-s { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: clamp(1.5rem, 3vw, 2.5rem); }
}
.ce-s__i {
  counter-increment: cs;
  position: relative;
  padding-block: clamp(1.5rem, 3vw, 2rem);
  border-bottom: 1px solid var(--line);
}
/* the rule fills to blue as the step lights, so the row reads as progress */
.ce-s__i::after {
  content: ""; position: absolute; left: 0; right: 0; top: -1px;
  height: 2px; background: var(--blue);
  transform: scaleX(0); transform-origin: left;
  transition: transform .7s var(--ease-out);
}
.ce-s__i.is-on::after { transform: none; }
.ce-s__i::before {
  content: counter(cs, decimal-leading-zero);
  display: block;
  font-family: var(--font-display); font-size: .875rem; font-weight: 300;
  color: var(--ink-soft);
  transition: color .5s var(--ease);
}
.ce-s__i.is-on::before { color: var(--blue); }
.ce-s__i h3 { margin-top: .5rem; font-size: var(--fs-h3); }
.ce-s__i p { margin-top: .625rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6; }
.ce-s__i > * { position: relative; }
.no-js .ce-s__i::after { transform: none; }

/* Motion off: the cards are already up and every step is already lit. */
@media (prefers-reduced-motion: reduce) {
  .ce-d__i { opacity: 1; transform: none; transition: none; }
  .ce-s__i::after { transform: none; transition: none; }
  .ce-s__i::before { color: var(--blue); }
}



/* ==========================================================================
   27. WHY SAVVYTECH (.wy-*)  why.html
   Content is from the investor deck: slide 2 (the problem and its two cost
   figures), slide 5 (the solution and "why it's different"), slide 19's
   customer-facing half, slide 23 (the four alternatives). Market sizing,
   financials and the named-competitor slides are excluded, per the
   content-scope rule at the top of this file's notes.

   Every block here is a shape used nowhere else on the site. That is a hard
   requirement, not a preference: this page sits one nav click from the landing
   page and how-it-works, and reusing a card grid or a matrix would make the
   three read as one long page.
   ========================================================================== */

/* --- the cost comparison (.wy-cmp) -------------------------------------
   Replaced two side-by-side statistics. Those gave equal visual weight to two
   facts that are not equal and, worse, left the reader to do the comparison
   themselves — the page's whole argument is the RATIO between what the only
   formal alternative costs and what this costs, so the chart states it.

   One shared 0–250K/year axis, three bars, real figures only: US$140–230K from
   the deck, and $3,000 / $300 a month from pricing.html annualised. Both are
   USD, which that page states, so they belong on one axis.

   All three bars read the same --p that the counting figure does, so they grow
   together and the contrast is what animates: the hire bar keeps travelling
   long after the other two have stopped.
   ------------------------------------------------------------------------- */
.wy-cmp {
  --p: 1;
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: clamp(1.75rem, 3.5vw, 2.5rem);
  border-top: 1px solid var(--line);
}

.wy-cmp__h {
  margin: 0 0 clamp(1.5rem, 3vw, 2rem);
  font-size: .75rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}

.wy-cmp__rows { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(1rem, 2.2vw, 1.5rem); }

.wy-cmp__row {
  display: grid; gap: .5rem 1.25rem;
  align-items: center;
  grid-template-areas: "lab val" "bar bar";
  grid-template-columns: minmax(0, 1fr) auto;
}
@media (min-width: 760px) {
  .wy-cmp__row {
    grid-template-areas: "lab bar val";
    /* the value column has to hold "US$140-230K" at the hire row's larger
       size without clipping - measured at 229px, so 15rem */
    grid-template-columns: minmax(0, 12rem) minmax(0, 1fr) minmax(0, 15rem);
  }
}

.wy-cmp__lab {
  grid-area: lab;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink); line-height: 1.35;
}
.wy-cmp__lab em {
  display: block; margin-top: .1875rem;
  font-style: normal; font-size: .75rem; font-weight: 400; color: var(--ink-soft);
}

.wy-cmp__bar {
  grid-area: bar; position: relative; display: block;
  height: 10px; border-radius: 999px; background: var(--line);
}
.wy-cmp__bar i {
  position: absolute; top: 0; bottom: 0;
  left: calc(var(--a) * 100%);
  /* --p is the same eased progress the digits use, written per frame by
     wyStat(), so bar and number can never drift apart */
  width: calc((var(--b) - var(--a)) * var(--p, 1) * 100%);
  /* Growth is 1.44% of the axis — about 4px. That IS the point, but under
     3px it reads as a rendering fault rather than a number. */
  min-width: 3px;
  border-radius: 999px;
  background: var(--blue);
}
/* the alternative is the one that should feel heavy */
.wy-cmp__row--hire .wy-cmp__bar i {
  background: linear-gradient(90deg, #0a2a6b, var(--navy));
  box-shadow: 0 6px 18px -8px rgba(3, 16, 77, .55);
}

.wy-cmp__v {
  grid-area: val;
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.125rem, 2.4vw, 1.5rem); line-height: 1;
  letter-spacing: -.02em; color: var(--ink-soft);
  white-space: nowrap;
  /* the digits change every frame while counting, and proportional figures
     make the whole line jitter as they do */
  font-variant-numeric: tabular-nums;
}
@media (min-width: 760px) { .wy-cmp__v { text-align: right; } }
.wy-cmp__row--hire .wy-cmp__v {
  font-size: clamp(1.375rem, 3vw, 2rem); font-weight: 200; color: var(--navy);
}

.wy-cmp__ax {
  display: flex; justify-content: space-between;
  margin: .875rem 0 0;
  font-size: .6875rem; letter-spacing: .06em; color: var(--ink-soft);
}
@media (min-width: 760px) {
  /* line the axis up with the bars, not with the labels */
  .wy-cmp__ax { margin-left: calc(12rem + 1.25rem); margin-right: calc(15rem + 1.25rem); }
}

.wy-cmp__note {
  margin: clamp(1.25rem, 2.5vw, 1.75rem) 0 0;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1.25rem;
  font-size: var(--fs-sm); color: var(--ink-mid);
}
.wy-cmp__note b { color: var(--ink); font-weight: 600; }

/* --- the blind-spot figure, supporting rather than co-equal ------------- */
.wy-blind {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding: clamp(1.25rem, 2.5vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
  background: var(--tint); border-radius: 18px;
  display: grid; gap: .5rem 2rem;
}
@media (min-width: 820px) { .wy-blind { grid-template-columns: minmax(0, 14rem) minmax(0, 1fr); align-items: center; } }
.wy-blind__n { margin: 0; display: flex; align-items: baseline; gap: .625rem; flex-wrap: wrap; }
.wy-blind__n b {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2rem, 4.4vw, 2.75rem); line-height: 1;
  letter-spacing: -.03em; color: var(--navy);
}
.wy-blind__n span {
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--blue);
}
.wy-blind__b { margin: 0; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.65; }

/* --- the three symptoms, as one low-weight row ------------------------- */
.wy-tri {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .625rem 1rem;
  margin: clamp(1.5rem, 3vw, 2rem) 0 0;
}
.wy-tri span {
  padding: .375rem .75rem;
  border: 1px solid var(--line); border-radius: 999px;
  font-size: .8125rem; color: var(--ink-mid);
}
.wy-tri .chev { margin-left: auto; }

/* --- the four alternatives ---------------------------------------------
   Deliberately NOT the pricing page's .mx matrix. Each alternative is one
   row split into what it offers and what changes here, and the second half
   slides in from scroll position so the row reads left to right as an
   argument rather than as a table to scan. */
.wy-alt {
  list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0;
  counter-reset: wa;
  border-top: 1px solid var(--line);
}
.wy-alt__row {
  counter-increment: wa;
  display: grid; gap: .5rem clamp(1.5rem, 3vw, 2.5rem);
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-bottom: 1px solid var(--line);
}
@media (min-width: 940px) {
  .wy-alt__row { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
}
.wy-alt__t {
  font-size: 1.125rem; margin: 0;
  display: flex; gap: .625rem; align-items: baseline;
}
.wy-alt__t::before {
  content: counter(wa, decimal-leading-zero);
  flex: none;
  font-size: .8125rem; font-weight: 300; color: var(--ink-soft);
}
.wy-alt__a, .wy-alt__b {
  margin: 0; font-size: var(--fs-sm); line-height: 1.6;
}
.wy-alt__a span, .wy-alt__b span {
  display: block; margin-bottom: .3125rem;
  font-size: .6875rem; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase;
}
.wy-alt__a { color: var(--ink-soft); }
.wy-alt__a span { color: var(--ink-soft); }
/* the win side: ink, not soft, and it arrives second */
.wy-alt__b { color: var(--ink); }
.wy-alt__b span { color: var(--blue); }
.wy-alt__b {
  opacity: 0; transform: translateX(14px);
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
.wy-alt__row.is-on .wy-alt__b { opacity: 1; transform: none; }

.wy-gap {
  margin: clamp(1.75rem, 3vw, 2.5rem) 0 0; max-width: 62ch;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.65;
}
.wy-gap b { color: var(--ink); font-weight: 600; }

/* --- the three capabilities --------------------------------------------
   A rule that draws down the left of each row as it is reached. Vertical, but
   per-row and short — not the how-it-works thread's single continuous spine. */
.wy-cap {
  list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 0;
  display: grid; gap: clamp(1.25rem, 2.5vw, 2rem);
}
@media (min-width: 900px) { .wy-cap { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.wy-cap li { position: relative; padding-left: clamp(1rem, 2vw, 1.5rem); }
.wy-cap li::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px; background: var(--line);
}
.wy-cap li::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px; background: var(--blue);
  transform: scaleY(0); transform-origin: top;
  transition: transform .7s var(--ease-out);
}
.wy-cap li:nth-child(2)::after { transition-delay: 110ms; }
.wy-cap li:nth-child(3)::after { transition-delay: 220ms; }
.wy-cap.is-in li::after { transform: scaleY(1); }
.wy-cap h3 { font-size: var(--fs-h3); max-width: 18ch; }
.wy-cap p {
  margin-top: .75rem; font-size: var(--fs-sm);
  color: var(--ink-mid); line-height: 1.65;
}

/* --- why it's different ------------------------------------------------ */
.wy-diff {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  background: var(--tint);
  border-radius: 20px;
}
.wy-diff__h {
  font-size: .75rem; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}
.wy-diff ul {
  list-style: none; margin: 1.25rem 0 0; padding: 0;
  display: grid; gap: .875rem;
}
@media (min-width: 900px) { .wy-diff ul { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1.25rem, 2.5vw, 2rem); } }
/* grid, not flex: the label is a block so it never splits from its own
   explanation mid-phrase, and the tick has to stay on the first line rather
   than centring against a two-line item */
.wy-diff li {
  display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: .25rem .625rem;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.6;
}
.wy-diff li b { color: var(--ink); font-weight: 600; grid-column: 2; }
.wy-diff li span { grid-column: 2; }
.wy-diff li svg { width: 16px; height: 16px; margin-top: .2em; color: var(--blue); grid-row: 1; }
.wy-diff__more {
  display: flex; flex-wrap: wrap; gap: .75rem;
  margin: clamp(1.5rem, 3vw, 2rem) 0 0;
}

/* --- who it fits, on the navy band ------------------------------------- */
.wy-fit {
  display: grid; gap: clamp(1rem, 2vw, 1.25rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}
@media (min-width: 820px) { .wy-fit { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.wy-fit__i {
  position: relative;
  padding: clamp(1.5rem, 3vw, 2rem);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 18px;
  background: rgba(255, 255, 255, .035);
  transition: border-color .35s var(--ease), background-color .35s var(--ease);
}
.wy-fit__i:hover { border-color: rgba(122, 214, 255, .5); background: rgba(255, 255, 255, .07); }
/* the two strong-fit bands are marked, so "who this is really for" is legible
   without reading all three */
.wy-fit__i--on { border-color: rgba(122, 214, 255, .34); background: rgba(122, 214, 255, .06); }
.wy-fit__n {
  margin: 0;
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2.25rem, 5vw, 3.25rem); line-height: 1;
  letter-spacing: -.03em; color: var(--cyan);
}
.wy-fit__i h3 { margin-top: .75rem; font-size: 1.0625rem; color: #fff; }
.wy-fit__b {
  margin-top: .625rem; font-size: var(--fs-sm);
  color: rgba(226, 236, 255, .74); line-height: 1.6;
}
.wy-fit__tag {
  position: absolute; top: clamp(1.5rem, 3vw, 2rem); right: clamp(1.5rem, 3vw, 2rem);
  font-size: .625rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--navy);
  background: var(--cyan); border-radius: 999px;
  padding: .25rem .5rem;
}

/* The count-up and the two slide-ins are the only motion here, and all three
   are position-driven rather than timed, so with motion off everything simply
   sits in its finished state. */
@media (prefers-reduced-motion: reduce) {
  .wy-alt__b { opacity: 1; transform: none; transition: none; }
  .wy-cap li::after { transform: scaleY(1); transition: none; }
  .wy-tri li::before { transform: none; transition: none; }
}


/* ==========================================================================
   28. LEGAL PAGES (.lgl-*)  privacy.html, terms.html
   Deliberately the plainest thing on the site. Legal copy is read by someone
   looking for one clause, so the job is a clear hierarchy and a contents list
   that jumps — not a treatment. No reveal choreography beyond the standard
   [data-reveal], and nothing scroll-driven.
   ========================================================================== */
.lgl-meta { margin-top: 1.25rem; font-size: var(--fs-sm); color: var(--ink-soft); }

/* the draft banner. Loud on purpose — these pages carry [square brackets] that
   must not reach production unnoticed. */
.lgl-note {
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  padding: clamp(1rem, 2.5vw, 1.375rem) clamp(1.125rem, 3vw, 1.625rem);
  border-left: 3px solid var(--blue);
  border-radius: 0 12px 12px 0;
  background: rgba(0, 97, 249, .06);
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.65;
}
.lgl-note b { color: var(--ink); font-weight: 600; }

.lgl-toc {
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  padding: clamp(1.25rem, 3vw, 1.75rem);
  background: var(--tint); border-radius: 16px;
}
.lgl-toc__h {
  margin: 0 0 .875rem;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}
.lgl-toc ol {
  margin: 0; padding: 0; list-style: none;
  counter-reset: lt;
  display: grid; gap: .5rem;
}
@media (min-width: 700px) { .lgl-toc ol { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem 2rem; } }
.lgl-toc li { counter-increment: lt; display: flex; gap: .625rem; font-size: var(--fs-sm); }
.lgl-toc li::before {
  content: counter(lt); flex: none; min-width: 1.25rem;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.lgl-toc a { color: var(--ink-mid); }
.lgl-toc a:hover { color: var(--blue); }

.lgl { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.lgl__s { padding-top: clamp(2rem, 4vw, 2.75rem); }
.lgl__s + .lgl__s { border-top: 1px solid var(--line); }
/* scroll-margin so a contents jump does not tuck the heading under the nav —
   the page-level scroll-padding covers hash loads, this covers the rest */
.lgl__s { scroll-margin-top: calc(var(--nav-h) + var(--frame-inset) * 2 + 1.5rem); }

.lgl h2 { font-size: var(--fs-h3); }
.lgl h3 {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  font-size: 1rem; color: var(--ink);
}
.lgl p { margin-top: 1rem; font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.75; max-width: 68ch; }
.lgl h2 + p, .lgl h3 + p { margin-top: .75rem; }
.lgl ul { margin: 1rem 0 0; padding: 0; list-style: none; display: grid; gap: .75rem; max-width: 68ch; }
.lgl li {
  position: relative; padding-left: 1.125rem;
  font-size: var(--fs-sm); color: var(--ink-mid); line-height: 1.7;
}
.lgl li::before {
  content: ""; position: absolute; left: 0; top: .65em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--line);
}
.lgl b { color: var(--ink); font-weight: 600; }
.lgl a { color: var(--blue); text-decoration: underline; text-underline-offset: 2px; }
.lgl__last { margin-top: 1.5rem; padding-bottom: .5rem; color: var(--ink-soft); }


/* ==========================================================================
   29. MOBILE PRICING (.mp-*)  pricing.html, <= 860px only
   The desktop table is untouched. Below 860px it used to become a 720px grid
   inside a nested scroller capped at 82vh — two scroll axes inside the page's
   own, with about half a column visible at 375px. This replaces it.

   ONE list of fifteen rows, not three. The tiers are a strict ladder, so
   switching plan only re-marks rows rather than rebuilding the list — which is
   precisely what lets the marks animate their change. `data-g` (group index)
   plus the selected tier is the whole inclusion rule.
   ========================================================================== */
.mp { display: none; }
@media (max-width: 860px) {
  .mp { display: block; margin-top: clamp(1.5rem, 4vw, 2rem); }
  /* the desktop matrix and its scroller go entirely — not merely shrunk */
  .mx-wrap { display: none; }
}

/* --- the switcher ------------------------------------------------------
   Same sliding pill as the nav underline and the contact tabs, and measured
   the same way: getBoundingClientRect minus the strip's border, on BOTH axes,
   because three plan names can wrap to two rows on a narrow phone. */
.mp__tabs {
  position: relative;
  display: flex; gap: .25rem;
  padding: .25rem;
  border: 1px solid var(--line); border-radius: 999px;
}
.mp__tabs::before {
  content: ""; position: absolute; z-index: 0; top: 0; left: 0;
  width: var(--mp-w, 0); height: var(--mp-h, 0);
  transform: translate(var(--mp-x, 0), var(--mp-y, 0));
  background: var(--navy); border-radius: 999px;
  transition: transform .45s var(--ease-spring), width .45s var(--ease-spring),
              height .45s var(--ease-spring);
  opacity: 0;
}
.mp__tabs[data-ready]::before { opacity: 1; }
/* until measured, the selected tab carries its own fill so the strip is never
   in a broken half-state */
.mp__tabs:not([data-ready]) .mp__tab[aria-checked="true"] { background: var(--navy); color: #fff; }
.mp__tab {
  position: relative; z-index: 1; flex: 1;
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  padding: .625rem .25rem;
  border: 0; border-radius: 999px; background: transparent;
  color: var(--ink-mid); cursor: pointer;
  transition: color .3s var(--ease);
}
/* The pill takes .45s to arrive but the label only .3s to turn white, so for
   about 150ms the incoming label was white on white. Hold it dark until the
   pill is most of the way there. The outgoing label has no delay — the pill is
   leaving it, so grey is already the right answer. */
.mp__tab[aria-checked="true"] { color: #fff; transition-delay: .2s; }

/* --- the selected plan ------------------------------------------------- */
.mp__head {
  margin-top: clamp(1.25rem, 3vw, 1.75rem);
  padding: clamp(1.25rem, 3.5vw, 1.75rem);
  border-radius: 18px; background: var(--tint);
}
.mp__badge {
  display: inline-block; margin: 0 0 .625rem;
  font-size: .625rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--navy); background: var(--cyan);
  padding: .25rem .5rem; border-radius: 999px;
}
.mp__badge[hidden] { display: none; }
.mp__name { margin: 0; font-family: var(--font-display); font-weight: 500; font-size: 1.125rem; }
.mp__for { margin: .25rem 0 0; font-size: var(--fs-sm); color: var(--ink-soft); }
.mp__price {
  margin: .75rem 0 0;
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2.25rem, 9vw, 2.75rem); line-height: 1;
  letter-spacing: -.03em; color: var(--navy);
  /* the figure counts between plans, and proportional digits make the whole
     line jitter while it does */
  font-variant-numeric: tabular-nums;
}
.mp__price small { font-size: .8125rem; font-weight: 400; color: var(--ink-soft); letter-spacing: 0; }
.mp__cta { width: 100%; justify-content: center; margin-top: 1.125rem; }
.mp__count { margin: .875rem 0 0; font-size: .8125rem; color: var(--ink-soft); }
.mp__count b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* --- the fifteen rows -------------------------------------------------- */
.mp__rows { list-style: none; margin: clamp(1.5rem, 4vw, 2rem) 0 0; padding: 0; }
.mp__g {
  margin: clamp(1.25rem, 3vw, 1.75rem) 0 .25rem;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft);
}
.mp__rows > .mp__g:first-child { margin-top: 0; }

.mp__r {
  --i: 0; --d: 0ms;
  display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: .75rem;
  align-items: start;
  padding-block: .8125rem;
  border-bottom: 1px solid var(--line);
  /* scroll reveal, staggered by row index and reversible */
  opacity: 0; transform: translateY(10px);
  transition: opacity .5s var(--ease-out), transform .5s var(--ease-out);
  transition-delay: calc(var(--i) * 28ms);
}
.mp__rows.is-in .mp__r { opacity: 1; transform: none; }
.no-js .mp__r { opacity: 1; transform: none; }

/* the mark. One element carries both states: a filled tick when included, a
   grey dash when not, and --d is written per row on a plan switch so only the
   rows that ACTUALLY CHANGED ripple. */
.mp__mk {
  position: relative; flex: none;
  width: 20px; height: 20px; margin-top: .125rem;
  border-radius: 50%; background: var(--blue);
  transition: background-color .4s var(--ease);
  transition-delay: var(--d);
}
.mp__mk::after {
  content: ""; position: absolute; left: 6px; top: 5px;
  width: 6px; height: 9px;
  border-right: 2px solid #fff; border-bottom: 2px solid #fff;
  transform: rotate(45deg) scale(1); transform-origin: center;
  transition: transform .4s var(--ease-spring), opacity .3s var(--ease);
  transition-delay: var(--d);
}
.mp__mk::before {
  content: ""; position: absolute; left: 5px; top: 9px;
  width: 10px; height: 2px; border-radius: 2px; background: var(--ink-soft);
  opacity: 0; transform: scaleX(0);
  transition: transform .4s var(--ease-spring), opacity .3s var(--ease);
  transition-delay: var(--d);
}
.mp__r.is-off .mp__mk { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
.mp__r.is-off .mp__mk::after { transform: rotate(45deg) scale(0); opacity: 0; }
.mp__r.is-off .mp__mk::before { opacity: 1; transform: scaleX(1); }

.mp__t b { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); line-height: 1.4; }
.mp__t em {
  display: block; margin-top: .1875rem;
  font-style: normal; font-size: .8125rem; color: var(--ink-mid); line-height: 1.45;
}
/* excluded rows recede but stay readable — this is still a comparison */
.mp__r.is-off .mp__t b { color: var(--ink-soft); }
.mp__r.is-off .mp__t em { color: #a3a29f; }   /* --line is a hairline colour; on text it is illegible */
.mp__t b, .mp__t em { transition: color .4s var(--ease); transition-delay: var(--d); }

.mp__note {
  margin: clamp(1.5rem, 3.5vw, 2rem) 0 0;
  font-size: .8125rem; color: var(--ink-soft); line-height: 1.6;
}

/* The pill slide and the mark change are the effect; with motion off the
   states still switch, just instantly. */
@media (prefers-reduced-motion: reduce) {
  .mp__tabs::before, .mp__tab, .mp__mk, .mp__mk::before, .mp__mk::after,
  .mp__t b, .mp__t em { transition: none; }
  .mp__r { opacity: 1; transform: none; transition: none; }
}
