/* =========================================================
   MLSN Franchising Solution Corp. — Landing Page
   1. Tokens        5. Header / Nav      9. Process
   2. Reset/base    6. Hero             10. Opportunities
   3. Utilities     7. About            11. CTA / Footer
   4. Buttons       8. Services / Why   12. Modal / Motion / Responsive
   ========================================================= */

/* ---------- 1. Tokens ---------- */
:root {
  /* "Purple Blend" palette. The five supplied swatches are 900/800/700/500/300;
     the remaining steps are interpolated so every existing token still resolves. */
  --purple-50:  #F5F1F8;
  --purple-100: #E7DEED;
  --purple-200: #C9B9D6;
  --purple-300: #AC91C0;   /* swatch 5 */
  --purple-400: #A276AF;
  --purple-500: #8F529B;   /* swatch 4 */
  --purple-600: #7A3F87;
  --purple-700: #572866;   /* swatch 3 */
  --purple-800: #3D2A5D;   /* swatch 2 */
  --purple-900: #2B103C;   /* swatch 1 */

  --ink:      #221130;
  --ink-soft: #4A3A5C;
  --muted:    #6F6280;
  --line:     #E7DEED;
  --white:    #FFFFFF;
  --shell:    #F8F5FA;
  --dark:     #1C0C28;
  --dark-2:   #2B103C;

  --grad-brand: linear-gradient(135deg, var(--purple-500) 0%, var(--purple-700) 55%, var(--purple-800) 100%);
  --grad-band:  linear-gradient(100deg, #3D2A5D 0%, #572866 45%, #8F529B 100%);

  /* --- glass surfaces ---
     Translucent white over a blurred, slightly saturated backdrop.

     PERFORMANCE TOGGLE. Live backdrop-filter is the most expensive thing on
     this page: roughly 25 panes each re-blur everything behind them every
     frame. Benchmarked on an Intel Iris Xe laptop, turning it off everywhere
     but the header and the modal came out inside run-to-run noise (~1fps over
     four interleaved passes), so it is left on. If the page ever feels heavy on
     weaker graphics, swap in the three commented values below: the header and
     the modal set their own blur literally, so they keep theirs, and every
     other pane falls back to a solid tint that looks near-identical against
     these near-white sections. */
  --glass-bg:        rgba(255, 255, 255, .24);   /* tinted fallback: .62 */
  --glass-bg-strong: rgba(255, 255, 255, .40);   /* tinted fallback: .82 */
  --glass-border:    rgba(255, 255, 255, .45);
  --glass-blur:      saturate(175%) blur(24px);  /* tinted fallback: none */
  --glass-sheen:     inset 0 1px 0 rgba(255, 255, 255, .34);

  --shadow-sm: 0 2px 10px rgba(43, 16, 60, .07);
  --shadow-md: 0 10px 30px rgba(43, 16, 60, .12);
  --shadow-lg: 0 22px 55px rgba(43, 16, 60, .18);

  /* --- surface texture ---
     Reusable background layers. Sections stack these in one `background`
     shorthand, so nothing here competes for a pseudo-element.
     Order in that shorthand is top-to-bottom: grain sits over the pattern,
     and a white "spotlight" layer goes between them to clear a calm area
     behind the text. */

  /* fine film grain — fractal noise baked at low opacity, tiled 180px */
  --tex-grain:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.45'/%3E%3C/svg%3E");
  /* pin-dot grid */
  --tex-dots:  radial-gradient(rgba(87, 40, 102, .14) 1px, transparent 1px);
  /* hairline graph paper */
  --tex-grid:
    linear-gradient(rgba(87, 40, 102, .075) 1px, transparent 1px),
    linear-gradient(90deg, rgba(87, 40, 102, .075) 1px, transparent 1px);
  /* fine diagonal weave */
  --tex-hatch:
    repeating-linear-gradient(-45deg,
      rgba(87, 40, 102, .06) 0 1px, transparent 1px 9px);
  /* soft concentric rings, like a faint watermark */
  --tex-rings:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(143, 82, 155, .07) 0 1px, transparent 1px 34px);

  /* --- bokeh ---
     Out-of-focus orbs of purple light, in two arrangements so neighbouring
     sections don't repeat the same constellation. A lens renders an
     out-of-focus highlight as a disc with a brighter rim, not a soft blob —
     so the small orbs carry that rim and the large ones stay diffuse, the way
     depth of field actually falls off. */
  --tex-bokeh:
    radial-gradient(circle 190px at 11% 20%,
      rgba(143, 82, 155, .26) 0 56%, rgba(143, 82, 155, .50) 56% 74%, transparent 76%),
    radial-gradient(circle 108px at 27% 71%,
      rgba(172, 145, 192, .28) 0 58%, rgba(172, 145, 192, .58) 58% 78%, transparent 80%),
    radial-gradient(circle 260px at 79% 14%, rgba(87, 40, 102, .36), transparent 72%),
    radial-gradient(circle  84px at 63% 84%,
      rgba(201, 185, 214, .32) 0 54%, rgba(201, 185, 214, .70) 54% 80%, transparent 82%),
    radial-gradient(circle 168px at 93% 63%, rgba(143, 82, 155, .36), transparent 70%),
    radial-gradient(circle  62px at 45% 11%,
      rgba(172, 145, 192, .30) 0 52%, rgba(172, 145, 192, .66) 52% 80%, transparent 82%),
    radial-gradient(circle 132px at 52% 47%, rgba(122, 63, 135, .20), transparent 72%);
  --tex-bokeh-alt:
    radial-gradient(circle 230px at 88% 24%, rgba(143, 82, 155, .42), transparent 70%),
    radial-gradient(circle  96px at 71% 76%,
      rgba(201, 185, 214, .32) 0 56%, rgba(201, 185, 214, .68) 56% 78%, transparent 80%),
    radial-gradient(circle 205px at 14% 78%,
      rgba(87, 40, 102, .22) 0 60%, rgba(87, 40, 102, .44) 60% 76%, transparent 78%),
    radial-gradient(circle  74px at 33% 26%,
      rgba(172, 145, 192, .30) 0 52%, rgba(172, 145, 192, .68) 52% 80%, transparent 82%),
    radial-gradient(circle 150px at 55% 8%,  rgba(143, 82, 155, .34), transparent 70%),
    radial-gradient(circle  58px at 92% 88%,
      rgba(172, 145, 192, .32) 0 50%, rgba(172, 145, 192, .70) 50% 80%, transparent 82%),
    radial-gradient(circle 140px at 42% 55%, rgba(122, 63, 135, .18), transparent 72%);

  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;

  --container: 1180px;
  --header-h: 86px;

  /* --- motion ---
     One curve family for the whole page, so nothing eases differently from
     anything else. --ease is the house curve; --ease-out has a longer settling
     tail for anything that travels a distance; --ease-soft tracks a pointer;
     --spring overshoots slightly before it lands. */
  --ease:      cubic-bezier(.22, .68, .36, 1);
  --ease-out:  cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);
  --spring:    cubic-bezier(.2, 1.25, .35, 1);

  --t-press:  .09s;   /* how quickly a control gives under a press */
  --t-base:   .28s;
  --t-slow:   .4s;
  /* Nothing should still be arriving a second after you have scrolled to it —
     that reads as the page lagging, even at a flat 60fps. Every entrance on
     this page is timed to be fully landed within ~0.6s of coming into view.
     At .45s the rise was over before the eye caught it, so blocks read as
     "already there" rather than as arriving. .6s on an ease-out curve is the
     shortest that still reads as a movement. */
  --t-reveal: .6s;
}

/* ---------- 2. Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }

/* Anchor jumps land just under the scrolled header bar (64px tall). The
   scrollbar gutter is reserved for good, so locking the page behind the modal
   can never shunt the layout sideways. */
html {
  scroll-behavior: smooth;
  /* follows --header-h rather than repeating it: the phone breakpoint below
     shrinks the bar, and an anchor still has to land clear of it */
  scroll-padding-top: calc(var(--header-h) - 2px);
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  font-family: "Plus Jakarta Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-soft);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

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

h1, h2, h3, h4 { color: var(--ink); line-height: 1.18; margin: 0 0 .5em; font-weight: 800; letter-spacing: -.02em; }
p { margin: 0 0 1em; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }

:focus-visible { outline: 3px solid var(--purple-500); outline-offset: 3px; border-radius: 6px; }

.skip-link {
  position: absolute; left: 16px; top: -60px; z-index: 200;
  background: var(--purple-700); color: #fff; padding: 10px 18px; border-radius: var(--r-sm);
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 12px; }

/* ---------- 3. Utilities ---------- */
.container { width: min(100% - 40px, var(--container)); margin-inline: auto; }

.section { padding: 72px 0; position: relative; }
/* section decoration sits at z-index 0; content always rides above it */
.section > .container { position: relative; z-index: 1; }

/* ---------- bokeh light layer ----------
   A blurred field of purple orbs behind each section's content. It drifts and
   breathes on a long, offset cycle, so the lighting is never quite static.
   Inset past the edges and blurred, so no orb shows a hard boundary. */
.services::before, .why::before, .brands::before,
.contact::before, .process::before, .about::before,
.reviews::before, .faq::before, .fo::before {
  content: ""; position: absolute; inset: -14%; z-index: 0; pointer-events: none;
  background: var(--tex-bokeh);
  filter: blur(10px);
  /* rasterised once and then only moved by the compositor, so a blurred field
     the size of the section costs nothing per frame */
  transform: translateZ(0);
  will-change: transform;
  animation: bokeh-drift 38s var(--ease-soft) infinite alternate;
}
/* alternate constellation, so adjacent sections don't mirror each other */
.why::before, .process::before, .contact::before, .faq::before {
  background: var(--tex-bokeh-alt);
  animation-duration: 46s; animation-delay: -12s;
}
/* a phone pays for six full-bleed blurred layers in memory as well as in
   frames — there the lighting is painted once and left still */
@media (hover: none) {
  .services::before, .why::before, .brands::before,
  .contact::before, .process::before, .about::before,
  .reviews::before, .faq::before, .fo::before {
    animation: none; will-change: auto;
  }
}
@keyframes bokeh-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(3%, -2.4%, 0) scale(1.09); }
}

.kicker {
  font-size: .72rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: var(--purple-600); margin-bottom: .6rem;
}
.eyebrow {
  font-size: .7rem; font-weight: 700; letter-spacing: .3em; text-transform: uppercase;
  color: var(--purple-700); margin-bottom: 1rem;
}
.eyebrow-light { color: rgba(255, 255, 255, .78); }

.h2 { font-size: clamp(1.65rem, 3.4vw, 2.3rem); }
.section-head { text-align: center; max-width: 720px; margin-inline: auto; margin-bottom: 40px; }
.section-sub { color: var(--muted); }

.grad {
  background: var(--grad-brand);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}

.script {
  font-family: "Caveat", "Segoe Script", cursive;
  font-size: clamp(1.6rem, 2.6vw, 2.1rem);
  font-weight: 700; line-height: 1.05; color: var(--purple-800);
  margin: 0; transform: rotate(-4deg);
}
.script-underline { width: 150px; height: 20px; margin-left: auto; }
.script-underline path { fill: none; stroke: var(--purple-500); stroke-width: 3; stroke-linecap: round; }

/* shared icon rendering: every inline icon is drawn as an outline */
.icon, .round-ico svg,
.sw-ico svg, .mc-ico svg, .to-top svg, .to-inquire svg {
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon { width: 18px; height: 18px; }

.card {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
  transition: transform var(--t-base) var(--ease-out),
              box-shadow var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease);
}

/* ---------- 4. Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 13px 26px; border: 1px solid transparent; border-radius: var(--r-pill);
  font: inherit; font-weight: 700; font-size: .93rem; cursor: pointer;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out),
              background var(--t-base) var(--ease), color var(--t-base) var(--ease),
              border-color var(--t-base) var(--ease);
}
.btn-arrow { transition: transform var(--t-base) var(--ease-out); }
.btn:hover .btn-arrow { transform: translateX(4px); }

.btn-primary { background: var(--grad-brand); color: #fff; box-shadow: 0 10px 22px rgba(87, 40, 102, .30); }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 16px 30px rgba(87, 40, 102, .38); }

/* glass fill, but a coloured outline — a white border on a light section is invisible */
.btn-ghost {
  background: var(--glass-bg-strong); color: var(--purple-700);
  border-color: var(--purple-300);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-sheen), 0 4px 14px rgba(43, 16, 60, .08);
}
.btn-ghost:hover {
  background: #fff; border-color: var(--purple-500); transform: translateY(-2px);
  box-shadow: var(--glass-sheen), 0 10px 24px rgba(43, 16, 60, .14);
}

.btn-light {
  background: var(--glass-bg-strong); color: var(--purple-700);
  border-color: rgba(255, 255, 255, .85);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 12px 26px rgba(34, 17, 48, .18), var(--glass-sheen);
}
.btn-light:hover { background: #fff; transform: translateY(-2px); }

/* press: the button gives under the finger and springs back on release.
   Written per variant so it lands after each one's :hover lift. */
.btn-primary:active, .btn-ghost:active, .btn-light:active {
  transform: translateY(0) scale(.97);
  transition-duration: var(--t-press);
}

.btn-sm { padding: 11px 22px; font-size: .87rem; }
.btn-block { width: 100%; }

/* ---------- 5. Header / Nav ----------
   At the top of the page the header is a floating capsule; scrolling past the hero
   spreads it into a full-width bar. It moves with left/right/top rather than a
   transform on purpose: a transform would make the header the containing block
   for the fixed mobile menu and knock that panel out of place. */
.site-header {
  /* local height var: the bar changes size while the root --header-h,
     which the hero padding and scroll offsets depend on, stays put */
  --h: 72px;
  --gap: 14px;                                        /* capsule's float above the page */
  --side: max(16px, calc((100% - 1240px) / 2));      /* capsule's inset from each edge */

  position: fixed; z-index: 100;
  top: var(--gap); left: var(--side); right: var(--side);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .08);
  backdrop-filter: saturate(185%) blur(24px); -webkit-backdrop-filter: saturate(185%) blur(24px);
  border: 1px solid rgba(255, 255, 255, .26);
  box-shadow: 0 12px 36px rgba(43, 16, 60, .13), inset 0 1px 0 rgba(255, 255, 255, .5);
  transition: top .38s var(--ease-out), left .38s var(--ease-out), right .38s var(--ease-out),
              border-radius .38s var(--ease-out), background .3s var(--ease),
              border-color .3s var(--ease), box-shadow .3s var(--ease);
}
/* scrolled: the capsule spreads edge to edge and settles against the top */
.site-header.is-stuck,
.site-header.is-nav-open {
  --h: 64px;
  top: 0; left: 0; right: 0;
  border-radius: 0;
  /* a touch more solid than the floating capsule: once scrolled, arbitrary content
     passes underneath and the nav still has to stay readable */
  background: rgba(255, 255, 255, .58);
  border-color: transparent; border-bottom-color: var(--line);
  box-shadow: 0 8px 28px rgba(43, 16, 60, .12);
}
/* an open mobile panel needs the bar above it fully opaque */
.site-header.is-nav-open { background: #fff; }

.header-inner {
  min-height: var(--h);
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  transition: min-height .38s var(--ease-out), width .38s var(--ease-out), padding .38s var(--ease-out);
}
/* once the bar spans the screen, its contents spread with it: logo hard left,
   Inquire Now hard right, instead of staying inside the 1180px content column */
.site-header.is-stuck .header-inner,
.site-header.is-nav-open .header-inner {
  width: 100%; max-width: none;
  padding-inline: clamp(20px, 3vw, 46px);
}

.brand { display: flex; align-items: center; }
.brand-logo {
  height: 61px; width: auto;
  /* scaled rather than resized: changing the height re-lays out the whole bar
     on every frame of the transition, a transform does not */
  transform-origin: left center;
  transition: transform .38s var(--ease-out);
}
.site-header.is-stuck .brand-logo { transform: scale(.868); }

/* --- the bar's two marks ---
   One grid cell holds both, so the bar's layout never shifts as they trade
   places, and the nav beside them does not move. */
.brand-swap { display: grid; align-items: center; }
.brand-swap > .brand { grid-area: 1 / 1; }
.brand-mlsn,
.brand-rdr {
  transition: opacity .34s var(--ease), transform .38s var(--ease-out), visibility .38s;
}
.brand-rdr {
  gap: 11px; opacity: 0; visibility: hidden;
  transform: translate3d(0, -7px, 0); pointer-events: none;
}
.site-header.is-stuck .brand-mlsn {
  opacity: 0; visibility: hidden; transform: translate3d(0, 7px, 0); pointer-events: none;
}
.site-header.is-stuck .brand-rdr {
  opacity: 1; visibility: visible; transform: none; pointer-events: auto;
}

/* Their crest is issued as a white knockout on transparency — it is drawn for
   the navy bar on their own site and is invisible on ours. It gets the same
   navy back as a disc of its own rather than being recoloured, because the
   mark is theirs and a repainted version of someone else's logo is a liberty
   this page does not need to take. */
.rdr-crest {
  flex: none; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 50%;
  background: #122B4F;
  box-shadow: 0 4px 12px rgba(18, 43, 79, .26);
  transition: transform .4s var(--ease-out), box-shadow .4s var(--ease-out);
}
.rdr-crest img { display: block; width: 26px; height: 26px; }
.brand-rdr:hover .rdr-crest {
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 9px 18px rgba(18, 43, 79, .32);
}
.rdr-name { display: grid; gap: 1px; line-height: 1.14; }
/* both lines stay on one line of their own: the lockup is a name over a name,
   and a wrapped one would push the height of the bar around as it sticks */
.rdr-name strong,
.rdr-name em { white-space: nowrap; }
.rdr-name strong { font-size: .95rem; font-weight: 800; letter-spacing: .005em; color: var(--ink); }
.rdr-name em { font-size: .68rem; font-style: italic; font-weight: 600; color: var(--muted); }
/* a narrow phone keeps both lines — the nav is behind the toggle there, so the
   bar has the room — but the lockup is set a size down to sit comfortably */
@media (max-width: 420px) {
  .brand-rdr { gap: 9px; }
  .rdr-crest { width: 34px; height: 34px; }
  .rdr-crest img { width: 23px; height: 23px; }
  .rdr-name strong { font-size: .86rem; }
  .rdr-name em { font-size: .63rem; }
}

/* The links sit centred between the logo and the Inquire Now button, heavy and
   in the deepest brand purple — the reference bar's weight, in this page's
   colour rather than its navy. `flex: 1` hands the nav all the space left over
   after the logo, and `margin-inline: auto` on the list centres it inside that,
   so the row stays balanced as the bar grows and shrinks on scroll. The gap
   closes on narrower desktops before the links would reach the logo. */
.nav { display: flex; align-items: center; gap: 28px; flex: 1; }
.nav-list {
  display: flex; align-items: center;
  gap: clamp(18px, 2.3vw, 34px); margin-inline: auto;
}
.nav-link {
  position: relative; font-size: .95rem; font-weight: 800; color: var(--purple-900);
  letter-spacing: -.005em; white-space: nowrap;
  padding: 6px 0; transition: color .2s var(--ease);
}
.nav-link::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: var(--purple-600); border-radius: 2px;
  /* drawn by scaling from the left, so the underline never re-runs layout */
  transform: scaleX(0); transform-origin: left center;
  transition: transform .32s var(--ease-out);
}
/* against a base this dark, the lift has to be brighter, not darker */
.nav-link:hover { color: var(--purple-600); }
.nav-link:hover::after, .nav-link.is-active::after { transform: scaleX(1); }
.nav-link.is-active { color: var(--purple-600); }

.nav-toggle {
  display: none; width: 44px; height: 40px; padding: 9px 10px;
  background: var(--purple-50); border: 1px solid var(--line); border-radius: var(--r-sm); cursor: pointer;
}
.nav-toggle span {
  display: block; height: 2px; border-radius: 2px; background: var(--purple-700);
  transition: transform .34s var(--ease-out), opacity .2s var(--ease);
}
.nav-toggle span + span { margin-top: 5px; }
/* both belong to the phone sheet only; the desktop bar never shows either */
.nav-num { display: none; }
.nav-call { display: none; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.scroll-progress {
  position: absolute; left: 0; bottom: -1px; height: 2px; width: 100%;
  background: var(--grad-brand);
  /* scaled, not resized: the bar becomes a composited layer, so following the
     scroll costs nothing and never triggers layout */
  transform: scaleX(0); transform-origin: 0 50%;
  will-change: transform;
  /* a straight line would poke out past the capsule's round ends — show it only
     once the header has spread into a flat bar */
  opacity: 0; transition: opacity .35s var(--ease);
}
.site-header.is-stuck .scroll-progress { opacity: 1; }

/* ---------- 6. Hero ---------- */
.hero {
  position: relative; overflow: hidden;
  background: linear-gradient(120deg, #FFFFFF 0%, var(--purple-50) 55%, var(--purple-100) 100%);
}
.hero-media { position: absolute; inset: 0; z-index: 0; }
/* the still now sits inside a <picture>; give that wrapper the frame so the
   image still resolves its 100% width and height against the hero */
.hero-media picture { position: absolute; inset: 0; }
.hero-media img, .hero-media video {
  width: 100%; height: 100%; object-fit: cover; object-position: 55% 45%;
  animation: heroPhoto 1.8s cubic-bezier(.2, .7, .3, 1) backwards;
}
@keyframes heroPhoto {
  from { transform: scale(1.07); }
  to   { transform: scale(1); }
}
/* the video is the live layer; the still only stands in when motion is unwanted */
.hero-still { display: none; }

/* --- hero backdrop switch (see the comment above <html> in index.html) ---
   With .no-hero-video the still photo takes over completely. main.js also
   strips the video's src in that case, so nothing is fetched or decoded. */
.no-hero-video .hero-video { display: none; }
.no-hero-video .hero-still { display: block; }

/* .hero-plain drops the photo and the video and leaves a wash built from the
   same purples as the rest of the page: a bright pool behind the logo so the
   mark stays crisp, two soft corner glows borrowed from the section
   backgrounds, over the white-to-lavender base. Same spotlight idiom the other
   sections use, so the hero sits in the family rather than looking blank. */
/* ---------- interactive hero backdrop ----------
   Orbs of brand light that drift slowly and lean with the pointer, so the
   plain hero has depth without a photo behind it.

   Everything here moves by transform alone, and each element carries exactly
   ONE animation, so the orbs stay on the compositor. (A second animation on
   the same property is what pinned the main thread on the logo — see the note
   in main.js.) While the pointer is still, this layer costs nothing. */
.hero-fx { display: none; }
.hero-plain .hero-fx {
  display: block;
  position: absolute; inset: 0; z-index: 1;
  overflow: hidden; pointer-events: none;
}

.fx-orb { position: absolute; display: block; }
.fx-orb::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: var(--orb);
  animation: orbDrift var(--drift) ease-in-out infinite alternate;
}
@keyframes orbDrift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(5%, -7%, 0) scale(1.1); }
}

/* Top positions stay below ~16%: the header carries a backdrop-filter, and an
   orb that sits behind it gets its composited layer sampled as a hard-edged
   rectangle instead of a circle. Keeping them clear of the bar avoids it. */
.fx-orb--1 { left:  4%; top: 18%; width: 320px; height: 320px; --drift: 17s;
  --orb: radial-gradient(circle, rgba(143, 82, 155, .30) 0%, rgba(143, 82, 155, 0) 72%); }
.fx-orb--2 { left: 74%; top: 16%; width: 260px; height: 260px; --drift: 21s;
  --orb: radial-gradient(circle, rgba(172, 145, 192, .34) 0%, rgba(172, 145, 192, 0) 72%); }
.fx-orb--3 { left:  9%; top: 58%; width: 280px; height: 280px; --drift: 19s;
  --orb: radial-gradient(circle, rgba(87, 40, 102, .24) 0%, rgba(87, 40, 102, 0) 72%); }
.fx-orb--4 { left: 66%; top: 62%; width: 310px; height: 310px; --drift: 23s;
  --orb: radial-gradient(circle, rgba(143, 82, 155, .26) 0%, rgba(143, 82, 155, 0) 72%); }
/* The small ones sit "nearer" and travel furthest. An out-of-focus highlight
   brightens toward its edge, but the stops have to ramp rather than step —
   hard stops read as a grey ring here, because unlike the section bokeh there
   is no blur over this layer to soften them. */
.fx-orb--5 { left: 30%; top: 17%; width: 116px; height: 116px; --drift: 13s;
  --orb: radial-gradient(circle,
    rgba(172, 145, 192, .30) 0%, rgba(160, 120, 180, .26) 44%,
    rgba(143,  82, 155, .46) 68%, rgba(143, 82, 155, 0) 80%); }
.fx-orb--6 { left: 87%; top: 38%; width:  94px; height:  94px; --drift: 15s;
  --orb: radial-gradient(circle,
    rgba(160, 120, 180, .30) 0%, rgba(143,  82, 155, .26) 44%,
    rgba(122,  63, 135, .48) 68%, rgba(122, 63, 135, 0) 80%); }
.fx-orb--7 { left: 26%; top: 80%; width:  80px; height:  80px; --drift: 11s;
  --orb: radial-gradient(circle,
    rgba(172, 145, 192, .30) 0%, rgba(160, 120, 180, .26) 44%,
    rgba(143,  82, 155, .44) 68%, rgba(143, 82, 155, 0) 80%); }

.hero-plain .hero-media { display: none; }
.hero-plain .hero {
  background:
    radial-gradient(880px 560px at 50% 44%,
      rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, 0) 68%),
    radial-gradient(760px 520px at 10% 4%,  rgba(143, 82, 155, .20), transparent 66%),
    radial-gradient(680px 480px at 94% 92%, rgba(122, 63, 135, .18), transparent 68%),
    radial-gradient(520px 380px at 88% 12%, rgba(172, 145, 192, .22), transparent 70%),
    linear-gradient(120deg, #FFFFFF 0%, var(--purple-50) 50%, var(--purple-100) 100%);
}

/* ---------- hero-food: the brand spread ----------
   The artwork is a frame, not a fill: the food occupies the outer 28.2% on the
   left and everything past 70% on the right, and the middle 41.8% is left
   deliberately empty for the logo. Those three numbers were measured off the
   file, and every rule below is derived from them — so the copy column tracks
   the gap instead of guessing at it.

   How the width is worked out. object-fit: cover scales the artwork by
   max(viewportW / 1747, heroH / 900), so the empty middle lands on screen at
   0.418 x 1747 x that scale. Written as viewport units that is

       max(41.8vw, 81.1vh)

   and the copy column is that, less a gutter, never wider than its usual 620px.
   Both terms matter: a short, wide window is limited by its height, a tall
   narrow one by its width. If the hero ever grows past 100vh the real gap is
   wider than the formula says, which errs the safe way. */
.hero-bg-wrap { display: none; }

/* The hero's own backdrop, matched to the artwork's wash: near-white down the
   middle, lavender out at both edges. Sampled off the file itself. It shows
   during the entrance fade, and below 991px it is what the food band's faded
   top edge dissolves into — without it that join reads as a tonal step. */
.hero-food .hero {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .5) 0%, rgba(255, 255, 255, 0) 42%),
    linear-gradient(90deg,
      #D5ABFA 0%, #E9D6FC 18%, #F7F1FE 36%, #FDFBFF 50%,
      #F6EFFE 64%, #E6D1FB 82%, #CDA4F9 100%);
}

.hero-food .hero-media { display: block; }
/* the handshake layers belong to the other modes */
.hero-food .hero-video,
.hero-food .hero-media > picture { display: none; }

.hero-food .hero-bg-wrap {
  display: block; position: absolute; inset: 0; overflow: hidden;
  /* the entrance lives on the wrapper so the image itself keeps its transform
     free for the pointer — one animated transform per element, which is what
     keeps both on the compositor */
  animation: heroBgIn 1.6s var(--ease-out) backwards;
}
@keyframes heroBgIn {
  from { opacity: 0; transform: scale(1.05); }
  to   { opacity: 1; transform: scale(1); }
}

/* .hero-media picture is absolutely positioned for the handshake still; this
   one has to stay in flow, because below 991px the wrapper takes its height
   from the image rather than from the hero. */
.hero-food .hero-bg-wrap picture {
  position: static; inset: auto;
  display: block; width: 100%; height: 100%;
}

/* The head-of-screen copy is a phone composition only — above 991px the whole
   spread already runs across the hero. This needs the .hero-food prefix and has
   to sit after the rule above: .hero-food .hero-bg-wrap sets display:block at
   two classes, so a bare .hero-bg-wrap--top loses to it and the band was being
   painted twice on desktop. */
.hero-food .hero-bg-wrap--top { display: none; }

.hero-food .hero-bg {
  width: 100%; height: 100%;
  object-fit: cover;
  /* 49.1% is the measured centre of the empty middle */
  object-position: 49.1% 50%;
  animation: none;                      /* the wrapper owns the entrance */
}

/* Scrim. The artwork is already pale, so this is not the flat wash the video
   needs — just a pool of light under the logo and a band under the header, so
   the type and the nav keep contrast without bleaching the food. */
.hero-food .hero-media::before {
  background:
    radial-gradient(760px 520px at 49% 46%,
      rgba(255, 255, 255, .70) 0%, rgba(255, 255, 255, .26) 48%, rgba(255, 255, 255, 0) 72%),
    linear-gradient(180deg,
      rgba(252, 250, 253, .80) 0%, rgba(252, 250, 253, .34) 80px, rgba(252, 250, 253, 0) 158px);
}

/* the drifting orbs and the cursor glow are shared with .hero-plain */
.hero-food .hero-fx {
  display: block;
  position: absolute; inset: 0; z-index: 1;
  overflow: hidden; pointer-events: none;
}
/* On the plain hero the orbs are the whole backdrop and carry it. Here there
   is a photograph underneath with bubbles of its own, so they are pulled back
   to a wash the food can still be read through. */
.hero-food .fx-orb { opacity: .5; }

/* --- keeping the copy inside the gap --- */
.hero-food .hero-copy {
  max-width: max(300px, min(620px, calc(max(41.8vw, 81.1vh) - 88px)));
}
/* the logo is sized in vw of its own, so it has to be told to respect the
   column as well or it reaches the food before the column does */
.hero-food .logo-anim { max-width: 100%; }

/* --- rising bubbles ---
   The artwork has bubbles drifting through it; these carry that motif into the
   live layer. Same two-element trick as the orbs: the span places it, its
   ::before carries the one keyframe animation. Exactly one animation per
   element is what keeps this layer on the compositor. */
.fx-bubble { display: none; }
.hero-food .fx-bubble { display: block; position: absolute; }
.hero-food .fx-bubble::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  /* a real bubble is brightest where the light enters and carries a thin rim */
  background:
    radial-gradient(circle at 34% 28%,
      rgba(255, 255, 255, .80) 0%, rgba(255, 255, 255, .34) 22%,
      rgba(205, 178, 222, .30) 54%, rgba(172, 145, 192, .20) 76%,
      rgba(172, 145, 192, .05) 92%, rgba(172, 145, 192, 0) 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .40);
  animation: bubbleRise var(--rise) ease-in-out infinite alternate;
}
@keyframes bubbleRise {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(7%, -34%, 0) scale(1.09); }
}

/* Kept to the middle band and the soft transition either side of it, so they
   drift over the wash rather than across the food. */
.fx-bubble--1 { left: 33%; top: 66%; width: 54px; height: 54px; --rise: 15s; }
.fx-bubble--2 { left: 62%; top: 74%; width: 38px; height: 38px; --rise: 12s; }
.fx-bubble--3 { left: 44%; top: 84%; width: 26px; height: 26px; --rise:  9s; }
.fx-bubble--4 { left: 29%; top: 40%; width: 30px; height: 30px; --rise: 13s; }
.fx-bubble--5 { left: 68%; top: 46%; width: 44px; height: 44px; --rise: 17s; }
.fx-bubble--6 { left: 56%; top: 24%; width: 22px; height: 22px; --rise: 11s; }

/* --- below the desktop composition ---
   The hero stops being a full screen here, so cover would scale the artwork by
   its height and crop away both food clusters — the middle is all that would
   be left. Instead the whole spread is laid across the foot of the hero at its
   own aspect ratio, and the copy is given room above it. The artwork's wash and
   the hero gradient are the same purples, so fading the top edge hides the join
   completely. */
@media (max-width: 991px) {
  /* Both copies are laid across the screen at their own aspect ratio and
     oversized to 138%, so the food clusters — which live in the outer third at
     each side — stay in frame instead of being cropped to the empty middle.
     138% of 100vw is 71vw tall each, so between them the spread covers about
     two thirds of the hero rather than the bottom quarter, and the logo sits
     in the clear gap between. cover is still no use here: it scales by height
     and would leave nothing but the middle. */
  .hero-food .hero-bg-wrap { inset: auto 0 0 0; height: auto; }
  .hero-food .hero-bg {
    /* max-width:100% in the reset would clamp this straight back to the
       viewport width, which is exactly what kept the band at 201px.
       The fade runs deeper than it used to (solid at 56% rather than 40%):
       it hands the copy another 16% of the band to sit over, which is what
       lets the reservation below come down far enough for the hero to fit a
       phone screen. Less of the band is solid, but all of it is now above the
       fold, so more solid food is visible than before, not less. */
    width: 138%; max-width: none; margin-left: -19%; height: auto; object-fit: fill;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .34) 22%, #000 56%);
            mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .34) 22%, #000 56%);
  }

  .hero-food .hero-bg-wrap--top { display: block; inset: 0 0 auto 0; height: auto; }
  .hero-food .hero-bg-wrap--top .hero-bg {
    /* flipped, so the two bands are not an obvious repeat of each other */
    transform: scaleX(-1);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .5) 58%, transparent 86%);
            mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .5) 58%, transparent 86%);
  }

  /* The hero was ending 80px short of the screen, so the next section showed
     under it and the composition read as a card rather than a front page.
     svh is the shrunk viewport — the one that excludes the browser's own
     chrome — so this is a screenful with the address bar showing, not one that
     only fits once it has scrolled away. */
  .hero { min-height: 100vh; min-height: 100svh; }
  /* The copy clears the solid part of the lower band and overlaps only its
     faded top edge, the way it sits over the spread on desktop.

     56vw of reservation was more than a short phone had to give: header, copy
     and reservation came to 726px against a 654px screen, so the hero grew
     past the fold, align-content had no free space left to centre anything,
     and the band it was holding room for hung 72px below the screen. What
     showed was the stack pinned under the header with a pale strip beneath it
     — the gap.

     The band is 71vw tall and the mask above turns it solid at 56% of that, so
     the food begins to read 0.44 x 71vw = 31vw above the foot of the hero.
     Reserving 36vw puts the edge of the content box just above that line, and
     since align-content centres the copy inside the box, the copy lands on the
     middle of the clear zone between the header and the food on its own, at
     any screen height. On the 654px screen above the whole composition now
     fits in one screenful with the band entirely in frame. */
  .hero-food .hero-inner {
    padding-bottom: 36vw;
    display: grid; align-content: center;
  }
  .hero-food .hero-copy { max-width: 100%; }
  /* a stronger pool of light than the desktop one: there is food above the
     logo now as well as below it */
  .hero-food .hero-media::before {
    background:
      radial-gradient(560px 420px at 50% 43%,
        rgba(255, 255, 255, .80) 0%, rgba(255, 255, 255, .42) 46%, rgba(255, 255, 255, 0) 74%),
      linear-gradient(180deg,
        rgba(252, 250, 253, .84) 0%, rgba(252, 250, 253, .36) 84px, rgba(252, 250, 253, 0) 164px);
  }
  /* the bubbles belong to the wide composition's empty middle */
  .hero-food .fx-bubble { display: none; }
  /* The orbs were sized for a full-screen desktop hero. Over a phone screen
     they land as flat pale discs on top of the artwork — one sat right behind
     the logo. The food frames the screen itself now, so this layer has nothing
     left to do here. (.hero-plain keeps them: there they ARE the backdrop.) */
  .hero-food .fx-orb { display: none; }
}

/* light scrim so the copy stays readable over the video */
.hero-media::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(255, 255, 255, .8) 0%, rgba(245, 241, 248, .7) 55%, rgba(231, 222, 237, .74) 100%);
}
/* the hero is a column: copy takes the free space, brand strip sits at the foot */
.hero { display: flex; flex-direction: column; }
/* top padding clears the fixed header, which sits over the video */
.hero-inner { position: relative; z-index: 2; flex: 1; padding: calc(var(--header-h) + 26px) 0 64px; }

.hero-copy { max-width: 620px; margin-inline: auto; text-align: center; }
.hero-title { margin: 0; }

/* --- hero call-to-action row + round contact icons --- */
.hero-actions {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 14px;
  margin-top: 30px;
  animation: textIn .46s cubic-bezier(.3, .7, .35, 1) .2s backwards;
}
.hero-social {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;
  margin-top: 20px;
  animation: textIn .46s cubic-bezier(.3, .7, .35, 1) .28s backwards;
}
.hero-social a {
  display: grid; place-items: center; width: 42px; height: 42px;
  border-radius: 50%; background: var(--grad-brand); color: #fff;
  box-shadow: 0 8px 18px rgba(87, 40, 102, .3);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
.hero-social a:hover { transform: translateY(-3px); box-shadow: 0 13px 26px rgba(87, 40, 102, .4); }
.hero-social a:active { transform: translateY(-1px) scale(.94); transition-duration: var(--t-press); }
.hero-social svg {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- "Recommended by" badge ----------
   Sits above the logo. Its space is reserved from first paint, so the delayed
   entrance animates opacity/transform only and never shifts the logo. */
.rec-badge {
  display: inline-flex; align-items: center; gap: 12px;
  padding: 7px 16px 7px 7px; margin: 0 auto 22px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border); border-radius: var(--r-pill);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 6px 20px rgba(34, 17, 48, .13), 0 1px 3px rgba(34, 17, 48, .08);
  text-align: left; vertical-align: middle;
  animation: recIn .5s cubic-bezier(.3, .7, .35, 1) .06s backwards;
}
.rec-avatar {
  flex: none; width: 42px; height: 42px; border-radius: 50%; overflow: hidden;
  background: #E7DEED;
}
.rec-avatar img { width: 100%; height: 100%; object-fit: cover; }

.rec-body { display: grid; gap: 2px; }
.rec-stars { display: flex; gap: 2px; }
.rec-stars svg { width: 13px; height: 13px; fill: var(--purple-500); stroke: none; }
.rec-stars .is-half { opacity: .4; }

.rec-text {
  font-size: .92rem; font-weight: 700; color: var(--ink); white-space: nowrap;
}
.rec-name {
  color: var(--purple-700); font-weight: 800;
  background: var(--purple-100); border-radius: 4px; padding: 1px 6px; margin-left: 2px;
}
.rec-chev { display: grid; place-items: center; flex: none; }
.rec-chev svg {
  width: 16px; height: 16px;
  fill: none; stroke: var(--purple-600); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
}

@keyframes recIn {
  from { opacity: 0; transform: translateY(-14px) scale(.94); }
  to   { opacity: 1; transform: none; }
}

/* --- phones: the same pill, a size down ---
   At a 42px avatar and .92rem of text the badge measured close to 300px wide
   and 56px tall, which is a lot of the fold to spend before the logo has even
   arrived. Everything steps down together — roughly a quarter off each part —
   so the proportions are kept and the row still reads as one object rather
   than a shrunken avatar sitting next to full-size type.
   The line is meant to stay unbroken: at .78rem the pill measures about 250px,
   inside the container even on a 320px screen, so .rec-text's nowrap never
   has to fight the width and the badge keeps its single-row silhouette. */
@media (max-width: 599px) {
  .rec-badge {
    gap: 9px;
    padding: 5px 13px 5px 5px;
    margin-bottom: 16px;
    box-shadow: 0 4px 14px rgba(34, 17, 48, .12), 0 1px 2px rgba(34, 17, 48, .07);
  }
  .rec-avatar { width: 32px; height: 32px; }
  .rec-body { gap: 1px; }
  .rec-stars { gap: 1.5px; }
  .rec-stars svg { width: 11px; height: 11px; }
  .rec-text { font-size: .78rem; }
  .rec-name { padding: 1px 5px; border-radius: 3px; margin-left: 1px; }
  .rec-chev svg { width: 13px; height: 13px; stroke-width: 2.8; }
}

/* ---------- Animated logo ----------
   Four layers cut from the original artwork, stacked in register. Percentages below
   are measured from the 900x641 source: the skyline ends at x=694 (77.1%) and the
   bulb's glass centre sits at 786,128 (87.3%, 20.0%).

   RUNNING ORDER. The building and its light are the finale; everything else is
   already sitting there before they arrive, so nothing is still being waited
   for while the mark draws itself:

     .06s  badge
     .12s  wordmark
     .20s  buttons
     .28s  social row          — all landed by .74s
     ---- a beat ----
     .88s  the skyline draws
    1.64s  the bulb makes contact, the glow flashes
    1.86s  the rays fire
    2.46s  the light settles into its idle breathing

   The two idle loops publish their start time as --idle-at, because main.js
   has to take them over at exactly that moment and hardcoding the number in
   two files is how the two drift apart. */
.logo-anim {
  position: relative; display: block; margin-inline: auto;
  width: clamp(260px, 35vw, 480px);
  aspect-ratio: 900 / 641;
  /* once it has drawn itself, the mark keeps breathing rather than sitting dead */
  animation: logoFloat 6s ease-in-out 2.5s infinite;
}
.logo-anim img { position: absolute; inset: 0; width: 100%; height: 100%; }

.la-glow {
  position: absolute; left: 87.3%; top: 20%;
  width: 46%; aspect-ratio: 1; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(201, 185, 214, .75) 0%, rgba(172, 145, 192, .35) 42%, rgba(172, 145, 192, 0) 70%);
  opacity: 0; pointer-events: none;
}

/* the line travels from its own left edge to the point where it meets the bulb */
@keyframes drawLine {
  /* fully closed, not 89.5%. At 89.5% the line's own left stub sat on screen
     from first paint — barely noticeable when this ran at .15s, an obvious
     loose end now that it waits until .88s. */
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 22% 0 0); }
}
/* contact — the bulb takes the hit, overshoots, settles */
@keyframes bulbPop {
  0%   { opacity: 0; transform: scale(.55); }
  45%  { opacity: 1; transform: scale(1.18); }
  68%  { transform: scale(.93); }
  85%  { transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes glowFlash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35); }
  35%  { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.5); }
}
/* the rays fire outward once the filament catches */
@keyframes raysBurst {
  0%   { opacity: 0; transform: scale(.35); }
  60%  { opacity: 1; transform: scale(1.16); }
  100% { opacity: 1; transform: scale(1); }
}
/* then keep breathing so the light reads as lit, not frozen */
@keyframes raysPulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: .72; transform: scale(1.07); }
}
@keyframes textIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
/* the flash fades into a steady glow so the bulb stays lit */
@keyframes glowIdle {
  0%, 100% { opacity: .14; transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: .36; transform: translate(-50%, -50%) scale(1.14); }
}
@keyframes logoFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}

.la-line { animation: drawLine .78s cubic-bezier(.42, 0, .2, 1) .88s backwards; }
.la-glow {
  --idle-at: 2.46s;
  animation: glowFlash .82s ease-out 1.64s backwards,
             glowIdle 3.4s ease-in-out var(--idle-at) infinite;
}
.la-bulb {
  transform-origin: 87.3% 20%;
  animation: bulbPop .5s cubic-bezier(.3, .7, .35, 1) 1.64s backwards;
}
.la-rays {
  --idle-at: 2.3s;
  transform-origin: 87.3% 20%;
  animation: raysBurst .44s cubic-bezier(.3, .8, .4, 1) 1.86s backwards,
             raysPulse 2.6s ease-in-out var(--idle-at) infinite;
}
/* the wordmark belongs to phase one: the name is readable while the skyline
   above it is still drawing */
.la-text { animation: textIn .46s cubic-bezier(.3, .7, .35, 1) .12s backwards; }

/* ---------- Brand strip (marquee) ----------
   Opens the About section — the first thing on the second screen. */
/* full-bleed band: sits outside the container, so it runs the whole viewport */
.brand-strip {
  position: relative; z-index: 1; margin-bottom: 64px;
  /* The colour lives here rather than on .marquee, because .marquee carries a
     mask that feathers its left and right edges — that would eat the band's
     colour at the sides too. Here the band runs edge to edge and only the
     logos inside it fade in and out.
     Kept light on purpose: several of these marks are themselves purple, and a
     dark band would swallow them. */
  background:
    radial-gradient(72% 180% at 50% 50%, rgba(255, 255, 255, .74), rgba(255, 255, 255, 0) 72%),
    linear-gradient(100deg,
      var(--purple-200) 0%, var(--purple-100) 26%, var(--purple-50) 50%,
      var(--purple-100) 74%, var(--purple-200) 100%);
  box-shadow: 0 14px 34px rgba(43, 16, 60, .12), inset 0 1px 0 rgba(255, 255, 255, .7);
  transition: box-shadow .35s var(--ease);
}
/* a brand-gradient rule top and bottom, so the stripe is clearly bounded */
.brand-strip::before,
.brand-strip::after {
  content: ""; position: absolute; left: 0; right: 0; height: 3px; z-index: 2;
  background: var(--grad-brand);
}
.brand-strip::before { top: 0; }
.brand-strip::after  { bottom: 0; }

/* the strip is the first thing after the hero, so it sits flush against it */
.section.about { padding-top: 0; }
.marquee {
  overflow: hidden; padding: 24px 0;
  /* transparent: the band behind it does the colour */
  background: none; border: 0;
  /* a narrow feather at each screen edge so logos slide in rather than pop */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
}
.marquee-track {
  display: flex; align-items: center; width: max-content;
  /* duration scaled to the longer track so the speed stays the same */
  animation: marquee 44s linear infinite;
  will-change: transform;
  /* no text/image selection highlight when the cursor sweeps across */
  user-select: none; -webkit-user-select: none;
}
/* margin (not gap) keeps both halves identical in width, so -50% loops exactly */
.marquee-track li { margin-right: 66px; flex: none; }
.marquee-track img {
  height: 62px; width: auto; object-fit: contain;
  opacity: .95;
  filter: drop-shadow(0 5px 12px rgba(43, 16, 60, .18));
  /* only opacity and transform move: re-rendering two dozen drop-shadows at
     once is what made the strip stutter as the cursor crossed it */
  transition: transform .45s var(--ease-out), opacity .45s var(--ease);
  /* the image itself takes no pointer events, so it can never be dragged —
     the <li> around it still receives hover */
  pointer-events: none;
}

/* --- hover: cursor anywhere in the strip wakes the whole panel up --- */
.brand-strip:hover {
  box-shadow: 0 20px 48px rgba(43, 16, 60, .18), inset 0 1px 0 rgba(255, 255, 255, .85);
}
.brand-strip:hover .marquee-track img {
  opacity: 1;
  transform: scale(1.06);
}
/* and the single logo under the cursor comes further forward */
.brand-strip .marquee-track li:hover img {
  transform: scale(1.2);
}
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* --- phones: the band was sized for a desktop screen ---
   At 110px tall plus a 64px gap it took 174px — most of a phone's fold — to
   show three logos. Everything scales down together so the marks keep their
   breathing room: 74px of band and a 44px gap, about a third off.
   The scroll speed looks after itself: main.js sets the animation duration
   from the measured track width, so smaller logos travel proportionally less
   and the strip still moves at the same pixels per second. */
@media (max-width: 599px) {
  .brand-strip { margin-bottom: 44px; }
  .marquee { padding: 15px 0; }
  .marquee-track img { height: 44px; }
  .marquee-track li { margin-right: 42px; }
}

/* ---------- 7. About ---------- */
/* Soft violet light pooling in opposite corners — keeps the white from reading as paper. */
/* Texture, top layer down: grain over a spotlight that clears the copy column,
   over graph-paper hairlines, over the two aurora washes, on white. */
.about {
  overflow: hidden;
  background:
    var(--tex-grain),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 800' preserveAspectRatio='xMidYMid slice' fill='none'><path d='M-40 690 C 280 560 560 800 900 640 S 1360 380 1500 450' stroke='%238F529B' stroke-opacity='.2' stroke-width='2' stroke-dasharray='10 12'/><path d='M1180 250 l16 16 -16 16 M1214 250 l16 16 -16 16 M1248 250 l16 16 -16 16' stroke='%238F529B' stroke-opacity='.26' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>"),
    radial-gradient(860px 560px at 27% 48%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 64%),
    var(--tex-grid),
    radial-gradient(820px 520px at 8% 6%, rgba(143, 82, 155, .30), transparent 66%),
    radial-gradient(760px 540px at 98% 90%, rgba(122, 63, 135, .28), transparent 68%),
    var(--white);
  background-size: 180px 180px, cover, auto, 44px 44px, 44px 44px, auto, auto, auto;
  background-repeat: repeat, no-repeat, no-repeat, repeat, repeat, no-repeat, no-repeat, no-repeat;
  background-position: 0 0, center, center, 0 0, 0 0, center, center, center;
}
/* the logo's bulb as a large ghost in the corner. (The dashed sweep and the trio
   of chevrons moved into .about's own background list, so ::before is free to
   carry the bokeh layer.) */
/* logo-bulb.png is 900x641 with the bulb at x715-857, y52-248. Scaled 1.02x (918px wide)
   and offset so that region lands inside this 220x240 box */
.about::after {
  /* tucked half off the bottom-right corner, clear of the video and its caption */
  content: ""; position: absolute; right: -54px; bottom: -70px; z-index: 0; pointer-events: none;
  width: 220px; height: 240px; opacity: .05;
  background: url("../img/logo-bulb.png") -699px -33px / 918px auto no-repeat;
}
.section.about > .container, .section.about > .brand-strip { position: relative; z-index: 1; }
/* --- split layout: copy left, swappable media panel right --- */
.about-split { display: grid; gap: 40px; align-items: center; }

.about-left p { color: var(--muted); font-size: .95rem; }

/* endorsement chip — solid bar version of the hero's white pill */
.rec-chip {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 18px; margin-bottom: 18px;
  background: var(--purple-900); color: #fff;
  /* flat left edge carrying the accent bar, pill-rounded right — as in the reference */
  border-radius: 4px var(--r-pill) var(--r-pill) 4px; border-left: 4px solid var(--purple-400);
  font-size: .7rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
}
.rec-chip svg { flex: none; width: 13px; height: 13px; fill: var(--purple-300); }
.rec-chip strong { color: var(--purple-300); font-weight: 800; }

.about-headline {
  font-size: clamp(1.8rem, 3.8vw, 2.9rem);
  font-style: italic; font-weight: 800;
  line-height: 1.08; letter-spacing: -.035em;
  color: var(--purple-900); margin: 0;
}
.about-headline .hl {
  color: var(--purple-600);
  background: linear-gradient(transparent 62%, var(--purple-200) 62%, var(--purple-200) 92%, transparent 92%);
}
.about-rule {
  display: block; width: 104px; height: 5px; border-radius: 3px;
  /* strong at the start, fading out — as in the reference */
  background: linear-gradient(90deg, var(--purple-700) 0%, var(--purple-500) 45%, rgba(143, 82, 155, .12) 100%);
  margin: 20px 0 22px;
}

/* --- four stat cards, reference style: icon, bold coloured value, small caption --- */
.about-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 26px 0 30px; }

/* Four cards, four steps down the brand ramp. Same idiom as the Franchise
   Opportunity cards further down the page: one accent token per card driving
   the icon tile, the wash, the edge and the headline together, so each reads
   as its own colour without any of them leaving the palette.
   nth-child rather than classes, so reordering or adding a claim in the markup
   re-colours the row on its own. */
.about-stats li {
  --as-accent: var(--purple-600);
  --as-wash:   rgba(122, 63, 135, .26);
  --as-wash-2: rgba(122, 63, 135, .10);
  --as-edge:   rgba(122, 63, 135, .40);
  --as-glow:   rgba(122, 63, 135, .30);

  display: grid; align-content: start; gap: 3px;
  /* the tint sits over the glass rather than replacing it, so these still
     belong to the same family as every other card on the page */
  background:
    linear-gradient(158deg, var(--as-wash) 0%, var(--as-wash-2) 96%),
    var(--glass-bg);
  border: 1px solid var(--as-edge); border-radius: 14px; padding: 16px 14px 15px;
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 8px 22px rgba(43, 16, 60, .08), 0 1px 2px rgba(43, 16, 60, .05);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
/* The hue steps down the ramp AND the tint gets heavier, so the row reads as
   one colour getting deeper from the first card to the last rather than four
   cards at the same strength. The value and caption stay dark ink throughout;
   the heaviest card is still a tint over glass, not a solid. */
.about-stats li:nth-child(1) {
  --as-accent: var(--purple-500);
  --as-wash: rgba(143, 82, 155, .14); --as-wash-2: rgba(143, 82, 155, .05);
  --as-edge: rgba(143, 82, 155, .32); --as-glow: rgba(143, 82, 155, .30);
}
.about-stats li:nth-child(2) {
  --as-accent: var(--purple-600);
  --as-wash: rgba(122, 63, 135, .26); --as-wash-2: rgba(122, 63, 135, .10);
  --as-edge: rgba(122, 63, 135, .40); --as-glow: rgba(122, 63, 135, .34);
}
.about-stats li:nth-child(3) {
  --as-accent: var(--purple-700);
  --as-wash: rgba(87, 40, 102, .34);  --as-wash-2: rgba(87, 40, 102, .14);
  --as-edge: rgba(87, 40, 102, .46);  --as-glow: rgba(87, 40, 102, .34);
}
/* 900 rather than 800 to close the row. purple-800 (#3D2A5D) is the one
   indigo in the ramp and next to three warm purples it reads as grey — the
   fourth card looked like it had lost its colour. 900 is a deep plum, so the
   four stay one family from light to dark. */
.about-stats li:nth-child(4) {
  --as-accent: var(--purple-900);
  --as-wash: rgba(43, 16, 60, .44);   --as-wash-2: rgba(43, 16, 60, .19);
  --as-edge: rgba(43, 16, 60, .54);   --as-glow: rgba(43, 16, 60, .32);
}
/* A solid rule of the card's own colour along the top. The wash alone is a
   tint you read as "slightly warm white" at this card size; the bar is the bit
   that actually reads as colour across the room. Clipped by the card's own
   radius, so it follows the rounded corners. */
.about-stats li { position: relative; overflow: hidden; }
.about-stats li::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--as-accent);
}

/* hover deepens the card's own colour rather than everything going the same grey */
.about-stats li:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px var(--as-glow), 0 2px 4px rgba(43, 16, 60, .08);
}

/* the icon becomes a filled tile — the single strongest bit of colour in the
   card, and what makes the four read as a set at a glance */
.as-ico {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 10px;
  background: var(--as-accent); color: #fff;
  box-shadow: 0 6px 14px var(--as-glow);
  margin-bottom: 11px;
  transition: transform var(--t-base) var(--ease-out);
}
.about-stats li:hover .as-ico { transform: translateY(-1px) scale(1.06); }
.as-ico svg {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}
.as-big {
  font-size: .9rem; font-weight: 800; letter-spacing: .02em; text-transform: uppercase;
  color: var(--as-accent);
}
/* ink-soft rather than muted: the caption sits on four backgrounds now, and on
   the deepest two a mid grey on a mid mauve all but disappeared. */
.as-sub {
  font-size: .63rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-soft);
}

/* --- emphasised phrases in the body copy ---
   Weight and the brand ink carry the emphasis; there is no highlighter band
   behind them. It marks the phrase a skimming reader should come away with,
   so there is at most one per paragraph. */
.mark { font-weight: 800; color: var(--purple-700); }

/* --- drifting orbs: the phone-only backdrop ---
   Hidden everywhere the bokeh still drifts, so the two never stack up. Each
   orb animates one transform and nothing else, which is the whole point: the
   bokeh is parked on touch because six full-bleed *blurred* layers are
   expensive to hold, and these are neither full-bleed nor blurred. */
.drift { display: none; }
@media (hover: none) {
  .drift {
    display: block; position: absolute; inset: 0; z-index: 0;
    overflow: hidden; pointer-events: none;
  }
  .drift span {
    position: absolute; display: block;
    left: var(--x); top: var(--y); width: var(--s); height: var(--s);
    border-radius: 50%;
    background: radial-gradient(circle at 34% 30%,
      rgba(255, 255, 255, .72) 0%, rgba(255, 255, 255, .30) 24%,
      rgba(205, 178, 222, .28) 56%, rgba(172, 145, 192, .14) 80%,
      rgba(172, 145, 192, 0) 100%);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .34);
    animation: driftUp var(--t) ease-in-out infinite alternate;
  }
}
@keyframes driftUp {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(9%, -44%, 0) scale(1.12); }
}

/* --- the four claims take turns ---
   .is-cycling is added by main.js, never by the markup. It used to pull the
   three waiting cards back with saturate(.3), which left three of the four
   reading as grey at any moment and hid the light-to-dark ramp. Now every card
   keeps its own colour the whole time and the turn is carried by the lift and
   the glow alone. */
.about-stats.is-cycling li {
  transition: opacity .5s var(--ease), filter .5s var(--ease),
              transform var(--t-base) var(--ease-out),
              box-shadow var(--t-base) var(--ease-out);
}
.about-stats.is-cycling li.is-lit {
  opacity: 1; filter: none;
  transform: translateY(-4px);
  box-shadow: 0 16px 34px var(--as-glow), 0 2px 4px rgba(43, 16, 60, .08);
}
.about-stats.is-cycling li.is-lit .as-ico { transform: translateY(-1px) scale(1.06); }

/* the card under the cursor is lit directly by main.js, so the hover rule
   would otherwise fire a second, competing lift on top of it */
.about-stats.is-cycling li:hover { transform: translateY(-4px); }

.about-cta { display: flex; flex-wrap: wrap; gap: 14px; margin: 26px 0 16px; }

.about-note {
  display: flex; align-items: center; gap: 8px; margin: 0;
  font-size: .78rem; color: var(--muted);
}
.about-note svg {
  flex: none; width: 15px; height: 15px; color: var(--purple-500);
  fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

/* --- the media panel: holds a <video> or an <img>, identical either way --- */
.about-right { display: grid; justify-items: center; }
.media-panel {
  margin: 0; width: 100%; aspect-ratio: 16 / 11;
  border-radius: 18px; overflow: hidden; background: #2B103C;
  /* no frame — a soft lift plus a purple glow pooling beneath, as in the reference */
  box-shadow: 0 30px 60px -18px rgba(87, 40, 102, .45), 0 10px 26px rgba(43, 16, 60, .14);
}
.media-panel video, .media-panel img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* YouTube is 16:9 — match it exactly so the player has no black bars */
.media-panel--video { aspect-ratio: 16 / 9; position: relative; }
.media-panel iframe { width: 100%; height: 100%; border: 0; display: block; }

/* sound toggle — bottom-left, so it never sits over YouTube's own logo on the right */
.video-sound {
  position: absolute; left: 14px; bottom: 14px; z-index: 2;
  display: grid; place-items: center; width: 42px; height: 42px;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(34, 17, 48, .62); color: #fff;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .25);
  transition: background var(--t-base) var(--ease), transform var(--t-base) var(--ease-out);
}
.video-sound:hover { background: var(--purple-600); transform: scale(1.07); }
.video-sound:active { transform: scale(.93); transition-duration: var(--t-press); }
.video-sound svg {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}

/* --- "Featured in RDR Vlog" caption pill, facts rotating in one slot --- */
.vlog-pill {
  /* sit centred under the video at its own width, not stretched to the column */
  justify-self: center;
  display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center;
  gap: 4px 9px; margin: 22px 0 0; padding: 11px 20px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border); border-radius: var(--r-pill);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 8px 24px rgba(43, 16, 60, .1), 0 1px 3px rgba(43, 16, 60, .06);
  font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); text-align: center;
}
.vp-star { flex: none; width: 13px; height: 13px; fill: var(--purple-500); }
.vp-tag { color: var(--ink); }
.vp-tag strong { color: var(--purple-600); font-weight: 800; }
.vp-sep { color: var(--muted); }
/* all three facts stack in one grid cell, so the pill sizes to the longest and never jumps */
.vp-rotator { display: inline-grid; }
.vp-fact {
  /* left-aligned in the shared cell, so each quote starts right after the dash */
  grid-area: 1 / 1; justify-self: start; text-align: left; opacity: 0;
  animation: vpCycle 12s var(--ease) infinite;
}
.vp-fact:nth-child(2) { animation-delay: 4s; }
.vp-fact:nth-child(3) { animation-delay: 8s; }
@keyframes vpCycle {
  0%        { opacity: 0; transform: translateY(7px); }
  5%, 28%   { opacity: 1; transform: none; }
  33%, 100% { opacity: 0; transform: translateY(-7px); }
}

.video-sound .ico-sound { display: none; }
.video-sound.is-on { background: var(--purple-600); }
.video-sound.is-on .ico-sound { display: block; }
.video-sound.is-on .ico-muted { display: none; }

/* ---------- 7b. The Franchise Opportunity ----------
   The reference slide is built on three accents — a deep navy, a mid blue and a
   red. Here those roles are taken by three steps of the brand purple, so the
   three cards stay as distinguishable as the original without bringing a colour
   the page does not own. Everything else — the raking light, the faint grid,
   the quote watermark — is redrawn from the section's own tokens. */
.fo {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(900px 520px at 34% 40%, rgba(255, 255, 255, .94), rgba(255, 255, 255, 0) 62%),
    var(--tex-grid),
    radial-gradient(840px 520px at 100% 8%, rgba(143, 82, 155, .26), transparent 64%),
    radial-gradient(700px 460px at -4% 92%, rgba(122, 63, 135, .22), transparent 68%),
    linear-gradient(180deg, var(--white) 0%, #FCFAFD 46%, var(--purple-50) 100%);
  background-size: 180px 180px, auto, 46px 46px, 46px 46px, auto, auto, auto;
}

/* the band of light raking across from the left, as in the reference */
.fo-streak {
  position: absolute; z-index: 0; pointer-events: none;
  left: -20%; top: 2%; width: 86%; height: 52%;
  background: linear-gradient(101deg,
    rgba(172, 145, 192, 0) 0%,
    rgba(162, 118, 175, .34) 30%,
    rgba(201, 185, 214, .52) 50%,
    rgba(172, 145, 192, .16) 74%,
    rgba(172, 145, 192, 0) 100%);
  transform: rotate(-9deg);
  filter: blur(30px);
}

/* The opening quote mark blown up to watermark size. It is parked beside the
   lower half of the copy, mostly outside the content column, so it reads as
   texture rather than as something sitting on the words. */
.fo-watermark {
  position: absolute; z-index: 0; pointer-events: none; user-select: none;
  left: .6%; top: 31%;
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(12rem, 21vw, 22rem); line-height: .7;
  color: var(--purple-200); opacity: .48;
}

.fo-grid { display: grid; gap: 34px; align-items: center; }

/* --- left column --- */
.fo-kicker {
  display: flex; align-items: center; gap: 12px;
  margin: 0 0 18px;
  font-size: .72rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: var(--purple-600);
}
/* the rules either side of the label. The leading one is short and solid, the
   trailing one fades out — the same asymmetry the reference has. */
.fo-dash { display: block; height: 3px; border-radius: 3px; background: var(--grad-brand); }
.fo-dash:first-child { width: 30px; flex: none; }
.fo-dash:last-child {
  width: 34px; flex: none;
  background: linear-gradient(90deg, var(--purple-400), rgba(162, 118, 175, 0));
}

.fo-title {
  font-size: clamp(2rem, 4.6vw, 3.25rem);
  line-height: 1.08; letter-spacing: -.028em;
  color: var(--purple-900); margin: 0 0 .5em;
}
/* the highlighted last word, with the rule that draws itself underneath */
.fo-hl { position: relative; display: inline-block; color: var(--purple-500); }
.fo-underline {
  position: absolute; left: 0; right: 0; bottom: -.16em;
  width: 100%; height: .3em; overflow: visible;
}
.fo-underline path {
  fill: none; stroke: var(--purple-600); stroke-width: 5;
  stroke-linecap: round;
  /* held back until the heading is revealed, then drawn left to right */
  stroke-dasharray: 290; stroke-dashoffset: 290;
  transition: stroke-dashoffset .85s var(--ease-out) .25s;
}
.fo-copy.is-visible .fo-underline path { stroke-dashoffset: 0; }

.fo-lede { max-width: 42ch; font-size: .92rem; color: var(--muted); margin-bottom: 1.6em; }
.fo-ask { font-size: .9rem; color: var(--ink-soft); margin-bottom: .3em; }
.fo-question {
  max-width: 24ch;
  font-size: clamp(1.05rem, 1.7vw, 1.25rem); font-weight: 800; line-height: 1.32;
  letter-spacing: -.015em; color: var(--purple-600); margin: 0 0 20px;
}
.fo-rule { display: block; width: 52px; height: 3px; border-radius: 3px; background: var(--grad-brand); }

/* --- right panel: photo beside the quote --- */
/* stacked on a phone — photo above, quote below; side by side from 768px up */
.fo-panel {
  display: grid; grid-template-columns: minmax(0, 1fr);
  border-radius: var(--r-md); overflow: hidden;
  box-shadow: var(--shadow-lg);
  background: var(--purple-800);
}
.fo-photo { position: relative; overflow: hidden; background: var(--purple-100); min-height: 300px; }
.fo-photo img {
  position: absolute; inset: 0;
  /* A tall portrait in a frame that is never as tall, so cover crops the
     vertical and object-position picks the slice. 24% keeps his head clear of
     the top edge and puts the chair and suit in the body of the frame, with
     the floor cropped away below. */
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 24%;
  transition: transform 1.1s var(--ease-out);
}
.fo-panel:hover .fo-photo img { transform: scale(1.05); }

/* Stacked, the dark half is pulled up over the foot of the photo and its top
   edge cut on a slant, rising to the right — the panel reads as laid across
   the picture rather than butted against it. The negative margin and the clip
   are the same 30px, so the slope starts exactly at the photo's old edge and
   no part of the photo is lost on the right. The extra top padding is that
   30px back, so the text keeps its distance from the cut. */
/* The quote runs from the top, under the dot block, with the credits pushed to
   the foot by an auto margin — the order the reference sets out. It used to be
   centred vertically, which put the first line straight through the dots at
   every width the panel was checked at. */
.fo-quote {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-start;
  margin-top: -30px;
  /* Not a straight diagonal: the edge sits almost level for the first third
     and then sweeps up to the corner, the way the reference does it. The
     points trace y = 30(1 − t²) — flat where it meets the left edge, steepest
     as it reaches the right — and eight of them across is enough that it reads
     as a curve rather than a folded line.
     Depth is in px and position in %, so the sweep keeps the same 30px bite
     out of the photo at every width instead of deepening on a wide screen. */
  clip-path: polygon(
    0 30px, 15% 29px, 30% 27px, 45% 24px, 60% 19px, 75% 13px, 88% 7px, 100% 0,
    100% 100%, 0 100%);
  padding: 84px 30px 30px;
  background: linear-gradient(145deg, var(--purple-800) 0%, var(--purple-900) 100%);
  color: #fff;
}
/* The pin-dot block in the panel's high corner. Its height sets the quote's
   top padding above — keep the two in step if you resize it. */
.fo-pattern {
  position: absolute; top: 16px; right: 16px; width: 96px; height: 50px;
  background: radial-gradient(rgba(255, 255, 255, .34) 1.4px, transparent 1.4px) 0 0 / 11px 11px;
  pointer-events: none;
}
/* --- who is speaking ---
   One name under a hairline. It keeps the accent the show name used to carry,
   because with the other two lines gone it is the only thing down here and
   there is nothing left for it to be distinguished from. */
.fo-credit {
  margin-top: auto; padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, .16);
}
.fo-name {
  margin: 0;
  font-size: .95rem; font-weight: 800; letter-spacing: -.01em;
  color: var(--purple-300);
}
/* where the quote comes from, so the attribution can be checked. Quieter than
   the name, since it is a reference rather than the speaker. */
.fo-source {
  margin: 3px 0 0;
  font-size: .76rem; line-height: 1.45;
  color: rgba(255, 255, 255, .52);
}
.fo-source cite { font-style: italic; }

/* The line itself. It used to be one of three stacked in a single grid cell
   and cross-faded, which is what the grid-area and the opacity were for;
   with one line there is nothing to stack against and nothing to fade to. */
.fo-line { position: relative; margin: 0; padding-top: 34px; }
/* An opening mark the quote actually hangs from, so the panel reads as
   somebody speaking rather than as a slogan set in bold. Serif on purpose:
   the page's own face has no real quotation glyph with this much weight. */
.fo-line::before {
  content: "\201C";
  position: absolute; left: -4px; top: -14px;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 5.2rem; line-height: 1;
  color: var(--purple-400); opacity: .55;
}
.fo-line p {
  position: relative; margin: 0;
  font-size: clamp(1.1rem, 1.7vw, 1.42rem); font-weight: 800; line-height: 1.32;
  letter-spacing: -.015em; color: #fff;
}
/* the closing mark sits inline, right after the last word */
.fo-line p::after {
  content: "\201D";
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.15em; line-height: 0; vertical-align: -.18em;
  color: var(--purple-300); margin-left: .04em;
}

/* --- the three claims --- */
.fo-cards { display: grid; gap: 18px; margin-top: 46px; }
.fo-card {
  position: relative; overflow: hidden;
  padding: 34px 24px 26px; text-align: center;
  background: var(--glass-bg-strong);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease);
}
.fo-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg), var(--glass-sheen); }

/* the accent rule across the top, drawn outward from the middle once the card
   arrives — the reference has it static, this gives the row an entrance */
.fo-bar {
  position: absolute; top: 0; left: 50%; translate: -50% 0;
  width: 128px; height: 4px; border-radius: 0 0 3px 3px;
  background: var(--fo-accent);
  transform: scaleX(0); transform-origin: center;
  transition: transform .55s var(--ease-out) .12s;
}
.reveal.is-visible .fo-bar, .fo-card.is-visible .fo-bar { transform: scaleX(1); }

.fo-badge {
  display: grid; place-items: center; width: 54px; height: 54px; margin: 0 auto 16px;
  border-radius: 50%; background: var(--fo-accent); color: #fff;
  box-shadow: 0 10px 22px var(--fo-glow);
  transition: transform .45s var(--spring);
}
.fo-card:hover .fo-badge { transform: translateY(-3px) scale(1.08); }
.fo-badge svg {
  width: 25px; height: 25px;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

.fo-card h3 {
  font-size: 1.02rem; line-height: 1.34; color: var(--purple-900);
  margin: 0 0 14px;
}
/* the second line of the title, ruled underneath like the reference */
.fo-mark-line {
  display: block; width: fit-content; margin-inline: auto;
  box-shadow: inset 0 -.42em 0 var(--fo-wash);
}
.fo-tick {
  display: block; width: 26px; height: 3px; border-radius: 3px;
  margin: 0 auto 14px; background: var(--fo-accent); opacity: .85;
}
.fo-card p { margin: 0; font-size: .86rem; color: var(--muted); }

/* the three accents: deep, mid and light steps of the brand purple */
.fo-card--deep  { --fo-accent: var(--purple-800); --fo-glow: rgba(61, 42, 93, .30);  --fo-wash: rgba(61, 42, 93, .13); }
.fo-card--mid   { --fo-accent: var(--purple-600); --fo-glow: rgba(122, 63, 135, .30); --fo-wash: rgba(122, 63, 135, .14); }
.fo-card--light { --fo-accent: var(--purple-500); --fo-glow: rgba(143, 82, 155, .32); --fo-wash: rgba(143, 82, 155, .15); }

/* --- the same block on a phone -------------------------------------------
   The reference lays this section out centred on a narrow screen: the kicker,
   heading, lede and question all run down the middle, the photo sits shallow
   above the quote, and the three claims stay in one row across rather than
   stacking into three full-width slabs. Below 768px the desktop composition is
   swapped for that. */
@media (max-width: 767px) {
  .fo-grid { gap: 24px; }

  /* everything in the left column runs down the middle */
  .fo-copy { text-align: center; }
  .fo-kicker { justify-content: center; gap: 10px; margin-bottom: 14px; font-size: .66rem; }
  .fo-dash:first-child, .fo-dash:last-child { width: 26px; }
  /* centred, the pair reads best mirrored: the leading rule now fades in from
     the left the way the trailing one fades out to the right. */
  .fo-dash:first-child { background: linear-gradient(90deg, rgba(162, 118, 175, 0), var(--purple-400)); }

  /* the hard break after "becomes" was cut for a 40em column; at phone width it
     left a one-word line. Dropped, so the first two lines wrap to the column
     and only "opportunity" is still forced onto its own line under the rule. */
  .fo-br { display: none; }
  .fo-title {
    font-size: clamp(1.7rem, 7.4vw, 2.15rem);
    line-height: 1.14;
    text-wrap: balance;
    margin-bottom: .45em;
  }
  .fo-underline { bottom: -.13em; }
  .fo-underline path { stroke-width: 6; }

  .fo-lede { max-width: 36ch; margin-inline: auto; margin-bottom: 1.5em; font-size: .88rem; }
  .fo-question { max-width: 26ch; margin-inline: auto; margin-bottom: 18px; }
  .fo-rule { margin-inline: auto; }

  /* the watermark is parked against the left margin, level with the lede. It
     is behind centred body copy here rather than beside a left column, so it
     is lighter than at full width. */
  .fo-watermark { top: 30%; left: -5%; font-size: clamp(8rem, 30vw, 12rem); opacity: .26; }

  /* deeper than the other blocks' photos: a standing portrait squeezed into a
     208px letterbox showed a band of his suit and nothing else */
  .fo-photo { min-height: 290px; }
  .fo-quote { padding: 68px 22px 24px; }   /* clears the dot block above */
  .fo-line p { font-size: 1.02rem; }
  .fo-pattern { width: 72px; height: 40px; top: 14px; right: 14px; }

  /* the three claims stay side by side, scaled to fit rather than stacked */
  .fo-cards { grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 34px; }
  .fo-card { padding: 22px 8px 18px; border-radius: var(--r-sm); }
  .fo-bar { width: 52px; height: 3px; }
  .fo-badge { width: 42px; height: 42px; margin-bottom: 12px; }
  .fo-badge svg { width: 20px; height: 20px; }
  .fo-card h3 { font-size: .77rem; line-height: 1.32; letter-spacing: -.012em; margin-bottom: 10px; }
  /* at a third of the screen the title wraps on its own, so the second line is
     no longer forced. Left as a block it kept its fit-content width from before
     the wrap and the wash ran the whole card across; set inline and cloned per
     line, it hugs the words the way it does at full width. */
  .fo-mark-line {
    display: inline; width: auto; margin-inline: 0;
    box-decoration-break: clone; -webkit-box-decoration-break: clone;
    box-shadow: inset 0 -.36em 0 var(--fo-wash);
  }
  .fo-tick { width: 20px; margin-bottom: 10px; }
  .fo-card p { font-size: .72rem; line-height: 1.52; }
}

/* very narrow phones: the three-across row is the first thing to give */
@media (max-width: 379px) {
  .fo-cards { gap: 7px; }
  .fo-card { padding: 20px 6px 16px; }
  .fo-card h3 { font-size: .72rem; }
  .fo-card p { font-size: .68rem; }
}

/* ---------- 8. Services & Why ---------- */
/* Grain over a spotlight behind the heading, over a dot grid crossed with a
   diagonal weave — enough surface for the deck to cast onto. */
.services {
  background:
    var(--tex-grain),
    radial-gradient(820px 520px at 72% 52%, rgba(255, 255, 255, .90), rgba(255, 255, 255, 0) 62%),
    var(--tex-hatch),
    radial-gradient(rgba(143, 82, 155, .10) 1px, transparent 1px),
    radial-gradient(860px 460px at 88% 2%, rgba(143, 82, 155, .34), transparent 64%),
    radial-gradient(740px 500px at 2% 96%, rgba(122, 63, 135, .30), transparent 68%),
    linear-gradient(180deg, var(--purple-100) 0%, #FBF8FC 55%, var(--purple-50) 100%);
  background-size: 180px 180px, auto, auto, 26px 26px, auto, auto, auto;
}

/* --- the brand photo, bled into the right edge ---
   It sits on the right because the card deck owns the left of this section and
   would bury the sign. Two mask layers intersect: one fades the photo out
   leftward before it reaches the deck, the other softens its top and bottom
   edges, so the picture dissolves into the section rather than sitting in a box. */
.services-photo {
  /* fills the whole right side of the section, top to bottom */
  position: absolute; right: 0; top: 0; bottom: 0; z-index: 0;
  width: min(58%, 780px); pointer-events: none;
  /* the whole storefront, fitted to the section's height and anchored to the
     right edge, so the full place reads rather than a crop of it */
  background: url("../img/inasal-express-hub.jpg") 100% center / auto 100% no-repeat;
  /* the storefront's green reads loud against the purple palette — pulled back
     just enough to sit in the scheme without losing the brand's colours */
  filter: saturate(.85);
  opacity: .34;
  /* Three masks intersect: fade out leftward before the card deck, soften the
     top and bottom edges, and open a hole behind the heading and subhead so
     the type stays clean. The hole sits below the storefront sign, which stays
     at full strength. */
  -webkit-mask-image:
    linear-gradient(270deg, #000 0%, rgba(0, 0, 0, .82) 46%, rgba(0, 0, 0, .3) 76%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 13%, #000 88%, transparent 100%),
    radial-gradient(340px 120px at 58% 52%, transparent 0 40%, rgba(0, 0, 0, .45) 72%, #000 96%);
  mask-image:
    linear-gradient(270deg, #000 0%, rgba(0, 0, 0, .82) 46%, rgba(0, 0, 0, .3) 76%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 13%, #000 88%, transparent 100%),
    radial-gradient(340px 120px at 58% 52%, transparent 0 40%, rgba(0, 0, 0, .45) 72%, #000 96%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
/* narrow screens: the deck runs full width, so the photo sits further back */
@media (max-width: 899px) {
  .services-photo { width: 100%; background-size: cover; opacity: .14; }
}

/* --- card stack --- */
/* the deck fans right and the leaving card flies off-left, so keep both inside
   the section rather than letting them widen the page */
.services { overflow: hidden; --spring: cubic-bezier(.2, 1.25, .35, 1); }

/* deck on the left, heading and free space on the right */
.services-split { display: grid; gap: 40px; align-items: center; }
.services-aside { order: -1; }                 /* heading first while stacked */
.section-head--left { text-align: left; max-width: none; margin-inline: 0; margin-bottom: 0; }

/* --- aside --- */
/* the heading arrives in sequence once the section scrolls in */
.services-aside .kicker,
.services-aside .section-sub {
  opacity: 0; transform: translate3d(0, 14px, 0);
  transition: opacity .45s var(--ease-out), transform .45s var(--ease-out);
}
.services.is-in .services-aside .kicker { opacity: 1; transform: none; }
.services.is-in .services-aside .section-sub { opacity: 1; transform: none; transition-delay: .16s; }
/* delays are for the entrance only */
.services.is-settled .services-aside .kicker,
.services.is-settled .services-aside .section-sub { transition-delay: 0s !important; }

/* width:100% is doing real work here. As a grid item with auto side margins the
   deck would otherwise be sized shrink-to-fit — which, since every card is
   absolutely positioned, means the width of the little controls row underneath
   it: 194px of card on a 350px phone column. */
.card-stack {
  position: relative;
  /* The space under the deck, as padding rather than a margin on the deck: the
     fanned edges of the cards behind reach into it, and the controls — which
     ride on the front card — measure their offset up from the bottom of it. As
     padding it belongs to this box, so that offset is exact. */
  --deck-gap: 44px;
  width: 100%; max-width: 400px; margin: 0 auto; padding-bottom: var(--deck-gap);
}
/* Portrait cards: taller than wide, so the deck takes only part of the row.
   560px was the height with a two-row block of chips above the controls. With
   the chips gone that much card is mostly decoration, so it comes down to the
   copy plus the strip the controls sit in, and a margin for the art to read
   across. */
.stack-deck { position: relative; height: 480px; }

.stack-card {
  --i: 0;                                  /* 0 = front; main.js sets this per card */
  --fan: 1;                                /* 0 while the deck is still off-screen */
  --dx: 0px;                               /* live drag offset, set by main.js */
  --drot: 0deg;
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  /* the deep bottom padding is the strip the controls sit in */
  padding: 30px 30px 92px;
  /* the wash from the reference: purple settling out of the top-left corner
     into a warm cream at the bottom-right. Held in a custom property because
     the touch fallback below swaps the glass underneath it and has to keep the
     wash on top. */
  --deck-wash:
    radial-gradient(128% 96% at 0% 0%, rgba(176, 132, 196, .34), rgba(176, 132, 196, 0) 58%),
    radial-gradient(120% 92% at 100% 100%, rgba(248, 226, 168, .40), rgba(248, 226, 168, 0) 60%);
  background: var(--deck-wash), var(--glass-bg-strong);
  /* each card behind steps right, down, smaller and a touch further rotated.
     Kept shallow so the fanned edges stay inside the deck's own column.
     --fan collapses the whole fan for the deal-in; --dx/--drot ride on top for drag. */
  transform:
    translate(calc(var(--i) * 14px * var(--fan) + var(--dx)),
              calc(var(--i) * 12px * var(--fan) + (1 - var(--fan)) * 46px))
    scale(calc(1 - var(--i) * .04 * var(--fan)))
    rotate(calc(var(--i) * 1.6deg * var(--fan) + var(--drot)));
  transform-origin: 50% 100%;
  z-index: calc(10 - var(--i));
  opacity: calc(1 - var(--i) * .12);
  transition: transform .6s var(--ease-out), opacity .45s var(--ease), box-shadow .45s var(--ease);
  will-change: transform;
}
/* deal-in: the deck arrives as one flat pile, then fans open card by card */
.services:not(.is-in) .stack-card { --fan: 0; opacity: 0; }
.services.is-in .stack-card { transition: transform .55s var(--spring), opacity .4s var(--ease), box-shadow .45s var(--ease); }
.services.is-in .stack-deck > li:nth-child(1) { transition-delay: .05s; }
.services.is-in .stack-deck > li:nth-child(2) { transition-delay: .11s; }
.services.is-in .stack-deck > li:nth-child(3) { transition-delay: .17s; }
/* once dealt, the deck goes back to its normal quick transitions */
.services.is-settled .stack-card {
  transition: transform .6s var(--ease-out), opacity .45s var(--ease), box-shadow .45s var(--ease);
  transition-delay: 0s !important;
}
/* horizontal drags belong to the deck; vertical ones stay with the page scroll */
.stack-card { touch-action: pan-y; }
.stack-card.is-front { box-shadow: var(--shadow-lg), var(--glass-sheen); cursor: grab; }
/* the card follows the pointer 1:1 while dragged, so no easing in the way */
.stack-card.is-dragging { transition: none !important; cursor: grabbing; user-select: none; }
/* the flick: front card sails off to the left, then rejoins the back of the deck */
.stack-card.is-leaving {
  transform: translate(-135%, 10px) rotate(-14deg) scale(.95);
  opacity: 0; z-index: 20;
}
/* used while a card is repositioned behind the scenes, so it doesn't animate back */
.stack-card.no-anim { transition: none; }

/* phones: shallower fan, since the fanned edges eat into a narrow screen */
/* --- the header on a phone ---
   Every number in the bar was picked for a desktop row of six links. On a
   390px screen that same capsule — 74px tall with a 61px logo, floating 14px
   down — took 10% of the screen before anything had been read. The logo is
   most of it: at 61px it nearly fills a 72px bar.
   The bar keeps its shape, just at phone scale. --header-h comes down with it
   so the hero padding, the sticky FAQ column and the anchor offsets all
   follow. */
@media (max-width: 599px) {
  :root { --header-h: 70px; }

  .site-header { --h: 58px; --gap: 10px; }
  .site-header.is-stuck,
  .site-header.is-nav-open { --h: 52px; }

  .brand-logo { height: 42px; }

  .nav-toggle { width: 40px; height: 36px; padding: 8px 9px; }
  .nav-toggle span + span { margin-top: 4px; }
  /* the bars fold onto the middle one, so the travel is one bar plus one gap */
  .nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
}

/* --- the hero on a phone ---
   The two buttons measured 339px of content in a 350px column, so they wrapped
   by three pixels and the call to action took two rows. An even two-column
   grid settles it at any width instead of relying on them happening to fit. */
@media (max-width: 599px) {
  .hero-actions {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 10px; margin-top: 24px;
  }
  .hero-actions .btn {
    padding: 13px 12px; font-size: .86rem; gap: 6px;
    /* long labels shrink rather than pushing the row apart */
    min-width: 0; white-space: nowrap;
  }
  .hero-social { gap: 11px; margin-top: 18px; }
  /* the mark carries the screen here, so it is given more of the width */
  .logo-anim { width: min(78vw, 310px); }
}

/* --- stacked layouts (below 992px): the deck sizes itself ---
   The deck takes its height from its tallest card instead of the fixed 470px
   the desktop composition is built around. All four cards share one grid cell,
   so they still sit on top of each other and still come out the same height —
   but a long card can no longer spill past its own rounded corner, and a short
   one leaves no hole between the copy and its ticks. */
@media (max-width: 991px) {
  .stack-deck { display: grid; height: auto; }
  .stack-card {
    position: relative; grid-area: 1 / 1;
    /* A floor, not a fixed height: the deck still grows for a long card, but a
       short one no longer leaves a stub of a card on a tall screen. The tallest
       card measures about 340px of copy and padding at 360px wide, so this
       leaves room for the art to read without opening the hole the chips used
       to stand in. */
    min-height: 420px;
  }
}

/* phones: shallower fan, since the fanned edges eat into a narrow screen */
@media (max-width: 599px) {
  .card-stack { --deck-gap: 38px; }
  .stack-card {
    padding: 26px 22px 88px;
    transform:
      translate(calc(var(--i) * 12px * var(--fan) + var(--dx)),
                calc(var(--i) * 10px * var(--fan) + (1 - var(--fan)) * 40px))
      scale(calc(1 - var(--i) * .035 * var(--fan)))
      rotate(calc(var(--i) * 1.3deg * var(--fan) + var(--drot)));
  }
}

/* the icon and its label share the top line; the number sits opposite them */
.stack-top { display: flex; align-items: center; gap: 14px; padding-right: 54px; }
.stack-top .round-ico { margin-bottom: 0; flex: none; }
.stack-label {
  font-size: .7rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: var(--purple-700);
}
.stack-num {
  position: absolute; top: 30px; right: 30px;
  font-size: 1.7rem; font-weight: 800; letter-spacing: -.03em;
  color: var(--purple-300);
}
.stack-card h3 {
  font-size: clamp(1.42rem, 4.6vw, 1.66rem); line-height: 1.2; letter-spacing: -.024em;
  color: var(--purple-900); margin: 26px 0 12px;
}
.stack-card p { font-size: .92rem; color: var(--muted); margin: 0; line-height: 1.65; }

/* --- the card's own decoration ---
   A layer the size of the whole card rather than a medallion parked in the
   middle: rings swung out from beyond the bottom-right corner so their arcs
   cross the full face, and the card's mark set large and low.

   It sits on its own layer underneath the copy — the text and the number are
   lifted above it — so it fills the card edge to edge without
   taking any space from the layout or pushing anything around. Nothing here
   affects the flow at all; remove it and the card is laid out identically.

   The contrast is the whole game. The rings are at 5–6% and the mark at 9%
   against a wash that is already pale, which is enough to read as texture
   across a large area and far too little to interfere with type sitting on
   top. It is the same reasoning as the ghosted logo behind the Process
   section, at a smaller scale.

   The mark is the card's own icon, cloned in by main.js, so each icon still
   appears exactly once in the markup. */
.stack-art {
  position: absolute; inset: 0; z-index: 0;
  border-radius: inherit; overflow: hidden;
  pointer-events: none;
}

/* Rings centred past the bottom-right corner and wider than the card, so what
   shows is a set of long arcs sweeping across it rather than a bullseye. The
   spacing is a percentage of the ring block, so it stays even at any card
   size. */
.stack-art::before {
  content: ""; position: absolute;
  left: 82%; top: 78%; width: 210%; aspect-ratio: 1; translate: -50% -50%;
  border-radius: 50%;
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(143, 82, 155, .055) 0 1px, transparent 1px 6.5%),
    radial-gradient(circle,
      rgba(176, 132, 196, .16) 0%, rgba(176, 132, 196, .05) 40%, rgba(176, 132, 196, 0) 64%);
}

/* a second, smaller set from the opposite corner, so the top-left of the card
   is not left bare */
.stack-art::after {
  content: ""; position: absolute;
  left: 4%; top: 6%; width: 116%; aspect-ratio: 1; translate: -50% -50%;
  border-radius: 50%;
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(143, 82, 155, .04) 0 1px, transparent 1px 8%),
    radial-gradient(circle,
      rgba(198, 168, 220, .16) 0%, rgba(198, 168, 220, 0) 58%);
}

/* the mark, large and low. Bottom-right keeps it clear of the heading, which
   is the longest line on the card and the one that would suffer most from
   anything behind it. */
.stack-art svg {
  /* Bleeding off the right edge on purpose: a mark that stops short of the
     border reads as a motif placed on the card, one that runs off it reads as
     the card's own surface. Lifted clear of the chip row so most of it lands
     in the open band rather than behind the chips' white pills. */
  position: absolute; right: -8%; bottom: 11%;
  width: 62%; height: auto;
  fill: none; stroke: var(--purple-700); stroke-width: 1;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .09;
}

/* Everything the card actually says rides above that layer. Without this the
   decoration would paint over the copy: an absolutely positioned child at
   z-index 0 comes after in-flow inline content in the paint order, so "behind"
   has to be stated rather than assumed. */
.stack-top,
.stack-card h3,
.stack-card p { position: relative; z-index: 1; }
.stack-num { z-index: 1; }


/* Whenever a card lands at the front, its contents cascade in behind it. The
   cascade used to finish .9s after the card arrived, which on a flick meant a
   card that had already glided into place still looked empty; it now lands
   inside the glide. */
.services.is-in .stack-card.is-front > .stack-top { animation: svc-in .44s var(--ease) .02s backwards; }
.services.is-in .stack-card.is-front > .stack-num { animation: svc-in .44s var(--ease) .06s backwards; }
.services.is-in .stack-card.is-front > h3        { animation: svc-in .44s var(--ease) .08s backwards; }
.services.is-in .stack-card.is-front > p         { animation: svc-in .44s var(--ease) .13s backwards; }
@keyframes svc-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
/* a card mid-drag shouldn't restart its own content animation */
.stack-card.is-dragging > * { animation: none !important; }

/* The controls ride on the front card instead of sitting under the deck, which
   is what the card reserves its deep bottom padding for. They are laid over the
   deck rather than inside it because the deck is a <ul> and these are not list
   items; the offset is measured up from the bottom of .card-stack, whose
   padding is exactly the gap under the deck. */
.stack-controls {
  position: absolute; z-index: 15;
  left: 24px; right: 24px; bottom: calc(var(--deck-gap) + 22px);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  /* only the controls take the pointer — the card between them stays draggable */
  pointer-events: none;
}
.stack-controls > * { pointer-events: auto; }
@media (max-width: 599px) {
  .stack-controls { left: 20px; right: 20px; bottom: calc(var(--deck-gap) + 20px); }
}
.stack-btn {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 18px;
  border: 1px solid var(--purple-300); border-radius: var(--r-pill);
  background: var(--glass-bg-strong); color: var(--purple-700);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  font: inherit; font-size: .88rem; font-weight: 700; cursor: pointer;
  transition: background var(--t-base) var(--ease), border-color var(--t-base) var(--ease),
              transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
.stack-btn:hover { background: #fff; border-color: var(--purple-500); transform: translateY(-2px); }
.stack-btn:active { transform: translateY(0) scale(.93); transition-duration: var(--t-press); }
.stack-btn svg {
  width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
}
/* both controls are icon-only, so size them as a matched pair of circles */
.stack-btn[data-stack-prev], .stack-btn--next {
  width: 48px; height: 48px; min-height: 0; padding: 0;
  justify-content: center; border-radius: 50%;
}
.stack-btn--next { background: #fff; color: var(--purple-700); box-shadow: var(--shadow-sm); }
.stack-btn--next:hover { background: #fff; border-color: var(--purple-500); }
.stack-btn--next svg { width: 18px; height: 18px; }

.stack-count {
  margin: 0; min-width: 62px; text-align: center;
  font-size: .82rem; font-weight: 800; letter-spacing: .08em; color: var(--purple-700);
}
.stack-slash { margin: 0 5px; color: var(--purple-300); }

.round-ico {
  display: grid; place-items: center; width: 54px; height: 54px; border-radius: 50%;
  background: var(--grad-brand); color: #fff; margin-bottom: 16px;
  box-shadow: 0 10px 20px rgba(87, 40, 102, .25);
}
.round-ico svg { width: 25px; height: 25px; }

/* the deepest tint of the run, lit from the upper right */
/* Grain over a centred spotlight for the heading, over faint rings and a dot
   grid, with a vignette pulling the corners down so the card row reads first. */
.why {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(760px 420px at 50% 26%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 66%),
    var(--tex-dots),
    var(--tex-rings),
    radial-gradient(1200px 700px at 50% 50%, transparent 46%, rgba(87, 40, 102, .18) 100%),
    radial-gradient(820px 460px at 88% 10%, rgba(143, 82, 155, .30), transparent 66%),
    radial-gradient(700px 480px at 0% 94%, rgba(122, 63, 135, .30), transparent 70%),
    linear-gradient(120deg, var(--purple-100), #F6F1F9 55%, var(--purple-200));
  background-size: 180px 180px, auto, 22px 22px, auto, auto, auto, auto, auto;
}
/* --- four numbered cards under a centred header ---
   Reveal: heading lines rise out of clipped boxes, then the cards fade up one
   after another with a spring settle. Hover: the card tilts toward the cursor
   in 3D and a soft glare tracks the pointer. */
.why { --spring: cubic-bezier(.2, 1.25, .35, 1); }

/* text reveal — each line climbs out of its own overflow-hidden box */
.text-reveal { display: block; }
.tr-line { display: block; overflow: hidden; padding-bottom: .06em; }
.tr-line > span {
  display: block;
  transform: translateY(108%);
  transition: transform .5s cubic-bezier(.2, 1, .3, 1);
}
/* any section that has scrolled in releases its own clipped lines */
.is-in .tr-line > span { transform: none; }
.is-in .tr-line:nth-child(2) > span { transition-delay: .07s; }

.why-head .kicker {
  opacity: 0; transform: translate3d(0, 12px, 0);
  transition: opacity .45s var(--ease-out), transform .45s var(--ease-out);
}
.why.is-in .why-head .kicker { opacity: 1; transform: none; }

.why-cards { display: grid; gap: 20px; perspective: 1000px; }
.why-card {
  --ty: 34px;   /* start below, animates to 0 */
  --rx: 0deg;   /* tilt, set from the pointer */
  --ry: 0deg;
  --sc: .96;
  --mx: 50%;    /* glare position */
  --my: 0%;
  position: relative; overflow: hidden;
  background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: var(--r-lg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  padding: 30px 24px 28px;
  box-shadow: var(--shadow-sm);
  opacity: 0;
  transform-style: preserve-3d;
  transform: translateY(var(--ty)) rotateX(var(--rx)) rotateY(var(--ry)) scale(var(--sc));
  transition: opacity .45s var(--ease-out), transform .5s var(--spring),
              box-shadow .35s var(--ease-out), border-color .35s var(--ease);
}
/* staggered entrance — each card follows the one before it */
.why.is-in .why-card { opacity: 1; --ty: 0px; --sc: 1; }
.why.is-in .why-card:nth-child(1) { transition-delay: .05s; }
.why.is-in .why-card:nth-child(2) { transition-delay: .11s; }
.why.is-in .why-card:nth-child(3) { transition-delay: .17s; }
.why.is-in .why-card:nth-child(4) { transition-delay: .23s; }
/* once everything has arrived the delays are cleared so hover reacts instantly */
.why.is-settled .why-card { transition-delay: 0s !important; }
/* while the pointer is moving, drop the easing so the tilt tracks 1:1 */
/* while the pointer is moving the card follows it on a short curve — long
   enough that the hover lift still eases in, short enough that the tilt stays
   attached to the cursor. Cutting the transition outright made the lift snap. */
.why-card.is-tilting {
  transition: transform .2s var(--ease-soft),
              box-shadow .35s var(--ease-out), border-color .35s var(--ease);
}

/* glare that follows the cursor across the card */
.why-card::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(200px circle at var(--mx) var(--my), rgba(255, 255, 255, .55), transparent 62%);
  opacity: 0; transition: opacity .3s var(--ease);
}
.why-card:hover::after { opacity: 1; }
/* gradient accent that draws across the top edge on hover */
.why-card::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px;
  background: var(--grad-brand);
  transform: scaleX(0); transform-origin: left center;
  transition: transform .45s var(--ease);
}
/* the lift rides the same custom property as the entrance so the two compose
   instead of one overwriting the other's transform */
.why.is-in .why-card:hover { --ty: -10px; --sc: 1.02; box-shadow: var(--shadow-lg); border-color: var(--purple-200); }
.why-card:hover::before { transform: scaleX(1); }

/* oversized step number, faint, tucked into the corner */
.why-num {
  position: absolute; top: 14px; right: 20px;
  font-size: 2.8rem; font-weight: 800; line-height: 1; letter-spacing: -.04em;
  color: var(--purple-100);
  transition: color .3s var(--ease);
}
.why-card:hover .why-num { color: var(--purple-200); }

.why-badge {
  position: relative;
  display: grid; place-items: center; width: 56px; height: 56px;
  border-radius: 16px; background: var(--grad-brand); color: #fff;
  box-shadow: 0 12px 24px rgba(87, 40, 102, .28);
  transform: scale(.4) rotate(-20deg); opacity: 0;
  transition: transform .42s var(--spring), opacity .3s var(--ease);
}
.why.is-in .why-badge { transform: none; opacity: 1; }
.why.is-in .why-card:nth-child(1) .why-badge { transition-delay: .12s; }
.why.is-in .why-card:nth-child(2) .why-badge { transition-delay: .18s; }
.why.is-in .why-card:nth-child(3) .why-badge { transition-delay: .24s; }
.why.is-in .why-card:nth-child(4) .why-badge { transition-delay: .30s; }
.why.is-settled .why-badge { transition-delay: 0s !important; }

/* halo that breathes outward while the card is hovered */
.why-badge::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 2px solid var(--purple-400); opacity: 0;
}
.why-card:hover .why-badge::after { animation: badge-halo 1.6s var(--ease) infinite; }
@keyframes badge-halo {
  0%   { transform: scale(1);    opacity: .55; }
  100% { transform: scale(1.55); opacity: 0; }
}

.why-badge svg {
  width: 26px; height: 26px;
  fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
  transition: transform .45s var(--spring);
}
.why.is-in .why-card:hover .why-badge { transform: translateY(-2px) rotate(-6deg) scale(1.08); }
.why-card:hover .why-badge svg { transform: rotate(6deg) scale(1.1); }

.why-card h3 { font-size: 1.05rem; line-height: 1.3; color: var(--purple-900); margin: 22px 0 8px; }
.why-card p { font-size: .88rem; color: var(--muted); margin: 0; line-height: 1.6; }


/* ---------- 8c. Our Story & Commitment ----------
   Holds three answers from the client's asset checklist. Built out of the
   section idiom the rest of the page already uses — .section for the rhythm,
   .section-head for the heading, glass over the brand tokens for the panels —
   so it needs no new layout rules beyond the three blocks below.

   The stat row uses auto-fit rather than a breakpoint: at a phone's width the
   cards fall to one per row, and they reach three across as soon as there is
   room for it, with nothing to keep in step with the other card rows. */
.story {
  position: relative; overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(880px 460px at 50% 40%, rgba(255, 255, 255, .94), rgba(255, 255, 255, 0) 64%),
    var(--tex-dots),
    radial-gradient(720px 460px at 4% 10%, rgba(143, 82, 155, .26), transparent 68%),
    radial-gradient(700px 440px at 98% 92%, rgba(122, 63, 135, .24), transparent 70%),
    /* A bridge, not a flat base. This section now sits between the Franchise
       Opportunity block, which ends on purple-50, and Services, which opens on
       purple-100 — so it starts and finishes on exactly those two values and
       lightens through the middle, where the cards are. Anything flat here
       would put a visible step at both seams. */
    linear-gradient(180deg, var(--purple-50) 0%, #FCFAFD 46%, var(--purple-100) 100%);
  background-size: 180px 180px, auto, 24px 24px, auto, auto, auto;
}


/* --- the journey ---
   A straight rail with three dots in front, and a dashed flight path weaving
   behind the cards with a paper plane at its head. Both the rail fill and the
   plane are driven by one number, --tl-progress, which main.js updates as the
   block is scrolled.

   A phone has no room for two columns either side of a centre line, so below
   768px the rail sits at the left and every card hangs off it. The alternation
   the design asks for is carried by the direction each card arrives from --
   left, right, left -- which reads the same at any width. From 768px up the
   rail moves to the middle and the cards take the sides as well. */
.story-hl { color: var(--purple-500); }

/* This section opens on its title alone -- no standfirst under it -- so it
   carries more weight than the other section heads on the page. Scoped to
   .story rather than raised on .h2, which every other section shares. */
.story .section-head { margin-bottom: 8px; }
.story .h2 {
  font-size: clamp(2.1rem, 4.6vw, 3.35rem);
  line-height: 1.08; letter-spacing: -.03em;
}
.story .kicker { font-size: .78rem; letter-spacing: .24em; margin-bottom: 14px; }

/* --tl-progress is declared on the section, and main.js writes it there, so
   everything inside inherits it -- the rail fill and the capsule in the block,
   and the drifting backdrop that sits outside it. Declaring it on the wrapper
   as well would shadow the section's value for everything below. */
.story { --tl-progress: 0; }
.timeline-wrap { position: relative; }

/* --- the sky the plane crosses ---
   Sits behind the content column (.section > .container is already z-index 1)
   and inside .story's overflow:hidden, so nothing spills into the sections
   either side.

   Each piece carries two movements that must not overwrite each other: the
   scroll shift and the idle drift. They are kept apart by using the standalone
   `translate` property for the scroll and `transform` for the drift -- the two
   compose rather than one winning, which is what would happen if both were
   written as transform. */
.story-sky {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}
.sky-cloud, .sky-tick {
  position: absolute; left: var(--x); top: var(--y);
  /* negative, so the backdrop rises as the story advances */
  translate: 0 calc(var(--tl-progress, 0) * var(--p, 30) * -1px);
  will-change: transform;
}
.sky-cloud {
  width: var(--s); height: var(--s); border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(143, 82, 155, .17), rgba(143, 82, 155, .06) 58%, rgba(143, 82, 155, 0) 76%);
  animation: sky-float var(--t, 20s) var(--ease) var(--d, 0s) infinite;
}
/* short dashes that echo the route's own stroke, so the backdrop belongs to
   the flight rather than being generic bokeh */
.sky-tick {
  width: var(--w, 20px); height: 4px; border-radius: 4px;
  background: var(--purple-300); opacity: .34;
  rotate: var(--r, 0deg);
  animation: sky-skim var(--t, 14s) var(--ease) var(--d, 0s) infinite;
}

@keyframes sky-float {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(16px, -22px, 0) scale(1.09); }
}
@keyframes sky-skim {
  0%, 100% { transform: translate3d(0, 0, 0); opacity: .34; }
  50%      { transform: translate3d(26px, -12px, 0); opacity: .6; }
}

/* the backdrop is the first thing to go when motion is unwelcome */
@media (prefers-reduced-motion: reduce) {
  .sky-cloud, .sky-tick { animation: none; translate: none; }
}

/* --- the flight path, behind everything --- */
.story-flight {
  position: absolute; inset: -10px 0 -10px 0; z-index: 0;
  width: 100%; height: calc(100% + 20px);
  pointer-events: none; overflow: visible;
}
.sf-path, .sf-trail { fill: none; stroke-linecap: round; }
/* Screen-space dashes for the route, so the pattern stays even however the
   block stretches. Deliberately not on the trail: non-scaling-stroke puts the
   dash array into screen units as well, which would undo the pathLength="1"
   normalisation the trail's reveal depends on. */
.sf-path { vector-effect: non-scaling-stroke; }
/* the route, always on show and deliberately faint */
.sf-path { stroke: var(--purple-300); stroke-width: 5; stroke-dasharray: 17 21; opacity: .55; }
/* Not a line that fills in: a short capsule that rides the dashed route just
   behind the plane, the way a progress bead runs along a track. Its length and
   position are set by main.js as dash attributes in the path's own user units —
   a CSS dasharray here would win over those attributes and freeze it. */
.sf-trail { stroke: var(--purple-500); stroke-width: 7; opacity: .85; }

.story-plane {
  position: absolute; top: 0; left: 0; z-index: 1;
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  pointer-events: none;
  transform: translate3d(-200px, -200px, 0);  /* parked until main.js places it */
  opacity: 0; transition: opacity .5s var(--ease);
}
.story-plane.is-flying { opacity: 1; }
.story-plane svg { width: 100%; height: 100%; display: block; overflow: visible; }
.sp-body { fill: var(--purple-400); }
.sp-fold { fill: var(--purple-700); }

/* --- phones: one line down the page, not two ---
   The rail already runs down the left edge at this width and the cards take
   everything to the right of it, so the route has no free channel to fly
   through. It ends up tracing the rail, and two lines in the same strip read
   as a mistake rather than as a flight. The rail carries the journey on its
   own here; the route and the plane come back where there is room for them.

   main.js needs no change: getScreenCTM() returns null on a hidden svg and
   flyPlane leaves early, so nothing is being positioned off-screen. */
@media (max-width: 767px) {
  .story-flight,
  .story-plane { display: none; }
}

/* --- the rail and its steps --- */
.timeline {
  --tl-rail: 15px;                         /* where the rail sits across the block */
  position: relative; z-index: 1; list-style: none;
  margin: 40px 0 44px; padding: 0;
  display: grid; gap: 46px;
}
/* the track, and the fill that grows down it */
.timeline::before,
.timeline::after {
  content: ""; position: absolute; top: 8px; bottom: 8px;
  left: var(--tl-rail); width: 3px; margin-left: -1.5px; border-radius: 3px;
}
.timeline::before { background: var(--purple-100); }
.timeline::after {
  background: linear-gradient(180deg, var(--purple-400), var(--purple-700));
  transform: scaleY(var(--tl-progress)); transform-origin: 50% 0;
}

.tl-step { position: relative; padding-left: calc(var(--tl-rail) + 26px); }
/* the arrival: alternating sides, set here rather than in the markup so a step
   added or removed re-alternates the rest on its own */
.tl-step.reveal { transform: translate3d(var(--tl-from, -26px), 18px, 0); }
.tl-step.reveal.is-visible { transform: none; }
.tl-step:nth-child(odd)  { --tl-from: -26px; }
.tl-step:nth-child(even) { --tl-from: 26px; }

.tl-dot {
  position: absolute; top: 26px; left: var(--tl-rail); z-index: 2;
  width: 15px; height: 15px; margin-left: -7.5px; border-radius: 50%;
  background: var(--white); border: 2px solid var(--purple-200);
  transition: background .45s var(--ease), border-color .45s var(--ease),
              box-shadow .45s var(--ease), transform .45s var(--spring);
}
/* main.js adds is-active when the step reaches the viewport */
.tl-step.is-active .tl-dot {
  background: var(--purple-600); border-color: var(--purple-600);
  box-shadow: 0 0 0 5px rgba(122, 63, 135, .16);
  transform: scale(1.12);
}

/* the card: the illustration takes the edge away from the rail, the date the
   corner nearest it */
.tl-card {
  display: flex; align-items: center; gap: 16px;
  padding: 18px 20px;
  background: var(--glass-bg-strong);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); border-radius: var(--r-lg);
  box-shadow: 0 18px 40px -22px rgba(87, 40, 102, .45), 0 2px 8px rgba(43, 16, 60, .05);
  transition: box-shadow var(--t-base) var(--ease-out);
}
.tl-step.is-active .tl-card {
  box-shadow: 0 26px 52px -22px rgba(87, 40, 102, .5), 0 3px 10px rgba(43, 16, 60, .07);
}
.tl-icon {
  flex: none; display: grid; place-items: center;
  width: 78px; height: 78px; border-radius: 20px;
  background: linear-gradient(150deg, rgba(172, 145, 192, .30), rgba(199, 180, 219, .14));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .75);
}
.tl-icon svg { width: 44px; height: 44px; }
.ic-soft { fill: var(--purple-300); }
.ic-mid  { fill: var(--purple-400); }
.ic-bold { fill: var(--purple-600); }
.ic-line {
  fill: none; stroke: var(--purple-500); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.tl-body { min-width: 0; }
.tl-when {
  margin: 0 0 5px;
  font-size: .68rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  color: var(--purple-500);
}
.tl-title {
  margin: 0 0 7px;
  font-size: 1.06rem; line-height: 1.3; letter-spacing: -.015em; color: var(--purple-900);
}
.tl-text { margin: 0; font-size: .88rem; line-height: 1.65; color: var(--muted); }

/* --- the card's own contents arrive one after another ---
   The step slides in as a block; without this its insides land with it and the
   card reads as a flat panel that appeared. Each part is held back and let go
   in turn once main.js marks the step active, so the tile pops, then the date,
   the title and the copy rise into place behind it.

   Delays sit on the active state only. is-active is never taken off a step --
   the observer stops watching once it lands -- so there is no reverse to keep
   in step with. */
.tl-icon, .tl-when, .tl-title, .tl-text {
  opacity: 0; transform: translate3d(0, 14px, 0);
  transition: opacity .5s var(--ease-out), transform .55s var(--ease-out);
}
/* the tile grows into place rather than rising into it */
.tl-icon { transform: scale(.78) rotate(-8deg); }

.tl-step.is-active .tl-icon,
.tl-step.is-active .tl-when,
.tl-step.is-active .tl-title,
.tl-step.is-active .tl-text { opacity: 1; transform: none; }

.tl-step.is-active .tl-icon  { transition-delay: .06s; }
.tl-step.is-active .tl-when  { transition-delay: .18s; }
.tl-step.is-active .tl-title { transition-delay: .26s; }
.tl-step.is-active .tl-text  { transition-delay: .34s; }

/* and once it has landed the tile keeps a slow drift, so a card that has been
   read does not sit completely dead. The 1s delay clears the entrance above:
   the animation takes over from the settled position, not from mid-transition. */
.tl-step.is-active .tl-icon { animation: tl-float 5.5s var(--ease) 1s infinite; }
@keyframes tl-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -6px, 0); }
}

/* --- card one's bulb actually switches on ---
   The glass warms to a soft yellow, the rays round it brighten with it and a
   glow blooms behind the glass -- twice in quick succession, then it rests.
   All three run off the same 2.8s clock so they light and die together.

   Held behind .is-active so it does not tick over while the card is still
   below the fold, and kept off the shared .ic-* classes so the chart and the
   summit tiles are untouched. */
.tl-icon--lamp { position: relative; }
.tl-icon--lamp svg { position: relative; z-index: 1; }
.tl-icon--lamp::before {
  content: ""; position: absolute; inset: 6px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(250, 221, 125, .95), rgba(250, 221, 125, 0) 72%);
  opacity: 0; pointer-events: none;
}

.tl-step.is-active .tl-icon--lamp::before { animation: lamp-glow 2.8s var(--ease) infinite; }
.tl-step.is-active .tl-icon--lamp .ic-soft { animation: lamp-glass 2.8s var(--ease) infinite; }
.tl-step.is-active .tl-icon--lamp .ic-line { animation: lamp-rays 2.8s var(--ease) infinite; }

@keyframes lamp-glass {
  0%, 17%   { fill: var(--purple-300); }
  23%, 33%  { fill: #FBE58F; }
  39%, 49%  { fill: var(--purple-300); }
  55%, 79%  { fill: #FBE58F; }
  86%, 100% { fill: var(--purple-300); }
}
@keyframes lamp-rays {
  0%, 17%   { stroke: var(--purple-500); opacity: .5; }
  23%, 33%  { stroke: #E9B93C; opacity: 1; }
  39%, 49%  { stroke: var(--purple-500); opacity: .5; }
  55%, 79%  { stroke: #E9B93C; opacity: 1; }
  86%, 100% { stroke: var(--purple-500); opacity: .5; }
}
@keyframes lamp-glow {
  0%, 17%   { opacity: 0; }
  23%, 33%  { opacity: .85; }
  39%, 49%  { opacity: 0; }
  55%, 79%  { opacity: .85; }
  86%, 100% { opacity: 0; }
}

/* --- card two's chart climbs ---
   The bars grow out of their own base one after another and the trend line
   rises behind them, so the tile reads as something going up rather than a
   static picture of a chart. One 3.6s clock, the stagger done with delays.

   transform-box: fill-box makes each bar's own bounding box the reference, so
   transform-origin: 50% 100% is that bar's foot -- without it the origin would
   be the whole svg's, and the bars would swing rather than grow.

   Held behind .is-active, and scoped to this tile so the bulb and the summit
   are untouched. */
.tl-icon--chart .ch-bar,
.tl-icon--chart .ch-trend,
.tl-icon--chart .ch-arrow {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

.tl-step.is-active .tl-icon--chart .ch-bar {
  animation: ch-rise 3.6s var(--ease) infinite;
}
.tl-step.is-active .tl-icon--chart .ch-bar:nth-of-type(2) { animation-delay: .13s; }
.tl-step.is-active .tl-icon--chart .ch-bar:nth-of-type(3) { animation-delay: .26s; }

.tl-step.is-active .tl-icon--chart .ch-trend {
  animation: ch-climb 3.6s var(--ease) .30s infinite;
}
.tl-step.is-active .tl-icon--chart .ch-arrow {
  animation: ch-climb 3.6s var(--ease) .44s infinite;
}

@keyframes ch-rise {
  0%        { transform: scaleY(.16); }
  28%, 72%  { transform: scaleY(1); }
  94%, 100% { transform: scaleY(.16); }
}
/* the line and its arrowhead lift into place as the bars top out */
@keyframes ch-climb {
  0%        { opacity: 0; transform: translateY(7px); }
  30%, 66%  { opacity: 1; transform: translateY(0); }
  88%, 100% { opacity: 0; transform: translateY(7px); }
}

/* --- card three's flag catches the wind ---
   Cloth pinned along one edge: the hoist stays on the pole and everything
   downwind of it moves. scaleX is the cloth filling and emptying, the skew is
   the ripple running through it, and the small rotate is the whole banner
   lifting on a gust. Nothing touches the pole, so the mast stays put.

   transform-box: fill-box makes the flag's own bounding box the reference, so
   transform-origin: 0% 50% is its hoist edge -- without it the origin would be
   the whole svg's and the flag would swing off the mountain instead.

   Held behind .is-active, and scoped to this tile so the bulb and the chart are
   untouched. */
.tl-icon--summit .ic-flag {
  transform-box: fill-box;
  transform-origin: 0% 50%;
}
.tl-step.is-active .tl-icon--summit .ic-flag {
  animation: flag-wave 2.9s var(--ease) infinite;
}

/* uneven on purpose: an even flap reads as a machine, not as wind */
@keyframes flag-wave {
  0%, 100% { transform: scaleX(1) skewY(0deg) rotate(0deg); }
  22%      { transform: scaleX(.88) skewY(4.5deg) rotate(1.4deg); }
  44%      { transform: scaleX(1.03) skewY(-3deg) rotate(-1deg); }
  68%      { transform: scaleX(.92) skewY(3deg) rotate(.8deg); }
  84%      { transform: scaleX(1.01) skewY(-1.4deg) rotate(-.4deg); }
}

/* --- below 768px: the same journey, drawn for one column ---
   main.js swaps the flight path for a narrow route that serpentines down the
   block, crossing the full width in the gaps between the cards. These rules
   give that route somewhere to be seen. */
@media (max-width: 767px) {
  /* The gaps are the only full-width space on this layout, and they are where
     the route crosses, so they are worth the extra height. */
  .timeline { gap: 74px; }

  /* Where the route runs behind a card it comes through the glass as a soft
     blur rather than a line, so it is drawn heavier here to survive that.
     The trail is in user units, which this viewBox squeezes hard on the
     narrow axis -- 7 comes out at about half the weight it has on a desktop,
     hence the jump rather than a nudge. The dashed route is already in screen
     units (non-scaling-stroke), so it only needs a little. */
  .sf-path { stroke-width: 6; opacity: .68; }
  .sf-trail { stroke-width: 12; }

  /* The one thing that should never be lost: at this width the route passes
     under all three cards, and the plane with it. */
  .story-plane { z-index: 3; }

  /* The clouds are sized in pixels for a section three or four times this
     wide. Left alone, the big ones sit mostly past the right edge and read as
     smudges rather than sky. */
  .sky-cloud { width: calc(var(--s) * .58); height: calc(var(--s) * .58); }

  /* --- one card in focus, the other two as ghosts ---
     Each card is most of a screenful at this width, so two of them are always
     half-shown at the edges and there is nothing telling the eye which one it
     is meant to be reading. main.js marks whichever step is nearest the middle
     of the screen; the rest drop back to an outline of themselves. Scrolling
     hands the focus on, so the block reads one card at a time.

     .is-focusing on the list is the switch, and main.js only adds it while it
     is actually tracking. Without JavaScript, and under reduced motion where
     every step is simply lit at once, the class never appears and all three
     cards stay exactly as they are — nothing is left permanently faded.

     Dimmed with opacity and scale rather than a filter: .tl-card carries a
     backdrop-filter for its glass, and a filter on that same element is the
     one combination browsers have never handled the same way. */
  .timeline.is-focusing .tl-card {
    transition: opacity .5s var(--ease-out), transform .5s var(--ease-out),
                box-shadow .5s var(--ease-out);
  }
  .timeline.is-focusing .tl-step:not(.is-focus) .tl-card {
    opacity: .3;
    transform: scale(.95);
    box-shadow: 0 10px 22px -18px rgba(87, 40, 102, .5);
  }
  /* the icon's idle float is a transform on a card that is now a ghost; there
     is no reading it at this opacity and it is the only always-on animation
     in the section */
  .timeline.is-focusing .tl-step:not(.is-focus) .tl-icon { animation: none; }
}

/* --- from 768px the rail takes the middle and the cards take the sides --- */
@media (min-width: 768px) {
  .story-plane { width: 40px; height: 40px; margin: -20px 0 0 -20px; }

  /* the cards were reading as one block at 62px; well over a card-height of
     air between them lets each land on its own */
  .timeline { --tl-rail: 50%; gap: 116px; margin: 64px 0 60px; }
  .tl-step { width: 50%; padding-left: 0; }
  .tl-step:nth-child(odd)  { margin-right: auto; padding-right: 44px; --tl-from: -40px; }
  .tl-step:nth-child(even) { margin-left: auto;  padding-left: 44px;  --tl-from: 40px; }
  .tl-dot { left: auto; margin-left: 0; }
  .tl-step:nth-child(odd)  .tl-dot { right: -7.5px; }
  .tl-step:nth-child(even) .tl-dot { left: -7.5px; }

  /* a short rule from the dot to the card it belongs to */
  .tl-step::before {
    content: ""; position: absolute; top: 33px; width: 30px; height: 3px;
    background: var(--purple-200);
  }
  .tl-step:nth-child(odd)::before  { right: 7px; }
  .tl-step:nth-child(even)::before { left: 7px; }

  /* the illustration keeps to the outer edge, the date to the inner corner */
  .tl-step:nth-child(odd) .tl-when { text-align: right; }
  .tl-step:nth-child(even) .tl-card { flex-direction: row-reverse; }
  .tl-card { padding: 20px 24px; gap: 20px; }
  .tl-icon { width: 92px; height: 92px; border-radius: 24px; }
  .tl-icon svg { width: 52px; height: 52px; }
}

/* reduced motion: the rail and the trail are simply full, every step is on
   show, and the plane and its capsule sit at the end of the route rather than
   travelling it */
@media (prefers-reduced-motion: reduce) {
  .timeline::after { transform: scaleY(1); }
  .tl-step.reveal { transform: none; }
  .tl-dot { transition: none; }
  .story-plane { transition: none; }
  /* the card contents are simply there, and the tile does not drift */
  .tl-icon, .tl-when, .tl-title, .tl-text {
    opacity: 1; transform: none; transition: none;
  }
  .tl-step.is-active .tl-icon { animation: none; }
  /* the bulb stays lit rather than blinking */
  .tl-step.is-active .tl-icon--lamp::before,
  .tl-step.is-active .tl-icon--lamp .ic-soft,
  .tl-step.is-active .tl-icon--lamp .ic-line { animation: none; }
  .tl-icon--lamp .ic-soft { fill: #FBE58F; }
  .tl-icon--lamp .ic-line { stroke: #E9B93C; }
  .tl-icon--lamp::before { opacity: .7; }
  /* the chart is drawn at full height, not climbing */
  .tl-step.is-active .tl-icon--chart .ch-bar,
  .tl-step.is-active .tl-icon--chart .ch-trend,
  .tl-step.is-active .tl-icon--chart .ch-arrow { animation: none; }
  .tl-icon--chart .ch-bar { transform: none; }
  .tl-icon--chart .ch-trend,
  .tl-icon--chart .ch-arrow { opacity: 1; transform: none; }
  /* the flag hangs still */
  .tl-step.is-active .tl-icon--summit .ic-flag { animation: none; }
  .tl-icon--summit .ic-flag { transform: none; }
}
/* --- the three claims under the timeline ---
   Title then description, no icon, on the same glass surface as the pledge
   below so the row belongs to this section rather than arriving as a new kind
   of card. With the tile gone the copy carries the card, so it gets a little
   more room to sit in and the two lines are given a measure to wrap against. */
.story-claims {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px;
}
.sc-card {
  padding: 20px 22px 19px;
  background: var(--glass-bg-strong);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
  transition: transform .45s var(--ease), box-shadow .45s var(--ease);
}
@media (hover: hover) {
  .sc-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md), var(--glass-sheen);
  }
}
.sc-title {
  margin: 0 0 5px;
  font-size: 1.02rem; font-weight: 800; line-height: 1.22; letter-spacing: -.015em;
  color: var(--purple-700);
}
.sc-text { margin: 0; font-size: .86rem; line-height: 1.5; color: var(--muted); }

@media (max-width: 600px) {
  .sc-card { padding: 17px 18px 16px; }
  .sc-title { font-size: .98rem; }
}

/* On a phone the claims stack into three more cards directly under three
   timeline cards, and the journey ends in a column of boxes that all look
   alike. Dropped here, so the story runs to its last step and then straight
   into the commitment. They come back at tablet width, where they sit side by
   side and read as a row rather than more of the same. */
@media (max-width: 767px) {
  .story-claims { display: none; }
}

/* the closing promise: a panel rather than a card row, so it reads as one
   statement and not as another claim to be skimmed */
.story-pledge {
  position: relative; margin: 34px auto 0; max-width: 780px;
  padding: 28px 26px 26px 30px;
  background: var(--glass-bg-strong);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
  overflow: hidden;
}
/* a rule of brand colour down the leading edge, clipped by the panel's radius */
.story-pledge::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 4px;
  background: var(--grad-brand);
}
.story-pledge-label {
  margin: 0 0 10px;
  font-size: .72rem; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: var(--purple-600);
}
.story-pledge-text {
  margin: 0; font-size: .96rem; line-height: 1.7; color: var(--ink-soft);
}

/* --- the commitment reads itself ---
   Karaoke. Every word starts faded and lights up at its own moment, held in
   --at, which main.js works out from the word's own length.

   `both` is what makes this read rather than flicker: each word holds the
   faded colour until its turn comes round and keeps the settled one after,
   so the panel fills in left to right and stays filled. */
.story-pledge { --kw-dim: rgba(74, 58, 92, .28); }
.story-pledge-text .kw { color: var(--kw-dim); }
.story-pledge.is-reading .kw {
  animation: kw-read .78s var(--ease) var(--at, 0s) both;
}
/* the read head catches the word in brand colour on the way past, then lets it
   settle into body ink -- the sweep is visible, the finished text is not loud */
@keyframes kw-read {
  0%   { color: var(--kw-dim); }
  38%  { color: var(--purple-600); }
  100% { color: var(--ink-soft); }
}
/* no observer, or motion unwelcome: the words are simply already read */
.story-pledge.is-read .kw { color: var(--ink-soft); }
@media (prefers-reduced-motion: reduce) {
  .story-pledge.is-reading .kw { animation: none; color: var(--ink-soft); }
}
/* ---------- 9. Process ---------- */
/* Grain over a spotlight behind the step row, over a wide watermark of rings. */
.process {
  position: relative; overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(900px 440px at 50% 56%, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0) 64%),
    var(--tex-rings),
    var(--tex-hatch),
    radial-gradient(760px 480px at 96% 88%, rgba(143, 82, 155, .30), transparent 68%),
    radial-gradient(680px 430px at 2% 4%, rgba(122, 63, 135, .26), transparent 70%),
    var(--white);
  background-size: 180px 180px, auto, auto, auto, auto, auto, auto;
}
/* the logo's own skyline, huge and faint — the box is cropped to the drawing's band
   (the skyline occupies the top ~29% of logo-line.png) */
.process::after {
  content: ""; position: absolute; right: -24px; bottom: 18px; z-index: 0;
  width: min(58vw, 620px); height: calc(min(58vw, 620px) * .29);
  background: url("../img/logo-line.png") 0 0 / 100% auto no-repeat;
  opacity: .08; pointer-events: none;
}
/* --- the stepper card ---
   One panel at a time, the rail across the top carrying both the progress and
   the shortcut to any step. Everything that changes between steps is a class
   main.js moves around; the card itself never resizes, because all four panels
   share one grid cell and the tallest of them sets the height. */
.step-wizard {
  /* how far through the run we are, 0 to 1. main.js writes it; the backdrop
     reads it. Declared here so it resolves even before the first press. */
  --sw-p: 0;
  position: relative; overflow: hidden;
  max-width: 760px; margin-inline: auto;
  padding: 26px 22px 22px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-md), var(--glass-sheen);
}
/* the accent bar down the left edge, as in the reference */
.step-wizard::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: linear-gradient(180deg,
    var(--purple-800) 0%, var(--purple-600) 46%, var(--purple-400) 100%);
}

/* --- the backdrop ---
   Two movements on each shape that must not overwrite one another: the idle
   drift, which runs whether or not anybody touches the card, and the shift
   along --sw-p as the steps advance. The drift is written as `transform` and
   the shift as the standalone `translate` property, so they compose instead of
   the animation winning. */
.sw-orbs { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.sw-orbs span {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--s); height: var(--s); border-radius: 50%;
  translate: calc(var(--sw-p, 0) * var(--k, 0) * 1px) 0;
  background: radial-gradient(closest-side,
    rgba(143, 82, 155, .16), rgba(143, 82, 155, .05) 58%, rgba(143, 82, 155, 0) 76%);
  transition: translate .6s var(--ease);
  animation: sw-drift var(--t, 20s) var(--ease) var(--d, 0s) infinite;
  will-change: transform;
}
@keyframes sw-drift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(14px, -18px, 0) scale(1.08); }
}

/* --- the rail --- */
.sw-rail { position: relative; z-index: 1; display: flex; align-items: center; margin: 0 0 22px; }
.sw-step { display: flex; align-items: center; flex: 1 1 auto; }
.sw-step:first-child { flex: 0 0 auto; }
/* the line belongs to the step it arrives at, so it fills whatever is left */
.sw-step:not(:first-child)::before {
  content: ""; flex: 1 1 auto; min-width: 12px; height: 2px; margin: 0 10px;
  border-radius: 2px; background: var(--line);
  transition: background var(--t-base) var(--ease);
}
.sw-step.is-on::before, .sw-step.is-done::before { background: var(--purple-400); }

.sw-dot {
  display: grid; place-items: center; flex: none;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line); border-radius: 50%; cursor: pointer;
  background: #fff; color: var(--muted);
  font: inherit; font-size: .82rem; font-weight: 800; font-style: italic;
  transition: background var(--t-base) var(--ease), color var(--t-base) var(--ease),
              border-color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease-out);
}
.sw-dot:hover { border-color: var(--purple-400); color: var(--purple-700); }
.sw-step.is-done .sw-dot { background: var(--purple-100); border-color: transparent; color: var(--purple-700); }
.sw-step.is-on .sw-dot {
  background: var(--grad-brand); border-color: transparent; color: #fff;
  box-shadow: 0 0 0 4px rgba(143, 82, 155, .16), 0 8px 16px rgba(87, 40, 102, .26);
}

/* --- the panels ---
   The step travels in the direction you sent it. main.js puts data-dir on the
   card before it swaps the classes, and the two offsets below flip with it, so
   Next slides the old panel out to the left and brings the new one in from the
   right, and Back does the reverse. A plain cross-fade cannot say which way
   you just went; this can.

   The panel leaving is given .is-out for the length of its slide. Without it
   the old panel would vanish on the same frame the class moves, and the swap
   would read as a cut rather than a hand-off. It also leaves a touch faster
   than the new one arrives, so the two are never both at half opacity in the
   middle of the card. */
.sw-stage { position: relative; z-index: 1; display: grid; touch-action: pan-y; }
.sw-panel {
  --sw-in: 38px; --sw-out: -38px;
  grid-area: 1 / 1; position: relative; min-width: 0;
  /* isolate so the watermark number can sit behind the copy without dropping
     behind the card itself */
  isolation: isolate;
  /* visibility, not display: a hidden panel keeps its size, so it still has a
     say in how tall the card is, and it leaves the tab order while it waits */
  visibility: hidden; opacity: 0; transform: translate3d(var(--sw-in), 0, 0);
  transition: opacity .34s var(--ease-out), transform .46s var(--ease-out), visibility .46s;
}
[data-dir="back"] .sw-panel { --sw-in: -38px; --sw-out: 38px; }
.sw-panel.is-on { visibility: visible; opacity: 1; transform: none; }
.sw-panel.is-out {
  visibility: visible; opacity: 0; transform: translate3d(var(--sw-out), 0, 0);
  transition-duration: .28s, .3s, .3s;
}

/* --- and the panel's own contents arrive in order ---
   The panel slides in as one object; these let its parts land a beat apart, so
   the step reads as being assembled rather than pushed on. Held off until the
   panel is the live one, so nothing animates behind the scenes. */
.sw-panel .sw-ico,
.sw-panel .sw-eyebrow,
.sw-panel h3,
.sw-panel .sw-copy,
.sw-panel .sw-cta {
  opacity: 0; transform: translate3d(0, 12px, 0);
  transition: opacity .42s var(--ease-out), transform .46s var(--ease-out);
}
.sw-panel.is-on .sw-ico,
.sw-panel.is-on .sw-eyebrow,
.sw-panel.is-on h3,
.sw-panel.is-on .sw-copy,
.sw-panel.is-on .sw-cta { opacity: 1; transform: none; }
.sw-panel.is-on .sw-ico     { transition-delay: .08s; }
.sw-panel.is-on .sw-eyebrow { transition-delay: .13s; }
.sw-panel.is-on h3          { transition-delay: .17s; }
.sw-panel.is-on .sw-copy    { transition-delay: .22s; }
.sw-panel.is-on .sw-cta     { transition-delay: .30s; }
/* the tile tips as it lands, which is the one flourish the icon gets */
.sw-panel .sw-ico { transform: translate3d(0, 12px, 0) rotate(-6deg) scale(.92); }
.sw-panel.is-on .sw-ico { transform: none; }

.sw-ico {
  display: grid; place-items: center; width: 46px; height: 46px; margin-bottom: 16px;
  border: 1px solid var(--glass-border); border-radius: 14px;
  background: var(--purple-50); color: var(--purple-700);
}
.sw-ico svg { width: 23px; height: 23px; }
.sw-eyebrow {
  margin: 0 0 7px;
  font-size: .68rem; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: var(--purple-600);
}
.sw-panel h3 {
  margin: 0 0 9px; font-size: clamp(1.25rem, 3.2vw, 1.7rem);
  letter-spacing: -.02em; color: var(--purple-900);
}
.sw-copy { margin: 0; max-width: 48ch; font-size: .92rem; color: var(--muted); }
/* the step number blown up to watermark size, as in the reference */
.sw-ghost {
  position: absolute; right: 0; bottom: -12px; z-index: -1;
  font-size: clamp(3.6rem, 11vw, 6.4rem); font-weight: 800; font-style: italic;
  line-height: .78; letter-spacing: -.05em;
  color: rgba(87, 40, 102, .08); pointer-events: none;
}

/* the ask, on the last panel only */
.sw-cta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  margin-top: 20px;
}
.sw-cta-note { font-size: .8rem; font-weight: 600; color: var(--purple-600); }

/* --- Back / Next ---
   The row collapses away on the last step, where the call to action takes over.
   It is collapsed rather than switched off, so the card closes down to it
   instead of jumping a button-height shorter the moment step 4 arrives.

   The padding and the negative margins that cancel it out are what make that
   possible: the row has to clip its own contents to collapse, and without the
   extra room inside the clip box the primary button would lose its shadow on
   every step, not just the last. */
.sw-actions {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 16px -26px -22px;
  padding: 10px 26px 22px;
  overflow: hidden; max-height: 150px;
  transition: max-height .4s var(--ease), opacity .3s var(--ease),
              margin-top .4s var(--ease), padding-top .4s var(--ease), visibility .4s;
}
/* visibility, not display: it also has to leave the tab order, or the last
   step keeps two buttons nobody can see */
.step-wizard.is-final .sw-actions {
  max-height: 0; opacity: 0; visibility: hidden;
  margin-top: 0; padding-top: 0; padding-bottom: 0;
}
.sw-actions .btn { min-width: 120px; }
/* the back arrow leans the way it travels */
.btn:hover .btn-arrow--back { transform: translateX(-4px); }
/* at either end of the run the button stays put rather than disappearing, so
   the row never changes shape underneath the pointer */
.sw-actions .btn[disabled] { opacity: .42; cursor: default; box-shadow: none; }
.sw-actions .btn[disabled]:hover { transform: none; }

/* the flourish sits under the card, out of the way of the stepper */
.process-script { display: none; }

/* phones: the same card, tightened, with the two buttons splitting the row */
@media (max-width: 599px) {
  .step-wizard { padding: 20px 16px 18px; }
  .sw-rail { margin-bottom: 18px; }
  .sw-dot { width: 30px; height: 30px; font-size: .76rem; }
  .sw-step:not(:first-child)::before { min-width: 8px; margin: 0 6px; }
  .sw-ico { width: 40px; height: 40px; margin-bottom: 13px; border-radius: 12px; }
  .sw-ico svg { width: 20px; height: 20px; }
  .sw-copy { font-size: .88rem; }
  .sw-ghost { bottom: -8px; }
  .sw-actions { display: grid; grid-template-columns: 1fr 1fr; margin-top: 12px; }
  .sw-actions .btn { min-width: 0; width: 100%; padding-inline: 14px; }
  /* the CTA takes the full width rather than sitting beside its own note */
  .sw-cta { display: grid; gap: 9px; justify-items: start; }
  .sw-cta .btn { width: 100%; }
}

/* nothing slides, nothing drifts: the step simply changes */
@media (prefers-reduced-motion: reduce) {
  .sw-panel { transform: none; transition: opacity .01s, visibility .01s; }
  .sw-panel.is-out { display: none; }
  .sw-panel .sw-ico,
  .sw-panel .sw-eyebrow,
  .sw-panel h3,
  .sw-panel .sw-copy,
  .sw-panel .sw-cta { opacity: 1; transform: none; transition: none; }
  .sw-orbs span { animation: none; translate: none; transition: none; }
}

/* ---------- 10. Our Brands ---------- */
/* Grain over a spotlight for the intro copy, over a dot grid and diagonal weave. */
.brands {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(820px 500px at 28% 40%, rgba(255, 255, 255, .90), rgba(255, 255, 255, 0) 62%),
    var(--tex-dots),
    var(--tex-hatch),
    radial-gradient(800px 500px at 4% 8%, rgba(143, 82, 155, .34), transparent 66%),
    radial-gradient(740px 440px at 100% 92%, rgba(122, 63, 135, .30), transparent 68%),
    linear-gradient(180deg, #FBF8FC, var(--purple-100));
  background-size: 180px 180px, auto, 24px 24px, auto, auto, auto, auto;
}

/* --- left: the pitch --- */
.brands-grid { display: grid; gap: 34px; align-items: center; }

/* the kicker carries a short rule in front of it, as in the reference */
.kicker--rule { display: flex; align-items: center; gap: 14px; }
.kicker--rule::before {
  content: ""; flex: none; width: 34px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--purple-700), var(--purple-400));
}
/* a flex box fills its line, so text-align on the heading above no longer
   reaches it — a centred head asks for the rule and its label together */
.kicker--center { justify-content: center; }

.brands-title {
  font-size: clamp(2.1rem, 6.4vw, 4rem);
  line-height: 1.03; letter-spacing: -.045em;
  margin: 14px 0 0;
}
/* "Top 3" carries the section, so it is set in Caveat — the same hand used for
   the flourish under the process steps, already loaded, no extra download —
   against the heavy sans of "Featured Brands". Caveat sits small for its point
   size, hence the 1.34em. The gradient fill is progressive: without
   background-clip the text simply stays purple. */
.bt-top {
  display: block;
  font-family: "Caveat", "Segoe Script", cursive;
  font-weight: 700; font-size: 1.34em; line-height: .86;
  letter-spacing: 0; margin-bottom: .04em;
  color: var(--purple-600);
}
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .bt-top {
    background: linear-gradient(96deg, var(--purple-500) 0%, var(--purple-700) 52%, var(--purple-900) 100%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
  }
}

.brands-sub { margin: 18px 0 0; max-width: 32ch; color: var(--muted); font-size: .96rem; }
.brands-intro .btn { margin-top: 26px; padding: 15px 30px; font-size: .98rem; }

/* --- the carousel ---
   A scroll-snap row: a phone swipes it natively, and the arrows only have to
   nudge the same scroll by one card. --bc-per is how many cards a screen shows;
   the card width is worked out from it, so nothing else changes per breakpoint. */
.brand-carousel { position: relative; }
.bc-track {
  --bc-per: 1;
  --bc-gap: 18px;
  display: flex; gap: var(--bc-gap);
  /* the padding gives each card shadow room inside the scroller; the negative
     margin takes it back off the layout */
  padding: 6px; margin: -6px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  /* the snap points step back over that padding, so resting at the first card
     is scrollLeft 0 and at the last one is the very end — which is how the
     arrows know when they have run out of track */
  scroll-padding-inline: 6px;
  /* the dots and the arrows are the controls here — a scrollbar under the cards
     would only cut into them */
  scrollbar-width: none; -ms-overflow-style: none;
}
.bc-track::-webkit-scrollbar { display: none; }
.bc-track:focus-visible { outline: 3px solid var(--purple-500); outline-offset: 4px; border-radius: var(--r-lg); }

/* The outer frame does one job: drift up and down, for ever, on its own. One
   animation, transform only, so it lives on the compositor and costs nothing
   per frame. Each card is given its own speed and a negative delay, so the
   three are out of step rather than bobbing in unison. */
.brand-card {
  position: relative; flex: none; scroll-snap-align: start;
  width: calc((100% - (var(--bc-per) - 1) * var(--bc-gap)) / var(--bc-per));
  display: flex;
  /* the tilt below is a 3D one; this is the depth it is read against */
  perspective: 1000px;
  animation: cardFloat var(--float-dur, 7s) ease-in-out var(--float-off, 0s) infinite alternate;
}
@keyframes cardFloat {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, var(--float-rise, -10px), 0); }
}

/* The body is the card you actually see, and the thing that leans toward the
   pointer. No fixed proportion: the gallery holds the artwork's own 4:5, so
   every card ends the same height. */
.bc-body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  padding: 14px;
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--card-wash);
  border: 1px solid rgba(255, 255, 255, .6);
  box-shadow: 0 20px 44px -20px rgba(87, 40, 102, .5), 0 4px 14px rgba(43, 16, 60, .1);
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform .5s var(--ease-out), box-shadow .5s var(--ease-out);
}
.brand-card:hover .bc-body {
  box-shadow: 0 34px 62px -20px rgba(87, 40, 102, .55), 0 6px 18px rgba(43, 16, 60, .16);
}
/* while the pointer is actually moving over it, the lean should track rather
   than trail half a second behind */
.bc-body.is-live { transition: transform .12s linear, box-shadow .5s var(--ease-out); }
/* a press has to register on a touch screen, where there is no hover at all */
.brand-card:active .bc-body { transform: scale(.985); transition-duration: var(--t-press); }
/* Each card now wears its brand's own colour rather than three shades of the
   site purple. The washes are sampled off the artwork itself — the lavender
   behind the frapp&eacute;, the warm amber behind the fries, the banana-leaf
   olive behind the inasal — and the accent is the strongest colour in that
   brand's logo, which is what the card's Inquire Now button becomes. */
.brand-card--blend {
  --card-wash: linear-gradient(170deg, #F8F1FC 0%, #E9D8F4 58%, #D9C0EC 100%);
  --card-accent: #5B2D90; --card-glow: rgba(91, 45, 144, .42);
  --float-dur: 6.4s; --float-rise: -9px;  --float-off: 0s;
}
.brand-card--fries {
  --card-wash: linear-gradient(170deg, #FFF6E6 0%, #FBDCAE 58%, #F6BE79 100%);
  --card-accent: #D2451C; --card-glow: rgba(210, 69, 28, .40);
  --float-dur: 7.7s; --float-rise: -12px; --float-off: -2.6s;
}
.brand-card--inasal {
  --card-wash: linear-gradient(170deg, #F7F8E8 0%, #E4E8BD 58%, #CBD494 100%);
  --card-accent: #1F6B2E; --card-glow: rgba(31, 107, 46, .40);
  --float-dur: 7s;   --float-rise: -8px;  --float-off: -4.3s;
}

/* --- head strip: just the Featured flag --- */
.bc-top { display: flex; align-items: center; margin-bottom: 12px; }

/* --- the card's own call to action, in the brand's colour --- */
.bc-cta {
  position: relative; overflow: hidden;
  margin-top: 12px; width: 100%; padding: 13px 18px;
  background: var(--card-accent); color: #fff; border-color: transparent;
  box-shadow: 0 12px 24px -10px var(--card-glow);
}
/* a light sweeps across on hover. It only moves while the pointer is on the
   button — nothing here animates on its own, which is what keeps a page with
   three of them from paying for the effect all the way down. */
.bc-cta::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg,
    transparent 36%, rgba(255, 255, 255, .34) 50%, transparent 64%);
  transform: translate3d(-130%, 0, 0);
  transition: transform .75s var(--ease-out);
}
.bc-cta:hover::after { transform: translate3d(130%, 0, 0); }
.bc-cta:hover {
  color: #fff; transform: translateY(-2px);
  box-shadow: 0 18px 32px -12px var(--card-glow);
  filter: brightness(1.09);
}
.bc-cta:active { transform: translateY(0) scale(.985); transition-duration: var(--t-press); }

/* what the visitor gets for pressing it */
.bc-cta-note {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 9px; font-size: .69rem; font-weight: 600;
  color: var(--ink-soft);
}
.bc-cta-note svg {
  flex: none; width: 12px; height: 12px;
  fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round;
}

.brands-cta-note {
  display: flex; align-items: flex-start; gap: 7px;
  margin: 14px 0 0; max-width: 30ch;
  font-size: .8rem; font-weight: 600; color: var(--ink-soft);
}
.brands-cta-note svg {
  flex: none; width: 14px; height: 14px; margin-top: 2px; color: var(--purple-500);
  fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round;
}

.bc-flag {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px 7px 11px; border-radius: var(--r-pill);
  background: var(--grad-brand); color: #fff;
  font-size: .68rem; font-weight: 800; letter-spacing: .03em;
  box-shadow: 0 8px 18px rgba(87, 40, 102, .34);
}
.bc-flag svg { flex: none; width: 13px; height: 13px; fill: #FFC93C; }

.bc-logo {
  width: auto; height: clamp(76px, 9vw, 96px); max-width: 76%;
  object-fit: contain; margin: 42px auto 0;
  filter: drop-shadow(0 10px 20px rgba(28, 12, 40, .18));
}
.bc-tm {
  margin: 12px 0 0; text-align: center;
  font-size: .82rem; font-weight: 800; color: var(--purple-900);
}
.bc-tm-label { font-weight: 700; color: var(--muted); }

/* --- each card's own gallery ---
   The same idiom as the carousel that holds the cards: a scroll-snap row, so a
   phone swipes it natively and the arrows only nudge the same scroll. Nested
   inside the outer one, which is why it contains its own overscroll — swiping
   past the last photo must not start dragging the card carousel behind it. */
/* The artwork is 1122x1402. Giving the gallery that exact ratio means the
   posters are never cropped and every card ends up the same height without a
   single fixed pixel value. */
.bc-gallery {
  position: relative; flex: none; margin-top: 0;
  aspect-ratio: 1122 / 1402; width: 100%;
}
.bc-shots {
  display: flex; gap: 8px; height: 100%; margin: 0; padding: 0; list-style: none;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  scrollbar-width: none; -ms-overflow-style: none;
}
.bc-shots::-webkit-scrollbar { display: none; }
.bc-shots:focus-visible { outline: 3px solid var(--purple-500); outline-offset: 3px; border-radius: var(--r-md); }
.bc-shots > .bc-shot {
  flex: none; width: 100%; height: 100%; margin-top: 0;
  scroll-snap-align: start;
}
.bc-shots > .bc-shot picture { display: block; width: 100%; height: 100%; }
.bc-shots > .bc-shot img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; border-radius: var(--r-md);
}

/* the product shot fills whatever is left under the logo */
.bc-shot { flex: 1 1 auto; min-height: 0; width: 100%; margin-top: 14px; }
img.bc-shot { object-fit: cover; object-position: center bottom; border-radius: var(--r-md); }
/* the stand-in until a photo lands: a plain dashed frame, not a broken image */
.bc-shot--empty {
  display: grid; place-content: center; justify-items: center; gap: 9px;
  padding: 16px; text-align: center;
  border: 1px dashed rgba(87, 40, 102, .3); border-radius: var(--r-md);
  background: rgba(255, 255, 255, .4);
}
.bc-shot--empty svg {
  width: 26px; height: 26px; opacity: .7; color: var(--purple-600);
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.bc-shot--empty span { font-size: .78rem; font-weight: 700; color: var(--purple-700); }

/* --- gallery controls ---
   Arrows and counter in one pill in the corner of the photo. The carousel that
   holds the cards already hangs an arrow on the left and the right, so a second
   pair on the same edges read as a duplicate of those rather than as the
   card's own. Hidden until main.js finds there is something to page through,
   so a brand with a single photo carries no controls at all. */
.bc-gbar {
  position: absolute; right: 8px; bottom: 8px; z-index: 2;
  display: none; align-items: center; gap: 1px;
  padding: 3px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .9);
  /* No backdrop-filter here on purpose. The card above it now drifts, and a
     blur inside a moving element has its backdrop re-sampled every single
     frame — the one thing that would make this effect expensive. The pill is
     opaque enough at .9 not to need it. */
  box-shadow: 0 4px 13px rgba(43, 16, 60, .2);
}
.bc-gallery.has-more .bc-gbar { display: flex; }

.bc-gnav {
  display: grid; place-items: center; width: 26px; height: 26px;
  padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: none; color: var(--purple-700);
  transition: background var(--t-base) var(--ease), transform var(--t-base) var(--ease-out);
}
.bc-gnav svg {
  width: 14px; height: 14px;
  fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
}
.bc-gnav:hover { background: var(--purple-100); }
.bc-gnav:active { transform: scale(.88); transition-duration: var(--t-press); }
.bc-gnav[disabled] { opacity: .28; cursor: default; }
.bc-gnav[disabled]:hover { background: none; }

.bc-count {
  min-width: 34px; text-align: center;
  font-size: .67rem; font-weight: 800; letter-spacing: .04em;
  color: var(--purple-700);
}

/* When the whole row is on screen there is nothing to scroll to, and main.js
   says so by flagging the carousel static. Disabled arrows would just sit
   there greyed out; these go entirely. It is driven by the measured range
   rather than a breakpoint, so adding a fourth brand brings them straight
   back. */
.brand-carousel.is-static .bc-nav { display: none; }

/* --- carousel controls --- */
.bc-nav {
  position: absolute; top: 50%; z-index: 3; transform: translateY(-50%);
  display: grid; place-items: center; width: 44px; height: 44px;
  border: 1px solid var(--glass-border); border-radius: 50%; cursor: pointer;
  background: rgba(255, 255, 255, .92); color: var(--purple-700);
  box-shadow: var(--shadow-md);
  transition: background var(--t-base) var(--ease), color var(--t-base) var(--ease),
              transform var(--t-base) var(--ease-out), opacity var(--t-base) var(--ease);
}
.bc-nav--prev { left: -20px; }
.bc-nav--next { right: -20px; }
.bc-nav:hover { background: #fff; transform: translateY(-50%) scale(1.07); }
.bc-nav:active { transform: translateY(-50%) scale(.93); transition-duration: var(--t-press); }
.bc-nav svg {
  width: 19px; height: 19px;
  fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
}
/* nothing to scroll to: the arrow stays in place but says so */
.bc-nav[disabled] { opacity: .3; cursor: default; box-shadow: none; }
.bc-nav[disabled]:hover { background: rgba(255, 255, 255, .92); transform: translateY(-50%); }

.bc-dots { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 18px; }
/* main.js leaves this empty when every card is already on screen */
.bc-dots:empty { display: none; }
.bc-dot {
  width: 8px; height: 8px; padding: 0; border: 0; border-radius: var(--r-pill);
  background: rgba(87, 40, 102, .24); cursor: pointer;
  transition: width var(--t-base) var(--ease-out), background var(--t-base) var(--ease);
}
.bc-dot.is-on { width: 24px; background: var(--grad-brand); }

/* Below 992px the container keeps only a 20px gutter, so an arrow hung outside
   the track would sit half off the screen. It moves inside the track instead,
   clear of the Featured flag in the opposite corner. */
@media (max-width: 991px) {
  .bc-nav--prev { left: 8px; }
  .bc-nav--next { right: 8px; }
}
/* On a phone the carousel is dropped altogether: the three cards simply run
   down the page, one to three, so a thumb scrolling past sees all of them
   instead of finding two hidden behind a swipe. The scroller becomes a plain
   column, which leaves the track with nothing to scroll — main.js reads that
   as no range, empties the dots and disables the arrows on its own, and the
   rules below take them off the page. Each card keeps its own photo gallery. */
@media (max-width: 599px) {
  .brand-carousel { display: block; }
  .bc-track {
    display: grid; grid-template-columns: 1fr; gap: 20px;
    overflow: visible; scroll-snap-type: none;
    padding: 0; margin: 0;
  }
  .brand-card { width: auto; scroll-snap-align: none; }
  .bc-nav, .bc-dots { display: none; }
}

/* --- the rest of the portfolio --- */
.more-concepts {
  display: grid; gap: 18px; margin-top: 44px; padding: 20px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
}
.mc-head h3 { margin: 0 0 6px; font-size: 1.02rem; color: var(--purple-900); }
.mc-head p { margin: 0; font-size: .82rem; color: var(--muted); }

.mc-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.mc-tile {
  display: grid; justify-items: center; align-content: start; gap: 10px;
  padding: 16px 10px; text-align: center;
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  background: rgba(255, 255, 255, .6);
  font-size: .74rem; font-weight: 700; line-height: 1.35; color: var(--purple-900);
  transition: transform var(--t-base) var(--ease-out), background var(--t-base) var(--ease),
              box-shadow var(--t-base) var(--ease-out);
}
.mc-tile:hover { transform: translateY(-3px); background: #fff; box-shadow: var(--shadow-sm); }
.mc-ico { color: var(--purple-600); }
.mc-ico svg {
  width: 24px; height: 24px;
  fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
/* --- phones: the marks alone ---
   Six captioned tiles in two columns ran three rows deep and repeated the word
   "More" six times. Here only the marks are kept, three across, so the whole
   set is two rows and reads at a glance.

   The captions are hidden from view, not removed: they stay in the document so
   a screen reader still announces each tile, and they come back at 600px where
   there is room for them.

   The tiles are wide but shallow rather than square: a square tile 100px across
   gave a 24px mark 76px of empty room to sit in, and six of those made the
   panel taller than the card above it. At 58px they are still a comfortable
   finger's worth — the 44px minimum is met on both sides — and the whole panel
   comes in at about two thirds of its old height. */
@media (max-width: 599px) {
  .more-concepts { gap: 14px; margin-top: 32px; padding: 16px; }
  .mc-head h3 { margin-bottom: 4px; font-size: .92rem; }
  .mc-head p { font-size: .76rem; }
  .mc-tiles { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .mc-tile {
    align-content: center; gap: 0; padding: 0; min-height: 58px;
    border-radius: var(--r-sm);
  }
  .mc-ico svg { width: 24px; height: 24px; stroke-width: 1.6; }
  .mc-tile--plus .mc-ico { width: 26px; height: 26px; }
  .mc-tile--plus .mc-ico svg { width: 16px; height: 16px; }
  .mc-label {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
  }
}

/* the last tile stands for everything not on the list, so its mark is a plus */
.mc-tile--plus .mc-ico {
  display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  background: var(--purple-100); color: var(--purple-700);
}
.mc-tile--plus .mc-ico svg { width: 17px; height: 17px; stroke-width: 2.2; }

/* an image that has not decoded yet fades into its frame instead of snapping
   in. main.js only ever sets this on an image that has not already arrived. */
img.is-loading { opacity: 0; }

/* ---------- Reviews ---------- */
/* Grain over a spotlight behind the heading, over a dot grid crossed with a
   diagonal weave. Process above it ends on white, so this one starts barely
   tinted and deepens downward, handing off into the FAQ. */
.reviews {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(880px 460px at 50% 20%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 64%),
    var(--tex-dots),
    var(--tex-hatch),
    radial-gradient(820px 480px at 4% 6%, rgba(143, 82, 155, .30), transparent 66%),
    radial-gradient(760px 460px at 98% 94%, rgba(122, 63, 135, .28), transparent 68%),
    linear-gradient(180deg, #FBF8FC 0%, var(--purple-50) 58%, var(--purple-100) 100%);
  background-size: 180px 180px, auto, 24px 24px, auto, auto, auto, auto;
}

/* the recommendation strip under the deck, built like the hero badge so the
   two read as the same claim rather than two different ratings */
.rv-summary {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px 18px;
  width: max-content; max-width: 100%;
  /* it sits under the deck now, so the space it carries is above it */
  margin: 34px auto 0; padding: 8px 20px 8px 8px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  border-radius: var(--r-pill);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
}
.rv-summary-body { display: grid; gap: 1px; }
.rv-summary-note { margin: 0; font-size: .88rem; font-weight: 700; color: var(--ink); }
.rv-summary-note strong { color: var(--purple-700); font-weight: 800; }
.rv-summary-link {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .82rem; font-weight: 800; color: var(--purple-700);
}
.rv-summary-link:hover { text-decoration: underline; }
.rv-summary-link svg {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

/* one star set for the strip and the cards. A half star is the same glyph at
   low opacity, and .is-off is the empty slot in a four-star review. */
.rv-stars { display: flex; gap: 2px; }
.rv-stars svg { width: 14px; height: 14px; fill: var(--purple-500); stroke: none; }
.rv-stars .is-half { opacity: .4; }
.rv-stars .is-off  { opacity: .2; }

/* --- the message deck ---
   One card face up, the rest stacked behind it stepping up and to the right.
   main.js owns exactly one number — which card is at the front — and writes it
   to each item as --i, the distance from the front. Everything else (the
   offset, the scale, the fade, the stacking order) is worked out from that, so
   adding a seventh message needs no change here or in the script. */
.rv-deck { position: relative; padding: 8px 0 0; }

.rv-stage { position: relative; max-width: 620px; margin-inline: auto; }
.rv-stack { display: grid; }

.rv-item {
  --i: 0;
  grid-area: 1 / 1;
  transform:
    translate3d(calc(var(--i) * 13px), calc(var(--i) * -14px), 0)
    scale(calc(1 - var(--i) * .035));
  transform-origin: 50% 100%;
  opacity: calc(1 - var(--i) * .26);
  z-index: calc(10 - var(--i));
  transition: transform .55s var(--ease-out), opacity .45s var(--ease), visibility .45s;
}
/* only three cards are ever worth painting; the rest wait out of sight */
.rv-item.is-back { opacity: 0; visibility: hidden; }
/* the card on its way out sails off to the left, then rejoins the back */
.rv-item.is-leaving {
  transform: translate3d(-130%, 8px, 0) rotate(-9deg) scale(.94);
  opacity: 0; z-index: 20;
}
/* used while a card is put back with no animation, so it does not fly home */
.rv-item.no-anim { transition: none; }

.rv-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 16px;
  min-height: 268px; padding: 28px 30px 30px;
  background: var(--glass-bg-strong); border: 1px solid var(--glass-border);
  border-radius: 22px;
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 26px 54px -22px rgba(87, 40, 102, .45), 0 4px 14px rgba(43, 16, 60, .1);
}
/* a faint ring in the far corner, the way the reference sets one behind the text */
.rv-card::after {
  content: ""; position: absolute; right: -54px; bottom: -66px; z-index: 0;
  width: 220px; height: 220px; border-radius: 50%;
  border: 26px solid rgba(143, 82, 155, .07);
  pointer-events: none;
}
.rv-card > * { position: relative; z-index: 1; }

/* the counter rides the top-right corner of the front card */
.rv-count {
  position: absolute; top: 22px; right: 26px; z-index: 24; margin: 0;
  font-size: .8rem; font-weight: 700; color: var(--muted); letter-spacing: .04em;
}
.rv-count b { font-size: 1.05rem; font-weight: 800; color: var(--purple-800); }

.rv-who { display: flex; align-items: center; gap: 13px; padding-right: 74px; }
/* larger here than in the summary strip: on the deck it is the face of the card */
.rv-card .rv-avatar { width: 52px; height: 52px; }
.rv-card .rv-meta strong { font-size: 1rem; }
/* Only the front card shows its words. There is no blur behind this glass on a
   touch screen, so the card underneath would otherwise read straight through
   the one on top. A card on its way out keeps its own, or it would blank the
   instant it was sent away. */
.rv-item:not(.is-on):not(.is-leaving) .rv-card > * { opacity: 0; }
.rv-text { margin: 0; font-size: .96rem; line-height: 1.7; color: var(--ink-soft); }
/* the opening quote runs into the first line, the way the reference sets it.
   It is drawn here rather than placed in the markup so it cannot be read out
   as a stray character, and so it can never take a line of its own. */
.rv-text::before {
  content: "\201C";
  font-family: Georgia, "Times New Roman", serif;
  font-size: 2.7rem; line-height: 0; vertical-align: -.34em;
  margin-right: 9px; color: var(--purple-300);
}

/* --- the two arrows, one to each side of the card --- */
.rv-nav {
  position: absolute; top: 50%; z-index: 30; transform: translateY(-50%);
  display: grid; place-items: center; width: 46px; height: 46px;
  border: 1px solid var(--glass-border); border-radius: 50%; cursor: pointer;
  background: rgba(255, 255, 255, .92); color: var(--purple-700);
  box-shadow: var(--shadow-md);
  transition: background var(--t-base) var(--ease), transform var(--t-base) var(--ease-out),
              color var(--t-base) var(--ease);
}
.rv-nav svg {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
}
.rv-nav:hover { background: #fff; transform: translateY(-50%) scale(1.08); }
.rv-nav:active { transform: translateY(-50%) scale(.93); transition-duration: var(--t-press); }
/* the deck wraps around, so neither arrow is ever a dead end and neither
   is ever disabled — they sit in the gutter beside the card */
.rv-nav--prev { left: max(0px, calc(50% - 382px)); }
.rv-nav--next { right: max(0px, calc(50% - 382px)); }

/* --- dots --- */
.rv-dots { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 26px; }
.rv-dot {
  width: 8px; height: 8px; padding: 0; border: 0; border-radius: var(--r-pill);
  background: rgba(87, 40, 102, .24); cursor: pointer;
  transition: width var(--t-base) var(--ease-out), background var(--t-base) var(--ease);
}
.rv-dot.is-on { width: 26px; background: var(--grad-brand); }

/* --- the moving backdrop ---
   Everything here is decoration: two dashed trails whose dashes crawl, two
   soft lights drifting on long offset cycles, and a chip that bobs. All of it
   animates transform, opacity or stroke-dashoffset only, so none of it costs a
   layout, and all of it stops under reduced motion. */
.rv-fx { position: absolute; inset: -40px -6px -10px; z-index: 0; pointer-events: none; }
.rv-trail { position: absolute; inset: 0; width: 100%; height: 100%; }
.rv-trail path {
  fill: none; stroke: rgba(143, 82, 155, .38); stroke-width: 2;
  stroke-linecap: round; stroke-dasharray: 9 13;
}
.rv-trail-a { animation: rvCrawl 26s linear infinite; }
.rv-trail-b { animation: rvCrawl 34s linear infinite reverse; }
@keyframes rvCrawl { to { stroke-dashoffset: -220; } }

.rv-orb {
  position: absolute; border-radius: 50%; filter: blur(26px);
  will-change: transform;
}
.rv-orb--a {
  width: 190px; height: 190px; left: 2%; top: 8%;
  background: radial-gradient(circle, rgba(176, 132, 196, .5), rgba(176, 132, 196, 0) 70%);
  animation: rvDriftA 17s var(--ease) infinite alternate;
}
.rv-orb--b {
  width: 240px; height: 240px; right: 0; bottom: 4%;
  background: radial-gradient(circle, rgba(248, 226, 168, .45), rgba(248, 226, 168, 0) 70%);
  animation: rvDriftB 21s var(--ease) infinite alternate;
}
@keyframes rvDriftA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(26px, 34px, 0) scale(1.12); }
}
@keyframes rvDriftB {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(-32px, -26px, 0) scale(.94); }
}

/* the floating chip from the reference, up in the corner beside the deck */
.rv-chip {
  position: absolute; left: 0; top: 16%;
  display: grid; place-items: center; width: 54px; height: 54px;
  border: 1px solid var(--glass-border); border-radius: 18px;
  background: rgba(255, 255, 255, .86); color: var(--purple-600);
  box-shadow: 0 14px 30px -12px rgba(87, 40, 102, .4);
  animation: rvBob 6.5s var(--ease) infinite alternate;
}
.rv-chip svg {
  width: 24px; height: 24px;
  fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
@keyframes rvBob {
  from { transform: translate3d(0, 0, 0) rotate(-4deg); }
  to   { transform: translate3d(0, -16px, 0) rotate(4deg); }
}

/* --- narrow screens --- */
@media (max-width: 991px) {
  /* no gutter left to stand in, so the arrows straddle the card's own edge,
     over its padding rather than over the words */
  .rv-nav--prev { left: -23px; }
  .rv-nav--next { right: -23px; }
  /* the chip would land on the card here */
  .rv-chip { display: none; }
}
@media (max-width: 599px) {
  .rv-card { min-height: 0; padding: 22px 20px 24px; border-radius: 18px; }
  .rv-who { padding-right: 62px; gap: 11px; }
  .rv-count { top: 18px; right: 20px; font-size: .72rem; }
  .rv-count b { font-size: .95rem; }
  .rv-text { font-size: .92rem; }
  .rv-nav { width: 38px; height: 38px; }
  .rv-nav svg { width: 17px; height: 17px; }
  .rv-nav--prev { left: -17px; }
  .rv-nav--next { right: -17px; }
  .rv-dots { margin-top: 20px; }
}

.rv-avatar {
  flex: none; width: 42px; height: 42px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  background: var(--grad-brand); color: #fff;
  font-size: .8rem; font-weight: 800; letter-spacing: .04em;
}
.rv-avatar--img { background: var(--purple-100); }
.rv-avatar img { width: 100%; height: 100%; object-fit: cover; }
.rv-meta { display: grid; gap: 1px; }
.rv-meta strong { font-size: .88rem; font-weight: 800; color: var(--ink); }
.rv-meta > span { font-size: .77rem; color: var(--muted); }

/* ---------- FAQ ---------- */
/* Grain over a spotlight for the intro column, over graph paper and the ring
   watermark. Picks up the tint the Reviews section ends on and clears to white
   for Contact. */
.faq {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(860px 540px at 24% 42%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 62%),
    var(--tex-grid),
    var(--tex-rings),
    radial-gradient(820px 500px at 98% 4%, rgba(143, 82, 155, .32), transparent 66%),
    radial-gradient(700px 460px at 0% 98%, rgba(122, 63, 135, .26), transparent 70%),
    linear-gradient(180deg, var(--purple-100) 0%, #FBF8FC 68%, var(--white) 100%);
  background-size: 180px 180px, auto, 46px 46px, 46px 46px, auto, auto, auto, auto;
}

.faq-grid { display: grid; gap: 30px; align-items: start; }
.faq-intro p { color: var(--muted); font-size: .9rem; }
.faq-call {
  display: flex; align-items: center; gap: 8px;
  margin: 16px 0 0; font-size: .85rem; color: var(--muted);
}
.faq-call svg {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: var(--purple-600); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.faq-call a { font-weight: 800; color: var(--purple-700); }
.faq-call a:hover { text-decoration: underline; }

.faq-list { display: grid; gap: 11px; }

/* Each row is a <details>, so it opens with no JavaScript at all; main.js only
   adds the height animation and closes the others. */
.faq-item {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm), var(--glass-sheen);
  transition: border-color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease);
}
.faq-item[open] { border-color: var(--purple-200); box-shadow: var(--shadow-md), var(--glass-sheen); }

.faq-q {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 15px 18px; cursor: pointer;
  font-size: .94rem; font-weight: 800; line-height: 1.4; color: var(--purple-900);
  transition: color .2s var(--ease);
  /* both are needed to drop the default disclosure triangle */
  list-style: none;
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { color: var(--purple-600); }
.faq-item[open] > .faq-q { color: var(--purple-700); }

/* a plus that rotates into a minus */
.faq-sign {
  position: relative; flex: none; width: 26px; height: 26px; border-radius: 50%;
  background: var(--purple-100);
  transition: background .25s var(--ease), transform .4s var(--ease-out);
}
.faq-sign::before, .faq-sign::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 11px; height: 2px; border-radius: 2px; background: var(--purple-700);
  transform: translate(-50%, -50%);
  transition: background .25s var(--ease), opacity .25s var(--ease);
}
.faq-sign::after { transform: translate(-50%, -50%) rotate(90deg); }
.faq-item[open] .faq-sign { background: var(--purple-600); transform: rotate(180deg); }
.faq-item[open] .faq-sign::before { background: #fff; }
.faq-item[open] .faq-sign::after { opacity: 0; }

/* main.js animates the height of .faq-a; the padding lives on the inner box so
   it is never animated along with it and the text cannot jump at the ends. */
.faq-a { overflow: hidden; }
.faq-a-inner { padding: 0 18px 16px; }
.faq-a p { margin: 0; font-size: .88rem; color: var(--ink-soft); }
.faq-a a {
  font-weight: 700; color: var(--purple-700);
  text-decoration: underline; text-underline-offset: 2px;
}
.faq-a a:hover { color: var(--purple-600); }

/* ---------- Contact ---------- */
/* glow spilling down from the section above, so it hands off into the purple CTA band */
/* Grain over a spotlight for the heading, over graph paper crossed with dots. */
.contact {
  overflow: hidden;
  background:
    var(--tex-grain),
    radial-gradient(880px 460px at 50% 34%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 64%),
    var(--tex-grid),
    radial-gradient(rgba(143, 82, 155, .09) 1px, transparent 1px),
    radial-gradient(980px 480px at 50% -14%, rgba(143, 82, 155, .36), transparent 66%),
    radial-gradient(660px 440px at 2% 100%, rgba(122, 63, 135, .28), transparent 70%),
    var(--white);
  background-size: 180px 180px, auto, 48px 48px, 48px 48px, 24px 24px, auto, auto, auto;
}
.contact-grid { display: grid; gap: 18px; }
/* --- contact cards, after the reference: everything on the centre line ---
   A soft disc instead of the filled brand circle the rest of the page uses,
   then the label, a short rule, then the detail itself. The rule is a small
   thing that does a lot of work: it makes the cards read as one set rather
   than as four stacked paragraphs. */
.contact-card {
  display: block; padding: 30px 20px 26px; color: inherit; text-align: center;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease);
}
a.contact-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); border-color: var(--purple-200); }
.contact-card .round-ico {
  width: 56px; height: 56px; margin: 0 auto 16px;
  background: var(--purple-100); color: var(--purple-700); box-shadow: none;
  transition: background var(--t-base) var(--ease), color var(--t-base) var(--ease);
}
.contact-card .round-ico svg { width: 24px; height: 24px; }
/* the disc fills in on the two cards that are links, so the whole card answers
   the pointer rather than only lifting */
a.contact-card:hover .round-ico { background: var(--grad-brand); color: #fff; }
.contact-card h3 { font-size: 1rem; color: var(--purple-800); margin: 0; }
.contact-card h3::after {
  content: ""; display: block; width: 26px; height: 3px; border-radius: 3px;
  margin: 10px auto 12px; background: var(--grad-brand);
}
.contact-card p {
  font-size: .85rem; color: var(--muted); margin: 0; line-height: 1.6;
  overflow-wrap: anywhere; /* the email is long enough to overflow a narrow card */
}

/* the line that answers "will anyone actually reply?" — the cards give the
   addresses, this gives the expectation */
.contact-note {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  margin: 26px 0 0; text-align: center; font-size: .85rem; color: var(--muted);
}
.contact-note strong { color: var(--purple-800); font-weight: 800; }
.contact-note svg {
  flex: none; width: 17px; height: 17px;
  fill: none; stroke: var(--purple-500); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.contact-cta { margin: 22px 0 0; text-align: center; }

.contact-social { text-align: center; margin: 30px 0 0; font-size: .88rem; color: var(--muted); }
.contact-social a {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 700; color: var(--purple-700);
}
.contact-social a:hover { text-decoration: underline; }
.contact-social svg {
  width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- 11. CTA & Footer ---------- */
.cta { position: relative; overflow: hidden; background: var(--grad-band); color: #fff; }
.cta-media { position: absolute; inset: 0; }
.cta-media img { width: 100%; height: 100%; object-fit: cover; opacity: .3; }
/* the same grain over the dark band, blended so it lifts the surface
   rather than muddying it, plus a diagonal weave for a woven-cloth feel */
.cta::after {
  content: ""; position: absolute; inset: 0;
  background:
    var(--tex-grain),
    repeating-linear-gradient(-45deg, rgba(255, 255, 255, .035) 0 1px, transparent 1px 10px),
    linear-gradient(100deg, rgba(61, 42, 93, .92) 0%, rgba(143, 82, 155, .78) 55%, rgba(143, 82, 155, .86) 100%);
  background-size: 180px 180px, auto, auto;
  background-blend-mode: overlay, normal, normal;
}
.cta-inner { position: relative; z-index: 2; padding: 46px 0; display: grid; gap: 22px; align-items: center; }
.cta-title { color: #fff; font-size: clamp(1.4rem, 3vw, 1.95rem); margin-bottom: .35em; }
.cta-copy p { color: rgba(255, 255, 255, .85); font-size: .9rem; margin: 0; max-width: 58ch; }

.cta-actions { display: grid; justify-items: start; gap: 12px; }
.cta-link { display: inline-flex; align-items: center; gap: 8px; font-size: .82rem; font-weight: 600; color: rgba(255, 255, 255, .9); }
.cta-link:hover { color: #fff; text-decoration: underline; }

.site-footer { background: var(--dark); color: rgba(255, 255, 255, .72); }
.footer-top { display: grid; gap: 28px; padding: 44px 0 32px; align-items: center; }
/* the logo's darker text would vanish on the near-black footer, so it sits on a light plate */
.footer-brand {
  display: inline-flex; align-items: center; justify-content: center;
  background: #fff; border-radius: var(--r-md); padding: 14px 18px;
}
.footer-brand img { width: 150px; height: auto; }

.footer-mantra { font-size: .82rem; line-height: 1.75; margin: 0; color: rgba(255, 255, 255, .72); }

.footer-nav { display: flex; flex-wrap: wrap; gap: 18px; font-size: .85rem; font-weight: 600; }
.footer-nav a { color: rgba(255, 255, 255, .82); transition: color .2s var(--ease); }
.footer-nav a:hover { color: var(--purple-400); }

.footer-bottom {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: space-between;
  border-top: 1px solid rgba(255, 255, 255, .09); padding: 16px 0;
  font-size: .76rem; color: rgba(255, 255, 255, .55);
}
.footer-bottom p { margin: 0; }

/* The two floating buttons: back to the top on the right, the inquiry form
   on the left. They are the same object in two places, so everything except
   which edge they hang off is written once. */
.to-top, .to-inquire {
  position: fixed; bottom: 20px; z-index: 90;
  width: 46px; height: 46px; display: grid; place-items: center;
  border: none; border-radius: 50%; cursor: pointer;
  background: var(--grad-brand); color: #fff; box-shadow: var(--shadow-md);
  opacity: 0; visibility: hidden; transform: translateY(14px) scale(.9);
  transition: opacity var(--t-base) var(--ease), transform .35s var(--ease-out), visibility .35s;
  will-change: transform, opacity;
}
.to-top { right: 20px; }
.to-inquire { left: 20px; }
.to-top.is-visible, .to-inquire.is-visible { opacity: 1; visibility: visible; transform: none; }
.to-top:hover, .to-inquire:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.to-top:active, .to-inquire:active { transform: scale(.92); transition-duration: var(--t-press); }
.to-top svg, .to-inquire svg { width: 20px; height: 20px; stroke-width: 2; }

/* ---------- 12. Modal ---------- */
.modal { position: fixed; inset: 0; z-index: 150; display: grid; place-items: center; padding: 20px; }
.modal[hidden] { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(28, 12, 40, .62); backdrop-filter: blur(3px); animation: fade .28s var(--ease) both; }
/* The dialog leaves the way it came rather than blinking off. These need
   keyframes of their own: reversing `fade`/`pop` would not replay them, since
   by then the entrance has long since finished and the element would simply
   jump to the end value. */
.modal.is-closing { pointer-events: none; }
.modal.is-closing .modal-backdrop { animation: fadeOut .24s var(--ease) both; }
.modal.is-closing .modal-panel { animation: popOut .26s var(--ease) both; }
.modal-panel {
  position: relative; width: min(100%, 540px); max-height: 90vh; overflow-y: auto;
  /* The reference panel is not flat white: it warms into the brand lavender
     towards the foot, under the button.

     Held in a variable because the touch fallback further down replaces this
     background wholesale to drop the live blur. Set literally here, the phone
     — which is where the reference shows this panel — would lose the wash and
     go plain white. */
  --panel-wash:
    linear-gradient(168deg, rgba(255, 255, 255, .90) 0%, rgba(231, 222, 237, .86) 100%);
  background: var(--panel-wash);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg); padding: 32px 28px;
  backdrop-filter: saturate(150%) blur(24px); -webkit-backdrop-filter: saturate(150%) blur(24px);
  box-shadow: var(--shadow-lg); animation: pop .36s var(--ease-out) both;
  overscroll-behavior: contain; scroll-behavior: smooth;
}
.modal-close {
  position: absolute; top: 12px; right: 14px; width: 34px; height: 34px;
  border: none; background: var(--purple-50); color: var(--purple-700);
  border-radius: 50%; font-size: 1.4rem; line-height: 1; cursor: pointer;
}
.modal-close { transition: background var(--t-base) var(--ease), transform .35s var(--ease-out); }
.modal-close:hover { background: var(--purple-100); transform: rotate(90deg); }
.modal-close:active { transform: rotate(90deg) scale(.9); transition-duration: var(--t-press); }
.modal-title { font-size: 1.35rem; }
.modal-sub { font-size: .87rem; color: var(--muted); }

.form { display: grid; gap: 15px; margin-top: 20px; }
.field { display: grid; gap: 7px; }
.field-row { display: grid; gap: 14px; }

/* the label carries the field's icon, and the control repeats it — the pair
   is what lets someone find "mobile number" without reading five labels */
.field label {
  display: flex; align-items: center; gap: 7px;
  font-size: .79rem; font-weight: 700; color: var(--purple-800);
}
.fl-ico {
  flex: none; width: 15px; height: 15px;
  fill: none; stroke: var(--purple-500); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.optional { font-weight: 500; color: var(--muted); }

.field-in { position: relative; display: block; }
/* the icon inside the control. translate is the standalone property, not a
   transform, so anything that later wants to rotate or scale one of these can
   do it without rewriting the centring. */
.fi-ico {
  position: absolute; left: 13px; top: 50%; translate: 0 -50%;
  width: 17px; height: 17px; pointer-events: none;
  fill: none; stroke: var(--purple-300); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  transition: stroke var(--t-base) var(--ease);
}
.field-in:focus-within .fi-ico { stroke: var(--purple-600); }

.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: .9rem; color: var(--ink);
  padding: 12px 14px 12px 40px;
  border: 1px solid var(--line); border-radius: 12px;
  background: rgba(255, 255, 255, .92);
  transition: border-color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease),
              background var(--t-base) var(--ease);
}
/* these two carry no icon of their own, so they start at the normal inset */
.field select, .field textarea { padding-left: 14px; }
.field input::placeholder,
.field textarea::placeholder { color: var(--muted); opacity: .72; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--purple-400);
  box-shadow: 0 0 0 4px rgba(143, 82, 155, .16); background: #fff;
}
.field textarea { resize: vertical; min-height: 96px; line-height: 1.55; }

/* --- the dropdown ---
   NOTHING IN THE FORM USES THIS RIGHT NOW: the "I'm interested in" field was
   taken out. Kept because the form is still being worked on and a select is
   the likeliest thing to come back — delete it if that stops being true.

   It restyles a native <select> rather than replacing one: the phone's own
   picker beats anything rebuilt out of divs, and it arrives with keyboard and
   screen-reader behaviour already working. Only the arrow is ours, which is
   why it can turn. */
.field select { appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 40px; }
.field select:invalid { color: var(--muted); }          /* the "Select an option" row reads as placeholder */
.fi-chev {
  position: absolute; right: 13px; top: 50%; translate: 0 -50%;
  width: 18px; height: 18px; pointer-events: none;
  fill: none; stroke: var(--purple-600); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: rotate .3s var(--ease-out), stroke var(--t-base) var(--ease);
}
.field-in--select:focus-within .fi-chev { rotate: 180deg; }

/* --- when something is wrong ---
   The message is collapsed to nothing until there is one, so the form does not
   carry a blank line under every field waiting for bad news, and it opens
   rather than appearing. The dot is drawn in CSS because main.js writes the
   text with textContent and cannot put an icon in it. */
.error {
  display: flex; align-items: center; gap: 6px;
  color: #C42B2B; font-size: .74rem; font-weight: 600;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .3s var(--ease), opacity .24s var(--ease);
}
.error::before {
  content: "!"; flex: none;
  display: grid; place-items: center; width: 14px; height: 14px;
  border-radius: 50%; background: #C42B2B; color: #fff;
  font-size: .6rem; font-weight: 800; line-height: 1;
}
.field.has-error .error { max-height: 3.2em; opacity: 1; }
.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: #E06A6A; background: #FDF6F6;
  box-shadow: 0 0 0 4px rgba(196, 43, 43, .10);
}
.field.has-error .fi-ico { stroke: #E06A6A; }

/* how much of the message is left. Quiet until it matters, then not. */
.field-count {
  margin: 1px 2px 0 0; text-align: right;
  font-size: .71rem; font-weight: 700; color: var(--muted);
  transition: color var(--t-base) var(--ease);
}
.field-count.is-near { color: var(--purple-600); }
.field-count.is-full { color: #C42B2B; }

.form-note { font-size: .82rem; margin: 0; color: var(--purple-700); font-weight: 600; }
.form-note.is-success { color: #15803D; }

/* --- the send-off ---
   Three beats on one clock: the plane leaves (0 to 1s), the ring and the tick
   draw themselves (0.85s to 1.65s), the words arrive (1.5s). main.js closes
   the dialog at 2.5s, which is the whole run plus a moment to read it.

   Everything hangs off .is-playing rather than running on insertion, so a
   second inquiry in the same visit replays the sequence instead of opening on
   a tick that has already finished drawing. */
.sent { display: grid; justify-items: center; gap: 3px; padding: 14px 0 6px; text-align: center; }
.sent[hidden] { display: none; }
/* the form and the heading stand down while it plays */
.modal-panel.is-sent .form,
.modal-panel.is-sent .kicker,
.modal-panel.is-sent .modal-title,
.modal-panel.is-sent .modal-sub { display: none; }

.sent-stage { position: relative; display: grid; place-items: center; width: 96px; height: 96px; }
.sent-plane { position: absolute; width: 40px; height: 40px; opacity: 0; }
.sent-tick { position: absolute; width: 88px; height: 88px; opacity: 0; }
.st-ring, .st-check {
  fill: none; stroke: var(--purple-600); stroke-width: 4.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* the ring's circumference at r=27 is 169.6, so 170 covers it exactly once */
.st-ring { stroke-dasharray: 170; stroke-dashoffset: 170; }
.st-check { stroke-dasharray: 42; stroke-dashoffset: 42; }

.sent.is-playing .sent-plane { animation: sent-fly 1s var(--ease-out) both; }
.sent.is-playing .sent-tick { animation: sent-pop .45s var(--ease-out) .85s both; }
.sent.is-playing .st-ring { animation: sent-draw .55s var(--ease-out) .9s both; }
.sent.is-playing .st-check { animation: sent-draw .34s var(--ease-out) 1.32s both; }
.sent.is-playing .sent-title { animation: sent-rise .42s var(--ease-out) 1.5s both; }
.sent.is-playing .sent-sub { animation: sent-rise .42s var(--ease-out) 1.62s both; }

@keyframes sent-fly {
  0%   { opacity: 0; transform: translate3d(-24px, 14px, 0) rotate(-10deg) scale(.84); }
  26%  { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
  58%  { opacity: 1; transform: translate3d(9px, -5px, 0) rotate(3deg) scale(1); }
  100% { opacity: 0; transform: translate3d(78px, -48px, 0) rotate(14deg) scale(.66); }
}
@keyframes sent-pop { from { opacity: 0; transform: scale(.8); } to { opacity: 1; transform: none; } }
@keyframes sent-draw { to { stroke-dashoffset: 0; } }
@keyframes sent-rise { from { opacity: 0; transform: translate3d(0, 9px, 0); } to { opacity: 1; transform: none; } }

.sent-title {
  margin: 8px 0 0; font-size: 1.12rem; font-weight: 800;
  letter-spacing: -.015em; color: var(--purple-800); opacity: 0;
}
.sent-sub { margin: 0; font-size: .84rem; color: var(--muted); opacity: 0; }
/* nothing flies: the card is simply there, and main.js still gives it its time */
@media (prefers-reduced-motion: reduce) {
  .sent-plane { display: none; }
  .sent-tick, .sent-title, .sent-sub { opacity: 1; }
  .st-ring, .st-check { stroke-dashoffset: 0; }
}

.form-secure {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin: 2px 0 0; text-align: center;
  font-size: .71rem; line-height: 1.4; color: var(--muted);
}
.form-secure svg {
  flex: none; width: 14px; height: 14px;
  fill: none; stroke: var(--purple-500); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop { from { opacity: 0; transform: translateY(14px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes popOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(10px) scale(.985); } }

body.is-locked { overflow: hidden; }

/* ---------- Reveal on scroll ---------- */
.reveal {
  /* 24px was too small a move to register at this duration — blocks read as
     having been there all along. 34px is far enough to be seen as arriving. */
  opacity: 0; transform: translate3d(0, 34px, 0);
  transition: opacity var(--t-reveal) var(--ease-out), transform var(--t-reveal) var(--ease-out);
  will-change: opacity, transform;
}
.reveal.is-visible { opacity: 1; transform: none; }
/* once it has landed it is ordinary content again.
   (Measured, in case it is ever worth revisiting: dropping the will-change
   above changes nothing here — the page holds 109 composited layers at rest
   with it and 109 without. Chrome is not pre-promoting these either way.) */
.reveal.is-settled { will-change: auto; }

/* the handwritten rule under "Same Dreams. Bigger Tomorrows." draws itself */
.process-script .script-underline path {
  stroke-dasharray: 220; stroke-dashoffset: 220;
  transition: stroke-dashoffset .6s var(--ease-out) .15s;
}
.process-script.is-visible .script-underline path { stroke-dashoffset: 0; }

@media (prefers-reduced-motion: reduce) {
  html, .bc-track { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .reveal { opacity: 1; transform: none; }
  /* no rotating facts: lay all three out, one per line */
  .vp-rotator { display: grid; gap: 2px; }
  .vp-fact { grid-area: auto; animation: none; opacity: 1; transform: none; }
  /* same for the pull quotes — all three listed, nothing on a timer, and the
     rule under the heading is simply there rather than drawn on */
  .fo-underline path { stroke-dasharray: none; stroke-dashoffset: 0; }
  .fo-bar { transform: scaleX(1); }
  .fo-streak { display: none; }
  /* no moving background: swap the video for its still */
  .hero-video { display: none; }
  .hero-still { display: block; }
  /* show the logo whole and still — the layers compose to the original artwork */
  .logo-anim, .logo-anim img, .la-glow, .rec-badge,
  .hero-actions, .hero-social { animation: none; }
  /* the bokeh field stays, but parked — no drifting lights */
  .services::before, .why::before, .brands::before,
  .contact::before, .process::before, .about::before,
  .reviews::before, .faq::before, .fo::before {
    animation: none; transform: none;
  }
  /* Why Choose: no stagger, no tilt, no glare — everything simply present */
  .tr-line > span { transform: none; }
  .why-head .kicker { opacity: 1; transform: none; }
  .why-card { opacity: 1; --ty: 0px; --sc: 1; --rx: 0deg; --ry: 0deg; transform: none; }
  .why-card::after { display: none; }
  .why-badge { opacity: 1; transform: none; }
  .why-card:hover .why-badge::after { animation: none; }
  /* no flying cards: lay the deck out as a plain list and drop the controls */
  .stack-deck { height: auto; display: grid; gap: 16px; }
  .stack-card {
    position: static; transform: none; opacity: 1;
    transition: none; inset: auto;
    /* below 992px the cards share one grid cell so they can stack; here they
       are a list, so each one takes a row of its own again */
    grid-area: auto;
    /* with no controls on the card and nothing to fill out, each one is just as
       tall as its own copy */
    min-height: 0; padding-bottom: 28px;
  }
  .card-stack { padding-bottom: 0; }
  .stack-controls { display: none; }
  .services-aside .kicker, .services-aside .section-sub { opacity: 1; transform: none; }
  /* a scrolling strip is exactly the kind of motion to drop — show the set, static */
  .marquee-track { animation: none; flex-wrap: wrap; justify-content: center; width: 100%; }
  .marquee-track li[aria-hidden="true"] { display: none; }
  .marquee-track li { margin: 12px 26px; }
  .marquee { -webkit-mask-image: none; mask-image: none; }
  .la-line { clip-path: none; }
  .la-glow { opacity: 0; }
  /* no drifting lights and nothing chasing the cursor */
  .hero-plain .hero-fx, .hero-food .hero-fx { display: none; }
  /* the spread is simply there, without the entrance zoom */
  .hero-food .hero-bg-wrap { animation: none; }
  /* the brand cards sit still */
  .brand-card { animation: none; }
  .bc-body { transform: none !important; }
  /* nothing keeps a composited layer it no longer animates */
  .reveal, .scroll-progress, .marquee-track,
  .services::before, .why::before, .brands::before,
  .contact::before, .process::before, .about::before,
  .reviews::before, .faq::before, .fo::before { will-change: auto; }
  /* the underline is simply drawn */
  .process-script .script-underline path { stroke-dasharray: none; stroke-dashoffset: 0; }
}

/* ---------- Touch devices: painted glass, not live glass ----------
   backdrop-filter re-blurs everything behind a pane on every frame of a scroll.
   Two dozen panes is the heaviest thing on this page and a phone feels it
   first, so there the same surfaces carry a solid tint that reads the same. */
@media (hover: none) {
  :root {
    --glass-blur: none;
    --glass-bg: rgba(255, 255, 255, .62);
    --glass-bg-strong: rgba(255, 255, 255, .82);
  }
  .site-header {
    backdrop-filter: none; -webkit-backdrop-filter: none;
    background: rgba(255, 255, 255, .62);
  }
  .site-header.is-stuck { background: rgba(255, 255, 255, .93); }
  .site-header.is-nav-open { background: #fff; }
  .modal-backdrop { backdrop-filter: none; }
  .modal-panel {
    backdrop-filter: none; -webkit-backdrop-filter: none;
    /* the same wash, opaque: there is no live blur behind it here */
    --panel-wash: linear-gradient(168deg, #FFFFFF 0%, #E9E0EF 100%);
  }
  .video-sound {
    backdrop-filter: none; -webkit-backdrop-filter: none;
    background: rgba(34, 17, 48, .82);
  }
}

/* The card deck is the one place where glass sits on glass: the three cards
   behind read straight through the front one and two sets of words land on top
   of each other. Blur was supposed to soften that on a pointer device, but it
   only smears the copy behind rather than hiding it — so a real deck's rule
   applies everywhere: only the edges of the cards underneath show, and the copy
   comes back the moment a card reaches the front. A card on its way out keeps
   its copy, or it would blank the instant it was flicked. Reduced motion is
   left out: there the deck is a plain list, every card is on show, and none of
   them may hold its copy back. */
@media (prefers-reduced-motion: no-preference) {
  .stack-card:not(.is-front):not(.is-leaving) > * { opacity: 0; }
}

/* touch devices have no blur at all, so the cards there need a more solid tint
   under the wash to stop the deck reading through itself */
@media (hover: none) and (prefers-reduced-motion: no-preference) {
  .stack-card { background: var(--deck-wash), rgba(255, 255, 255, .88); }
}

/* =========================================================
   Responsive
   ========================================================= */

/* --- Mobile nav (below 992px) ---
   A sheet that takes the whole screen, not a dropdown hanging off the bar.
   The old panel was as tall as its contents and left the page showing beneath
   it, so the menu read as a tooltip over a page rather than a place of its
   own. inset pins it from the foot of the header to the foot of the screen,
   which also means its height is the screen's and never has to be computed.

   The bar above it stays light. The logo is a colour PNG with dark wordmark
   underneath it and there is no knockout version, so a dark bar would swallow
   half of it — the seam between the white bar and the sheet is the price of
   keeping the mark legible. */
@media (max-width: 991px) {
  /* HEIGHT IS SET, NOT INFERRED FROM THE BOTTOM EDGE — and it has to be.
     .site-header carries a backdrop-filter, and a filtered ancestor becomes
     the containing block for any fixed descendant. So `bottom: 0` here does
     not mean the foot of the screen, it means the foot of the 52px bar, and
     the sheet collapses into a dark band under the logo. Pinning the top and
     giving it an explicit height is right whichever element it resolves
     against, because the bar sits at the top of the screen while it is open.

     dvh after vh: the first is the fallback for browsers without dynamic
     viewport units, the second wins where the mobile toolbars move. */
  .nav {
    position: fixed;
    top: var(--h); left: 0; right: 0; bottom: auto;
    height: calc(100vh - var(--h));
    height: calc(100dvh - var(--h));
    flex-direction: column; align-items: stretch; gap: 0;
    background:
      radial-gradient(130% 62% at 100% 0%, rgba(143, 82, 155, .5), transparent 64%),
      linear-gradient(168deg, var(--purple-800) 0%, var(--purple-900) 100%);
    border: 0; box-shadow: none;
    /* the foot inset keeps the actions clear of the home indicator */
    padding: 20px 22px calc(22px + env(safe-area-inset-bottom, 0px));
    transform: translate3d(0, -14px, 0); opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .3s var(--ease), transform .34s var(--ease-out), visibility .34s;
    overflow-y: auto; overscroll-behavior: contain;
  }
  .nav.is-open { transform: none; opacity: 1; visibility: visible; pointer-events: auto; }

  /* the desktop bar centres the list with auto side margins; in the sheet that
     would shrink every row to its own text width instead of filling it, so it
     is handed back to stretch */
  .nav-list { flex-direction: column; align-items: stretch; gap: 0; margin-inline: 0; }
  .nav-list a {
    display: flex; align-items: baseline; gap: 13px;
    padding: 16px 2px;
    border-bottom: 1px solid rgba(255, 255, 255, .15);
    font-size: clamp(1.22rem, 5.6vw, 1.7rem);
    font-style: italic; font-weight: 800; line-height: 1.12; letter-spacing: -.012em;
    color: rgba(255, 255, 255, .9);
  }
  /* the label is a bare text node, which flexbox makes an item of its own —
     that is what the gap above sits in */
  .nav-link::after { display: none; }
  .nav-link:hover, .nav-link:focus-visible, .nav-link.is-active { color: #fff; }

  .nav-num {
    display: block; flex: none;
    font-size: .68rem; font-style: normal; font-weight: 800; letter-spacing: .1em;
    color: var(--purple-300);
    transition: color var(--t-base) var(--ease);
  }
  .nav-link.is-active .nav-num { color: var(--purple-200); }

  /* The two actions sit at the foot of the sheet: auto margin above the first
     takes up whatever the rows leave. If the rows ever outgrow the screen the
     auto margin resolves to nothing and the sheet simply scrolls to them. */
  .nav-cta {
    margin-top: auto; width: 100%;
    padding: 15px 26px; font-size: 1rem;
    /* --grad-brand ends on --purple-800, which is the top of this sheet: the
       button's own tail would vanish into it. Lifted a step so the fill reads
       as a button at the dark foot of the screen. */
    background: linear-gradient(135deg, var(--purple-400) 0%, var(--purple-600) 100%);
    box-shadow: 0 12px 26px rgba(0, 0, 0, .34);
  }
  .nav-call {
    display: inline-flex; width: 100%;
    margin-top: 11px; padding: 15px 26px; font-size: 1rem;
    background: transparent; color: #fff;
    border-color: rgba(255, 255, 255, .42);
  }
  .nav-call:hover, .nav-call:focus-visible {
    background: rgba(255, 255, 255, .1); border-color: rgba(255, 255, 255, .72);
  }
  .nav-call svg {
    width: 17px; height: 17px; flex: none;
    fill: none; stroke: currentColor; stroke-width: 1.8;
    stroke-linecap: round; stroke-linejoin: round;
  }

  .nav-toggle { display: block; }
}

/* --- About while the split is stacked (below 992px) ---
   The video leads: it is what earns the rest of the section a look, and it is
   the order the reference reads in. Everything under it then runs as one
   straight column — caption pill, endorsement chip, headline, copy, the four
   cards, then the buttons. */
@media (max-width: 991px) {
  .about-split { gap: 26px; }
  .about-right { order: -1; }                  /* media panel first */

  /* with the column at full width, an uncapped panel would be half a tablet
     screen tall; the pill is held to the same width so the two line up */
  .media-panel, .vlog-pill { max-width: 560px; }

  /* a touch smaller than the shared clamp, so "Franchising" still fits on one
     line down to 320px */
  .about-headline { font-size: clamp(1.55rem, 7vw, 2.4rem); }
  .about-rule { width: 88px; height: 4px; margin: 16px 0 18px; }
  .rec-chip { margin-bottom: 14px; }

  /* the pill sits directly under the video here, so it carries less padding and
     is free to wrap rather than stretch the column */
  .vlog-pill {
    margin-top: 14px; padding: 9px 15px;
    gap: 3px 7px; font-size: .66rem; letter-spacing: .06em;
  }

  .about-stats { gap: 10px; margin: 22px 0 24px; }
  .about-stats li { padding: 14px 13px 13px; }
  .as-ico { margin-bottom: 9px; }

  .about-cta { margin: 0 0 14px; }
  /* the note wraps onto two lines on a phone; the shield stays on the first */
  .about-note { align-items: flex-start; font-size: .74rem; }
  .about-note svg { margin-top: 4px; }
}

/* --- About on phones --- */
@media (max-width: 599px) {
  /* stacked and full width — side by side, each button is an awkward thumb
     target and the labels wrap */
  .about-cta { display: grid; grid-template-columns: 1fr; gap: 10px; }
  .about-cta .btn { width: 100%; }

  /* --- Why Choose MLSN: a list, not four stacked cards ---
     The card puts the badge on a line of its own, then the heading 22px
     below it, then the text. That is a good shape in a four-across row and
     a poor one in a single column: four of them ran close to a thousand
     pixels, more than a phone screen of scrolling for four short claims.

     Here the same four become rows -- badge on the left, claim and line
     beside it -- which is roughly half the height. Nothing is hidden behind
     a tap and no copy is cut; the layout simply stops spending a card's
     worth of height on each one. From 600px up the cards are unchanged. */
  .why-cards { gap: 12px; }
  .why-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 14px; align-items: start;
    padding: 15px 17px 16px 20px;   /* the extra on the left clears the rule */
  }
  .why-badge { grid-column: 1; grid-row: 1 / span 2; width: 46px; height: 46px; border-radius: 13px; }
  .why-badge svg { width: 22px; height: 22px; }
  /* the 22px that separated the badge from the heading is the gap now */
  .why-card h3 { grid-column: 2; margin: 2px 0 3px; font-size: .98rem; }
  .why-card p  { grid-column: 2; font-size: .84rem; line-height: 1.55; }

  /* The number was a 2.8rem watermark in a corner, and a row has no corner
     to put it in -- shrunk down to fit beside the text it stops being a
     design element and becomes something else to read. It goes. display:none
     also takes it out of the grid, which is what lets the two cells above
     fall into place on their own. */
  .why-num { display: none; }

  /* The accent only ever appeared on hover, which no phone has. Turned down
     the left edge it is simply on, and gives the row the colour that the
     badge-above-heading arrangement used to carry. */
  .why-card::before {
    left: 0; right: auto; top: 0; bottom: 0;
    width: 4px; height: auto; transform: none;
  }

  /* --- Contact: the details as rows ---
     Everything on the centre line is the right shape for four cards side by
     side, which is what this becomes at 992px. In one column it spends about
     150px of height on one line of information, and the little rule under
     each label -- which is there to tie four cards into a set -- has nothing
     left to tie once they are stacked one under another.

     So the disc moves to the left and the label and the detail stack beside
     it. Each card is then as tall as what is in it: the phone and the email
     are one line, the hours two, the address three. */
  .contact-grid { gap: 10px; }
  .contact-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 14px; align-items: center;
    text-align: left;
    padding: 14px 16px;
  }
  .contact-card .round-ico {
    grid-column: 1; grid-row: 1 / span 2;
    width: 46px; height: 46px; margin: 0;
  }
  .contact-card .round-ico svg { width: 21px; height: 21px; }
  .contact-card h3 { grid-column: 2; font-size: .92rem; }
  /* a centred rule under a left-aligned label is just a gap */
  .contact-card h3::after { display: none; }
  .contact-card p {
    grid-column: 2; margin-top: 2px;
    font-size: .84rem; line-height: 1.45;
  }

  /* Two of the four are links -- the number and the address you would tap --
     and the only thing that ever said so was a hover fill on the disc, which
     no phone can reach. They wear it from the start instead, and their detail
     takes the brand colour, so the two you can act on look different from the
     two that are only telling you something. */
  a.contact-card .round-ico { background: var(--grad-brand); color: #fff; }
  a.contact-card p { color: var(--purple-700); font-weight: 700; }
}

/* --- ≥600px --- */
@media (min-width: 600px) {
  .why-cards, .contact-grid { grid-template-columns: repeat(2, 1fr); }
  /* two brand cards fit from here up; the third is a swipe or an arrow away */
  .bc-track { --bc-per: 2; }
  .mc-tiles { grid-template-columns: repeat(3, 1fr); }
  /* deck has more room from here up — tighten the fan on phones below */
  .field-row { grid-template-columns: 1fr 1fr; }
  .cta-actions { grid-auto-flow: column; align-items: center; justify-content: start; gap: 22px; }
  .footer-bottom { font-size: .8rem; }
}

/* --- ≥768px --- */
@media (min-width: 768px) {
  .section { padding: 84px 0; }
  /* the reference puts the three claims in one row; there is room for them
     from here up, and the photo can move back beside the quote */
  .fo-cards { grid-template-columns: repeat(3, 1fr); }
  .fo-panel { grid-template-columns: minmax(0, .82fr) minmax(0, 1fr); }
  .fo-photo { min-height: 340px; }
  /* Side by side now, so the same cut turns on its side: the dark half laps
     over the photo's right edge and its leading edge leans, flush at the foot
     and set in at the head. Same idea as the stacked version, rotated with the
     layout — a horizontal slope here would cut across the wrong axis. */
  .fo-quote {
    margin-top: 0; margin-left: -30px;
    /* the same sweep turned on its side: level where it meets the top, curving
       away to the foot */
    clip-path: polygon(
      30px 0, 29px 15%, 27px 30%, 24px 45%, 19px 60%, 13px 75%, 7px 88%, 0 100%,
      100% 100%, 100% 0);
    /* top still clears the dot block, left clears the curved edge */
    padding: 84px 30px 30px 54px;
  }
  /* 66px was less than the header is tall, so the badge sat behind the bar
     between 768 and 991px. The top padding has to clear the header the same
     way it does at every other width. */
  .hero-inner { padding: calc(var(--header-h) + 20px) 0 84px; }
  .about-stats { grid-template-columns: repeat(4, 1fr); }
  .modal-panel { padding: 38px 34px; }
}

/* --- ≥992px: full desktop composition --- */
@media (min-width: 992px) {
  .section { padding: 92px 0; }

  /* Hero — video runs the full width of the section, subject centred */
  .hero-media img, .hero-media video { object-position: 58% 46%; }
  /* wash centred on the logo, thinning towards the edges so the video still shows */
  .hero-media::before {
    background:
      /* soft band under the header so the nav keeps contrast on the video */
      linear-gradient(180deg, rgba(252, 250, 253, .8) 0%, rgba(252, 250, 253, .4) 62px, rgba(252, 250, 253, 0) 140px),
      radial-gradient(ellipse 58% 92% at 50% 50%,
        rgba(251, 248, 252, .58) 0%, rgba(249, 246, 251, .5) 42%,
        rgba(247, 244, 250, .27) 74%, rgba(245, 241, 248, .04) 100%),
      linear-gradient(90deg, rgba(250, 247, 251, .32) 0%, rgba(245, 241, 248, 0) 26%,
        rgba(245, 241, 248, 0) 74%, rgba(250, 247, 251, .24) 100%);
  }

  /* one full screen; the heavier bottom padding biases the centred logo upward */
  .hero { min-height: 100vh; min-height: 100svh; }
  .hero-inner {
    display: flex; flex-direction: column; justify-content: center;
    padding: calc(var(--header-h) + 16px) 0 96px;
  }
  .hero-copy { max-width: 820px; }

  /* About — a full screen of its own.
     The strip opens the section at its natural height, and the copy/video
     split takes whatever is left and sits centred in it, so the section fills
     the viewport instead of being a short band followed by empty page. */
  .section.about {
    min-height: 100vh; min-height: 100svh;
    display: flex; flex-direction: column;
  }
  .section.about > .container {
    flex: 1;
    display: grid; align-content: center;
  }
  .about-split { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 56px; }
  .about-right { justify-items: stretch; }

  /* The Franchise Opportunity — argument left, panel slightly wider on the right */
  .fo-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.12fr); gap: 58px; }
  .fo-cards { gap: 24px; margin-top: 58px; }
  .fo-photo { min-height: 380px; }
  /* still side by side here, so the clearance stays on the leading edge */
  .fo-quote { padding: 86px 38px 34px 62px; }

  /* Services */

  /* Services — portrait deck on the left, content column on the right */
  /* the gap clears the fanned card edges, which reach ~42px past the front card */
  .services-split { grid-template-columns: minmax(0, 400px) minmax(0, 1fr); gap: 86px; }
  .services-aside { order: 0; }
  .card-stack { margin: 0; }

  /* Contact */
  .contact-grid { grid-template-columns: repeat(4, 1fr); }

  /* Why choose */
  .why-cards { grid-template-columns: repeat(4, 1fr); gap: 22px; }

  /* Process */
  .step-wizard { padding: 34px 38px 28px; }
  /* a floor under the panel, so a one-line step does not leave a thin card
     between two roomy ones */
  .sw-stage { min-height: 196px; }
  .process-script {
    display: block; text-align: right;
    max-width: 760px; margin: 24px auto 0;
  }

  /* Our Brands — all three cards on screen at once, with no arrow to press:
     on a desktop the whole shortlist should be readable at a glance.
     The pitch sits above them by default rather than beside them. Three cards
     AND a column of text only fit side by side once the window is very wide;
     below that the split makes the cards smaller than stacking does, which is
     the opposite of the point. The side-by-side version returns at 1400px. */
  .brands-grid { grid-template-columns: minmax(0, 1fr); gap: 30px; }
  .brands-intro { max-width: 62ch; }
  .brands-sub { max-width: 58ch; }
  .bc-track { --bc-per: 3; --bc-gap: 20px; }
  .bc-body { padding: 18px; }
  .more-concepts {
    grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
    align-items: center; gap: 28px; padding: 22px 26px;
  }
  .mc-tiles { grid-template-columns: repeat(6, 1fr); }

  /* FAQ — the intro column holds its place while the answers scroll past it.
     It can stick because .reveal drops its transform once it has landed; a
     transformed ancestor would otherwise make position:sticky do nothing. */
  .faq-grid { grid-template-columns: minmax(0, .85fr) minmax(0, 1.7fr); gap: 56px; }
  .faq-intro { position: sticky; top: calc(var(--header-h) + 14px); }

  /* CTA */
  .cta-inner {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 40px; padding: 52px 0;
  }
  .cta-actions { justify-items: center; grid-auto-flow: row; justify-content: center; gap: 12px; }

  /* Footer */
  .footer-top {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 34px; padding: 40px 0 30px;
  }
  .footer-mantra { border-left: 1px solid rgba(255, 255, 255, .14); padding-left: 26px; }
}

/* --- ≥1200px polish --- */
@media (min-width: 1200px) {
  .to-top { right: 28px; bottom: 28px; }
  .to-inquire { left: 28px; bottom: 28px; }

  /* The shared 1180px column was drawn for a page of text. Three brand cards
     beside a heading do not fit in it at any useful size — they come out
     around 250px, which is the size this row started at and was widened away
     from. This section alone gets a wider column, the way About already does,
     and the cards come back up over 330px. */
  .brands > .container { width: min(100% - 56px, 1420px); }
}

/* Wide enough at last for the pitch to sit beside the cards and still leave
   them over 300px each. Measured, not guessed: below this the split produces
   a smaller card than simply stacking does. */
@media (min-width: 1400px) {
  .brands-grid {
    grid-template-columns: minmax(0, .72fr) minmax(0, 1.95fr);
    gap: 44px; align-items: center;
  }
  .brands-intro { max-width: none; }
  .brands-sub { max-width: 32ch; }
}

/* --- Big screens: About is a full screen of its own, so it gets a wider
       column than the rest of the page. At the shared 1180px the video column
       worked out to ~548px, which is under a third of a large display and
       made the player read as an afterthought. The split also tips toward the
       video here, since the copy beside it is short. --- */
@media (min-width: 1280px) {
  .section.about > .container { width: min(100% - 72px, 1360px); }
  .about-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: 64px; }
}
@media (min-width: 1600px) {
  .section.about > .container { width: min(100% - 96px, 1500px); }
}
