/* ==========================================================================
   FAMILY TREE DENTAL — Global styles

   Structure of this file
   ---------------------------------------------------------------------------
   01. Design tokens
   02. Reset & base
   04. Navigation
   05. Buttons
   06. Hero — the photograph, the sentence, the card rail
   06b. The card rail
   07. Why choose us (the first ordinary section)
   08. Three steps (the dark panel)
   09. What we offer (the service cards)
   10. The promo band
   11. Meet the team
   12. Our office (the gallery mosaic)
   13. What patients say (the review marquee)
   14. Common questions (the accordion)
   15. Where to find us
   16. Book an appointment (the stepped form)
   17. Footer
   19. Animation entry states
   20. Responsive
   21. Reduced motion / print
   ========================================================================== */


/* ==========================================================================
   01. DESIGN TOKENS
   ========================================================================== */
:root {
  /* --- Brand palette: premium blue-white ----------------------------------
     Secondary/tertiary inks are set against the measured backdrop, not
     against an assumption. The sequence runs #AECFF6 → #CBE4FE where the
     type falls; these clear 4.5:1 at the darkest frame with the scrim at
     half strength. Re-check them whenever the footage changes.
     ------------------------------------------------------------------------ */
  --c-ink:          #0b1e33;   /* deepest text                                */
  --c-ink-soft:     #35526f;   /* secondary text                             */
  --c-ink-faint:    #46617e;   /* tertiary text / labels                     */

  --c-blue-900:     #0a2c52;
  --c-blue-700:     #0f4f96;
  --c-blue-500:     #1a72d6;   /* primary action                             */
  --c-blue-300:     #6fb2f4;
  --c-blue-100:     #d6e8fb;
  --c-blue-50:      #eef5fd;

  --c-mint:         #7fe3d4;   /* single cool accent for the lighting        */
  --c-paper:        #abccf4;   /* holding tone until frame 001 paints        */

  /* THE ONE WARM ACCENT. It exists for exactly one phrase — the italic in the
     hero headline — and for the "evening hours" card that echoes it. A second
     warm note anywhere else on the page and this stops being the accent and
     starts being a colour scheme. */
  --c-accent:       #ff6b2c;

  /* The page's own paper, below the cinematic part. ONE number, used by both
     <body> and every ordinary section, and they must stay equal: the hero
     dissolves the whole environment out to this colour at the end of its pin,
     and the section that scrolls up next is painted in it. If the two ever
     drift apart you get a visible seam at the hand-off. */
  --c-page:         #f0f8fb;

  /* Rating stars. The one place the brand palette does not win: an amber
     star is what a rating looks like everywhere, and a blue one reads as a
     decoration rather than a score. */
  --c-star:         #f0a132;

  /* --- Glass ------------------------------------------------------------- */
  --glass-bg:       rgba(255, 255, 255, 0.52);
  --glass-bg-soft:  rgba(255, 255, 255, 0.34);
  --glass-brd:      rgba(255, 255, 255, 0.75);
  --glass-brd-cool: rgba(120, 170, 225, 0.30);
  --glass-blur:     22px;

  /* --- Shadows: layered, low-contrast, "daylight" ------------------------ */
  --sh-sm:  0 1px 2px rgba(11, 45, 82, 0.06),
            0 4px 12px rgba(11, 45, 82, 0.05);
  --sh-md:  0 2px 6px rgba(11, 45, 82, 0.06),
            0 14px 34px rgba(11, 45, 82, 0.08);
  --sh-lg:  0 6px 18px rgba(11, 45, 82, 0.07),
            0 34px 70px rgba(11, 45, 82, 0.12);
  --sh-blue: 0 10px 24px rgba(26, 114, 214, 0.28),
             0 24px 60px rgba(26, 114, 214, 0.22);

  /* --- Typography ---------------------------------------------------------
     THREE FAMILIES, AND EACH ONE HAS A JOB. The site used to be set entirely
     in Baskervville; it now pairs that serif with two grotesques, and which
     one a thing gets is decided by what the thing IS, not by where it sits:

       --f-display  BASKERVVILLE. Every heading, h1 through h6, and the two
                    places that behave like headings without being one (the
                    hero's italic phrase, a display figure). Roman 400 and
                    italic 400 — see the note on font-synthesis below.
       --f-sans     DM SANS. Running text: paragraphs, ledes, quotes, bios,
                    card copy. Anything you read a sentence of.
       --f-ui       POPPINS. The chrome: navigation, buttons, form controls,
                    and the small uppercase labels and eyebrows that behave
                    like controls even when they are not clickable.

     WHY THE SPLIT IS WORTH THE TWO EXTRA REQUESTS. Baskervville ships roman
     400 and italic 400 and nothing else — no bold, no light — so under a
     one-family system every `font-weight: 500` and `600` in this file rendered
     as 400 and emphasis had to be carried by size, case and tracking alone.
     DM Sans and Poppins both ship real weights, so those declarations mean
     something again everywhere they matter: labels, buttons and nav items.

     `font-synthesis` STAYS OFF, and now it is protecting one family rather
     than all three. Baskervville still has no bold for the browser to fake by
     smearing its outlines, and a smeared high-contrast serif looks like a
     rendering fault rather than like emphasis. The other two never need
     synthesis because their weights are real.

     The fallbacks in each stack are metric-near, so a slow font load shifts
     the layout as little as possible.
     ------------------------------------------------------------------------ */
  --f-display: "Baskervville", "Libre Baskerville", Baskerville,
               "Baskerville Old Face", "Hoefler Text", Garamond,
               "Times New Roman", serif;

  --f-sans: "DM Sans", ui-sans-serif, system-ui, -apple-system,
            "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --f-ui: "Poppins", ui-sans-serif, system-ui, -apple-system,
          "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Kept as an alias so the hero headline still says what it is at the point
     of use. It has always resolved to the display face and still does. */
  --f-hero: var(--f-display);

  font-synthesis: none;
  -webkit-font-synthesis: none;

  /* FIGURES.
     ------------------------------------------------------------------------
     Baskervville's default numerals are OLDSTYLE — the 3, 4, 5, 7 and 9 hang
     below the baseline and the 0, 1 and 2 sit at x-height. In running prose
     that is exactly right and it is why it is left as the default: numbers
     in a sentence should sit in the line, not shout over it.

     It is wrong for anything you SCAN rather than read. A column of opening
     hours, a phone number, a street address, a step counter in a circle —
     these are compared against each other, and oldstyle figures make them
     bounce and misalign. Those get --nums applied below: lining so every
     digit is cap height, tabular so every digit is the same width and the
     times line up in their column.

     Three tiers, and the default is the middle one:

       --nums-ui   lining, proportional. Set on <body>, so ANY numeral in the
                   site's chrome — a chip, a badge, a button label, a footer
                   year — is cap height without anyone having to remember a
                   class for it.
       --nums-tab  lining AND tabular. Only where digits stack into a column
                   and have to align down the page: the opening hours, the
                   statistic row, the booking step counters in their circles.
       normal      the font's own oldstyle, put BACK on running prose below.
                   A date inside a sentence should sit in the line. */
  --nums-ui:  lining-nums;
  --nums-tab: lining-nums tabular-nums;

  --fs-eyebrow: clamp(0.79rem, 0.72rem + 0.24vw, 0.91rem);
  --fs-body:    clamp(1.05rem, 0.98rem + 0.29vw, 1.17rem);
  /* TWO limits, and it needs both — hence the min().
     ------------------------------------------------------------------------
     vmin  guards the HEIGHT. A vw-only size grows on a short, wide window
           exactly where the vertical room is tightest, and the hero is a
           fixed 100svh box that the whole stack has to fit inside.

     vw    guards the WIDTH. A vmin-only size holds steady from 1920 down to
           1280 while the copy column it has to fit inside is still shrinking,
           and the headline rags into an extra line on every laptop. Sizing on
           vw below the cap makes the type track the column that constrains it.

     The ceiling is set from the longest authored line — currently "smiles,
     right here in" — against --measure. 3.6vw and the 3.6rem cap both sit
     comfortably under what that line can take, and the slack is what keeps
     the three lines from ragging if the copy is edited. Re-measure if it is:
     a fourth line breaks the .line masks, which assume one visual row each. */
  --fs-title:   clamp(1.95rem, min(0.68rem + 4.9vmin, 3.6vw), 3.6rem);

  /* --- Layout ------------------------------------------------------------ */
  --shell:     min(1440px, 100% - 2 * var(--gutter));
  --gutter:    clamp(1.25rem, 0.6rem + 3vw, 4.5rem);
  --nav-h:     clamp(72px, 6vw, 96px);
  /* Hero copy column. The headline is authored as three lines and the longest
     of them has to fit inside this at --fs-title; it is the number to raise
     first if the headline copy is ever edited longer. */
  --measure:   620px;

  /* --- The hero frame -----------------------------------------------------
     The opening section is a rounded card floating on the page rather than a
     full-bleed band, and these three numbers are the whole composition:

       --hero-inset   the page showing around the card, on all four sides.
                      The floating header and the card strip both measure
                      their own insets from this, so the three line up.
       --hero-radius  the corner. 28px is the reference's.
       --hero-pad     how far INSIDE the frame the header pill sits. Added to
                      --hero-inset it gives the pill's distance from the
                      viewport edge, which is why it is a token and not a
                      number written twice.
     ------------------------------------------------------------------------ */
  --hero-inset:  clamp(10px, 0.85vw, 16px);
  --hero-radius: 28px;
  --hero-pad:    clamp(6px, 0.7vw, 12px);

  /* --- The hero card strip -------------------------------------------------
     ONE NUMBER DRIVES THE REST. The height is the artwork's own 428:362 out
     of the width, and the overlap the strip hangs over the frame by is
     exactly half that height — so `half in, half out` is arithmetic rather
     than a margin somebody tuned by eye against one viewport. Change --hcard-w
     and the overlap, the frame's height and the section's total all follow.
     ------------------------------------------------------------------------ */
  --hcard-w:   clamp(11.5rem, 19.5vw, 18rem);
  --hcard-h:   calc(var(--hcard-w) * 362 / 428);
  --hcard-gap: clamp(.85rem, 1.8vw, 1.6rem);

  /* --- Vertical rhythm ----------------------------------------------------
     One scale for the whole hero stack, so the gaps are proportional rather
     than arbitrary. Each step is viewport-height aware: the composition
     opens up on a tall screen and tightens on a short one instead of
     overflowing it.
     ------------------------------------------------------------------------ */
  /* 8pt system. Every endpoint below is a multiple of 8, so the stack lands
     on the grid at both ends of its range — the composition still has to
     fit a 100svh box, and a fixed ladder overflows a short laptop, so the
     values interpolate BETWEEN 8pt steps rather than abandoning them. */
  --sp-eyebrow: clamp(16px, 2vh, 24px);    /* eyebrow → head  */
  --sp-head:    clamp(24px, 3vh, 40px);    /* head → lede     */
  --sp-lede:    clamp(32px, 4vh, 48px);    /* lede → buttons  */
  --sp-cta:     clamp(24px, 3.5vh, 40px);  /* buttons → stats */
  --sp-top:     clamp(24px, 4vh, 56px);    /* below the navbar*/

  /* --- Motion: slow, cinematic, never aggressive ------------------------- */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.33, 0.02, 0, 1);
  --t-fast:    .35s;
  --t-med:     .6s;
  --t-slow:    1.1s;

  /* --- Depth ------------------------------------------------------------- */
  --z-env:   0;
  --z-page:  1;
  --z-nav:   50;
}


/* ==========================================================================
   02. RESET & BASE
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  /* No `scroll-behavior: smooth` — it fights ScrollTrigger's scrub and
     produces visible stepping on the pinned chapters. */
}

body {
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.6;
  color: var(--c-ink);
  /* Lining figures everywhere in the chrome by default — chips, badges,
     button labels, the footer year. Running prose is handed back to the
     font's oldstyle further down; see the figures block. */
  font-variant-numeric: var(--nums-ui);
  /* The page's own paper. The hero paints its own photograph over the top of
     it, so this is only ever seen below the fold — but it has to match the
     hero's fallback gradient at the seam, which is why both read the same
     token. */
  background: var(--c-page);
  /* `clip`, not `hidden`: hidden would turn <body> into a scroll container
     and break ScrollTrigger's pinning maths. */
  overflow-x: clip;
  /* NO GRAYSCALE ANTIALIASING. `-webkit-font-smoothing: antialiased` was here
     and it is the first thing to go when a face this delicate looks washed
     out: it drops subpixel rendering, which THINS every stroke by roughly
     half a pixel. On a sturdy geometric sans that reads as crisp — which is
     why it suited Jost and Montserrat — but Baskervville is a high-contrast
     transitional serif whose hairlines are already at the limit of what a
     screen can hold. Taking half a pixel off them is what made the small
     text look faint rather than fine. Left to the browser's default,
     the same text renders with its full weight. */
  text-rendering: optimizeLegibility;
}

/* --- WHICH FAMILY A THING GETS ---------------------------------------------
   Three rules, stated once here rather than repeated on 40 selectors. <body>
   above already hands running text to DM Sans, so all that is left is the two
   exceptions to it.

   HEADINGS ARE MATCHED BY TAG, not by class. Every h1–h6 on the site is a
   heading of something and every one of them should be in the serif; matching
   the tag means a new section inherits the decision instead of needing a line
   added down here. The individual `font-family: var(--f-display)` declarations
   further down this file are now redundant for those elements and are left in
   place because they still document intent at the point of use.

   THE CHROME IS MATCHED BY ROLE. Navigation, buttons and form controls are
   things you operate rather than things you read, and the small uppercase
   labels are grouped with them for the same reason: at .1em+ of tracking a
   high-contrast serif comes apart, and Poppins does not.
   -------------------------------------------------------------------------- */
h1, h2, h3, h4, h5, h6 { font-family: var(--f-display); font-weight: 400; }

.nav__link,
.nav__panel a,
.nav__phone,
.nav__toggle,
.btn,
.foot__list a,
.foot__quick a,
.foot__legal a,
.foot__copy,
button,
input,
select,
textarea,
.field__label,
.pill span,
.book__legend,
.book__dot,
.card__tag,
.card__link,
.doctor__role,
.review__source,
.hcard__label,
.hcard__figure,
.why__eyebrow,
.steps-section__eyebrow,
.services__eyebrow,
.team__eyebrow,
.office__eyebrow,
.reviews__eyebrow,
.visit__eyebrow,
.book__eyebrow,
.why__badge-label,
.office__more-label,
.hours__day,
.hours__time { font-family: var(--f-ui); }

img, svg, canvas { display: block; max-width: 100%; }

a { color: inherit; text-decoration: none; }

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* Keyboard focus only — never on mouse click */
:focus-visible {
  outline: 2px solid var(--c-blue-500);
  outline-offset: 4px;
  border-radius: 4px;
}

.skip-link {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 999;
  padding: .75rem 1.25rem;
  font-size: 0.952rem;
  font-weight: 500;
  color: #fff;
  background: var(--c-blue-700);
  border-radius: 0 0 12px 12px;
  transform: translate(-50%, -120%);
  transition: transform var(--t-fast) var(--ease-out);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* Everything that is not the fixed environment sits above it */
main, .nav { position: relative; z-index: var(--z-page); }


/* --- Figures: which numerals are data and which are prose ------------------
   See --nums-ui / --nums-tab in the tokens block for why this exists at all.
   Chrome is lining by default (set on <body> in the reset), so this file only
   has to state the two exceptions.
   -------------------------------------------------------------------------- */

/* PROSE goes back to the font's oldstyle. A paragraph that happens to mention
   7pm or 1998 is being read, not scanned, and cap-height digits punch holes
   in the line. Anything with its own class below outranks this. */
p,
blockquote,
.hero__lede,
.review__quote { font-variant-numeric: normal; }

/* ...but not these paragraphs. A postal address and a copyright year are
   data that happen to live inside a <p>; they are read the way a label is,
   not the way a sentence is. */
.foot__copy,
.foot__contact,
.visit__address { font-variant-numeric: var(--nums-ui); }

/* DATA that stacks into a column and has to align down the page. */
.hours__day,
.hours__time,
.hcard__figure,
.book__dot span,
.step__num { font-variant-numeric: var(--nums-tab); }

/* ==========================================================================
   04. NAVIGATION
   ---------------------------------------------------------------------------
   THE PILL IS THE HEADER. It is not a scrolled state any more — it is what
   the bar looks like from first paint, all the way down the page, and again
   when it returns after being scrolled away. One design, one shape, one
   surface, so the header never appears to change identity on you.

   It used to be transparent at the top and only become a pill past 24px, and
   that had two costs. The header read as two different components depending
   on where you were, and while it was transparent its text was sitting
   straight on the render with nothing behind it — which is why there was a
   scrim gradient faked in behind it. Both are gone.

   THE SURFACE IS OPAQUE ON PURPOSE, and that is the answer to the sections
   underneath it. This bar crosses a blue render, a near-white page, white
   cards and one near-black panel on its way down. Glass tinted by whatever
   is behind it would need its type recoloured for each of those. An opaque
   pill does not: the type is always on the same white, so one ink colour is
   correct everywhere and nothing has to be measured per-section.

   Two classes still drive the rest:

     .is-scrolled   the pill lifts: a deeper shadow and a fully opaque
                    surface, because past the top of the page it is floating
                    over content rather than resting on the hero
     .is-hidden     the pill translated up out of frame, while the progress
                    line at the very top stays put

   Only .nav__inner carries the pill. That separation is what lets the
   progress line remain visible when the bar hides — hiding .nav itself would
   take the line with it.
   ========================================================================== */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  min-height: var(--nav-h);
  /* Padding, not margin: .nav's own box must still start at the top of the
     viewport so the progress line can pin to it. The pill is lifted off the
     top edge from the very first paint — it floats at rest exactly as it
     floats once you have scrolled.

     THE OFFSET IS THE HERO'S, not a number of its own: --hero-inset clears
     the page margin around the rounded frame and --hero-pad carries it the
     rest of the way inside. That is what makes the header read as floating
     IN the hero rather than above it, and it is why editing either token
     moves the pill with the frame instead of leaving it behind. */
  padding-top: calc(var(--hero-inset) + var(--hero-pad));
}

/* --- Reading progress ------------------------------------------------------
   scaleX from a left origin, so the browser composites it instead of laying
   it out. Written on every scroll frame — a width in % would be a layout on
   every scroll frame.
   -------------------------------------------------------------------------- */
.nav__progress {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  pointer-events: none;
}
.nav__progress-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, var(--c-blue-500), var(--c-mint));
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

/* --- The bar / the pill --------------------------------------------------- */
.nav__inner {
  /* AS WIDE AS THE FRAME IT SITS IN, less the pad on either side. Not a
     centred 1180px column any more: the reference's header spans the hero
     card almost end to end, and a narrower pill floating inside a full-bleed
     rounded frame reads as two unrelated components. */
  width: calc(100% - 5 * (var(--hero-inset) + var(--hero-pad)));
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 8px 8px 8px 1.1rem;
  background: rgba(255, 255, 255, 0.94);
  /* A COOL hairline, not a white one. White-on-white is the case that has to
     work: over the page's own near-white sections a white border is the same
     colour as what it is drawn against and the pill loses its edge entirely.
     A pale blue reads as a hairline on white and disappears into the glass
     over the hero, which is the right way round. */
  border: 1px solid rgba(186, 214, 243, 0.55);
  /* A ROUNDED RECTANGLE, not a pill. At this width a 100px radius turns the
     two ends into half-circles and the bar stops sharing a language with the
     28px frame it floats inside; 18px is the same corner, one step in. */
  border-radius: 18px;
  box-shadow: 0 2px 8px rgba(11, 45, 82, .05), 0 14px 34px rgba(11, 45, 82, .09);
  -webkit-backdrop-filter: blur(20px) saturate(165%);
  backdrop-filter: blur(20px) saturate(165%);
  transition: background var(--t-med) var(--ease-out),
              border-color var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              transform var(--t-med) var(--ease-out);
}

/* Off the top of the page the pill is no longer resting on the hero, it is
   floating over whatever section is passing underneath — including the dark
   panel. Fully opaque, and a deeper shadow to lift it off. The SHAPE does not
   change: same width, same radius, same padding, so nothing reflows and the
   header never looks like it turned into a different component. */
.nav.is-scrolled .nav__inner {
  background: #ffffff;
  border-color: rgba(168, 202, 238, 0.6);
  box-shadow: 0 2px 10px rgba(11, 45, 82, .07), 0 20px 50px rgba(11, 45, 82, .14);
}

/* Out of frame, but only the pill. The threshold and direction are decided in
   main.js; this is only the destination. */
.nav.is-hidden .nav__inner { transform: translateY(calc(-100% - 24px)); }

/* The top scrim that used to sit behind this is GONE. It existed to give the
   type something to read against while the bar was transparent, and the bar
   is never transparent now — the pill is its own readability layer. Leaving
   it in would put a pale band across the top of the render for no reason. */

/* --- Logo ----------------------------------------------------------------- */
.logo {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  flex: none;
  color: var(--c-blue-900);
}
.logo__mark {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  color: var(--c-blue-500);
  background: var(--glass-bg);
  border: 1px solid var(--glass-brd);
  border-radius: 13px;
  box-shadow: var(--sh-sm);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: transform var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out);
}
.logo__mark svg { width: 26px; height: 26px; }
.logo:hover .logo__mark { transform: translateY(-2px); box-shadow: var(--sh-md); }

/* The supplied artwork carries the wordmark, so it replaces the mark AND the
   type beside it. Sized by HEIGHT with width:auto — the file is 174x60 and
   the pill's height is what has to be respected, not the file's width. */
.logo__img {
  display: block;
  /* The pill's height is 2 × 8px of padding plus this, and the reference's
     header measures about 54px — so this is the number that sets it. */
  height: clamp(30px, 2.7vw, 38px);
  width: auto;
  transition: transform var(--t-med) var(--ease-out);
}
.logo:hover .logo__img { transform: translateY(-1px); }

.logo__type { display: flex; flex-direction: column; line-height: 1.05; }
.logo__name {
  font-family: var(--f-display);
  font-size: 1.2rem;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.logo__sub {
  font-size: 0.744rem;
  font-weight: 500;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--c-ink-faint);
}

/* --- Menu ----------------------------------------------------------------- */
.nav__menu {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.25rem);
}

.nav__list {
  display: flex;
  align-items: center;
  gap: clamp(.9rem, 1.8vw, 1.9rem);
  list-style: none;
}

.nav__item { position: relative; }

.nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .35rem 0;
  font-family: var(--f-ui);
  font-size: 0.98rem;
  font-weight: 500;
  white-space: nowrap;
  color: var(--c-ink-soft);
  transition: color var(--t-fast) var(--ease-out);
}
/* Hairline that draws in from the left */
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1.5px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-blue-500), var(--c-mint));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-med) var(--ease-out);
}
.nav__link:hover { color: var(--c-blue-700); }
.nav__link:hover::after,
.nav__item--menu:focus-within .nav__trigger::after { transform: scaleX(1); }

.nav__trigger { cursor: pointer; }
.nav__caret {
  width: 15px; height: 15px;
  transition: transform var(--t-med) var(--ease-out);
}
.nav__trigger[aria-expanded="true"] .nav__caret { transform: rotate(180deg); }

/* --- The dropdown ----------------------------------------------------------
   visibility, not display: a display change cannot transition, and without a
   transition the panel appears and vanishes in one frame while the caret
   under it is still turning.
   -------------------------------------------------------------------------- */
.nav__panel {
  position: absolute;
  top: calc(100% + 14px);
  left: 50%;
  min-width: 15rem;
  padding: .5rem;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid var(--glass-brd);
  border-radius: 16px;
  box-shadow: var(--sh-lg);
  -webkit-backdrop-filter: blur(22px) saturate(170%);
  backdrop-filter: blur(22px) saturate(170%);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, -8px);
  transition: opacity var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out),
              visibility var(--t-fast);
}
/* A gap between the trigger and the panel would drop the hover the moment
   the pointer crossed it. This bridges it without being visible. */
.nav__panel::before {
  content: "";
  position: absolute;
  inset: -14px 0 auto 0;
  height: 14px;
}
.nav__item--menu:hover .nav__panel,
.nav__item--menu:focus-within .nav__panel,
.nav__trigger[aria-expanded="true"] + .nav__panel {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.nav__panel ul { list-style: none; }
.nav__panel a {
  display: block;
  padding: .55rem .8rem;
  font-size: 0.941rem;
  font-weight: 500;
  color: var(--c-ink-soft);
  border-radius: 10px;
  transition: background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out);
}
.nav__panel a:hover {
  color: var(--c-blue-700);
  background: rgba(26, 114, 214, .08);
}

/* --- Actions -------------------------------------------------------------- */
.nav__actions {
  display: flex;
  align-items: center;
  gap: clamp(.75rem, 1.4vw, 1.25rem);
}

.nav__phone {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: 0.952rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--c-blue-900);
  transition: color var(--t-fast) var(--ease-out);
}
.nav__phone:hover { color: var(--c-blue-500); }
.nav__phone-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 15px; height: 15px;
  color: var(--c-blue-500);
}
.nav__phone-icon svg { width: 15px; height: 15px; }

.nav__cta { font-size: 0.888rem; letter-spacing: .06em; text-transform: uppercase; }

/* --- Mobile toggle -------------------------------------------------------- */
.nav__toggle {
  display: none;
  width: 44px; height: 44px;
  place-items: center;
  gap: 6px;
  border-radius: 12px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-brd);
  box-shadow: var(--sh-sm);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.nav__toggle-bar {
  width: 18px; height: 1.5px;
  background: var(--c-blue-900);
  border-radius: 2px;
  transition: transform var(--t-med) var(--ease-out),
              opacity var(--t-fast) var(--ease-out);
}
.nav.is-open .nav__toggle-bar:first-child { transform: translateY(3.75px) rotate(45deg); }
.nav.is-open .nav__toggle-bar:last-child  { transform: translateY(-3.75px) rotate(-45deg); }


/* ==========================================================================
   05. BUTTONS
   ========================================================================== */
/* Height is set by min-height, never by padding, so a button's contents
   (icon, play circle, plain label) can differ without changing its box. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  min-height: 48px;      /* 8pt, and the brief asks for restraint here */
  padding-inline: 32px;
  font-size: 0.991rem;
  font-weight: 500;
  line-height: 1;
  letter-spacing: .005em;
  white-space: nowrap;
  border-radius: 100px;
  overflow: hidden;
  transition: transform var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              background var(--t-med) var(--ease-out),
              border-color var(--t-med) var(--ease-out),
              color var(--t-med) var(--ease-out);
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0); transition-duration: .12s; }

.btn--sm { min-height: 42px; padding-inline: 1.3rem; font-size: 0.896rem; }

/* Primary: deep blue with a slow travelling sheen */
.btn--primary {
  color: #fff;
  background: linear-gradient(135deg, var(--c-blue-500) 0%, var(--c-blue-700) 55%, var(--c-blue-900) 100%);
  box-shadow: var(--sh-blue);
}
.btn--primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg,
              rgba(255,255,255,0) 38%,
              rgba(255,255,255,.34) 50%,
              rgba(255,255,255,0) 62%);
  transform: translateX(-130%);
  transition: transform 1.1s var(--ease-soft);
}
.btn--primary:hover::before { transform: translateX(130%); }
.btn--primary:hover { box-shadow: 0 14px 30px rgba(26,114,214,.34), 0 30px 72px rgba(26,114,214,.26); }

.btn__icon { width: 18px; height: 18px; transition: transform var(--t-med) var(--ease-out); }
.btn--primary:hover .btn__icon { transform: translateX(4px); }

/* --- The hero's call to action, breathing ---------------------------------
   MICRO ANIMATION. One slow swell on the shadow, five and a half seconds a
   cycle, and only on the button in the hero — the same treatment on every
   primary button on the page would be a tic rather than an accent.

   It is a box-shadow animation and that is deliberate despite the usual rule
   against them: .btn carries `overflow: hidden` so a glowing pseudo-element
   would be clipped at the pill's edge, and the repaint is a 48px-tall element
   in a composited layer at a fifth of a hertz. The cost is not measurable; the
   alternative is restructuring a button that works.

   Killed outright on hover rather than paused. A running animation outranks a
   normal declaration in the cascade, so :hover's own shadow would never be
   seen while this was still ticking over. */
@keyframes cta-breathe {
  0%, 100% {
    box-shadow: 0 10px 24px rgba(26, 114, 214, .28),
                0 24px 60px rgba(26, 114, 214, .22),
                0 0 0 0 rgba(110, 178, 244, 0);
  }
  50% {
    box-shadow: 0 12px 28px rgba(26, 114, 214, .34),
                0 26px 66px rgba(26, 114, 214, .26),
                0 0 26px 3px rgba(110, 178, 244, .26);
  }
}
.hero__actions .btn--primary { animation: cta-breathe 5.5s ease-in-out infinite; }
.hero__actions .btn--primary:hover,
.hero__actions .btn--primary:focus-visible { animation: none; }

/* Glass: the secondary, quieter action */
.btn--glass {
  color: var(--c-blue-900);
  background: var(--glass-bg);
  border: 1px solid var(--glass-brd);
  box-shadow: var(--sh-sm), inset 0 1px 0 rgba(255,255,255,.9);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(160%);
  backdrop-filter: blur(var(--glass-blur)) saturate(160%);
}
.btn--glass:hover {
  background: rgba(255,255,255,.76);
  border-color: #fff;
  box-shadow: var(--sh-md);
}

/* Pulled into the left padding so the play circle doesn't make this button
   read as wider-set than the primary next to it. */
.btn__play {
  display: grid;
  place-items: center;
  flex: none;
  width: 24px; height: 24px;
  margin-left: -.45rem;
  color: var(--c-blue-500);
  background: rgba(26, 114, 214, .1);
  border-radius: 50%;
  transition: background var(--t-med) var(--ease-out);
}
.btn__play svg { width: 8px; height: 9px; margin-left: 1px; }
.btn--glass:hover .btn__play { background: rgba(26, 114, 214, .17); }

/* Ghost: nav-weight action */
.btn--ghost {
  color: var(--c-blue-900);
  background: var(--glass-bg-soft);
  border: 1px solid var(--glass-brd-cool);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
.btn--ghost:hover { background: rgba(255,255,255,.66); box-shadow: var(--sh-sm); }


/* ==========================================================================
   06. HERO — the opening frame
   ---------------------------------------------------------------------------
   One photograph in a rounded card, one sentence, one action, and a strip of
   proof cards hanging half out of the card's bottom edge. Nothing is pinned:
   the section is an ordinary 100svh block and every scroll move on it is a
   scrub between `top top` and `bottom top`, so the page never takes the
   scrollbar away from you.

   THE SECTION IS TWO BOXES, and which one a thing belongs to is decided by a
   single question — does it have to be cropped to the radius, or does it have
   to escape it?

     .hero__frame   CLIPS. The photograph, its atmosphere and the copy. 28px
                    radius, --hero-inset of page showing around it, and the
                    floating header resting just inside its top edge.
     .hero__rail    ESCAPES. The card strip, pulled up by half a card so it
                    straddles the frame's floor. A sibling, because a child
                    could not hang out of a box that clips.

   The two are held apart by arithmetic rather than by eye: the frame's
   min-height is the viewport less the page inset and less the half card the
   strip steals back, so frame + overhang + inset comes to exactly 100svh at
   every width. Change --hcard-w and the whole thing re-solves itself.

   THE THREE LAYERS INSIDE THE FRAME, back to front:

     .hero__stage   the photograph and everything laid over it — the wash that
                    holds the type side of the frame, a warm grade, a vignette
                    and grain. Absolutely placed and clipped by the frame, so
                    the media inside it is free to be larger than the frame and
                    to travel without ever showing an edge.
     .hero__inner   the copy column. Left-set, capped at --measure.

   WHY THE MEDIA BOX IS INSET NEGATIVE. hero.js scales it and drifts it down
   across the section, and the pointer parallax drifts it sideways. Each of
   those uncovers an edge unless the box already extends past the frame it is
   clipped by, and the insets below are sized to the largest travel the two can
   ask for together. Raise the scrub and you must raise these with it.
   ========================================================================== */
.hero {
  position: relative;
  /* One backdrop root for the section. It keeps the stage's z-index local AND
     it is what the strip's glass reads its blur from: the cards sample
     everything painted beneath them inside this box, which is the photograph
     on their top half and the page on their bottom half. */
  isolation: isolate;
  /* The page showing around the rounded card, on all four sides. NOT
     `overflow: hidden` — the strip below has to hang out of this. */
  padding: var(--hero-inset);
  background: var(--c-page);
}

/* --- The rounded card ------------------------------------------------------
   min-height, not height: the copy stack is allowed to push it taller on a
   short window rather than being squeezed out of the bottom of it. The half
   card the strip steals is subtracted here so the section as a whole still
   measures 100svh — see the arithmetic in the block comment above.
   -------------------------------------------------------------------------- */
.hero__frame {
  position: relative;
  isolation: isolate;               /* the stage's z-index stays local        */
  display: flex;
  align-items: center;
  min-height: calc(100svh - 2 * var(--hero-inset) - var(--hcard-h) / 2);
  /* --util-h is the utility strip above the navigation pill; it is declared
     in system.css and is 0 on any page that does not render one. It is added
     here rather than folded into --nav-h because --nav-h is read in a dozen
     other places that must NOT move when the strip appears. */
  padding-top: calc(var(--util-h, 0px) + var(--nav-h) + clamp(0.5rem, 3vh, 2rem));
  /* Room for the strip's top half, so it lands on the photograph and never on
     the button above it. */
  padding-bottom: calc(var(--hcard-h) / 2 + clamp(1rem, 2.5vh, 2rem));
  border-radius: var(--hero-radius);
  overflow: hidden;
  background: var(--c-page);
}

/* --- The photograph and its atmosphere ------------------------------------ */
.hero__stage {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

/* THE INSETS ARE SIZED TO THE SCRUB, not eyeballed — and they are as small as
   that sizing allows, because every percent here is a percent of ZOOM the
   photograph is carrying before anybody has scrolled. hero.js now takes this
   box to scale 1.06 and yPercent 3: the growth moves each edge out by 3% and
   the drift moves the whole box down by 3%, so the top edge ends where it
   started and the bottom ends 6% low. 2% of cover on every side absorbs that
   and the ±5px of pointer parallax with it. Change either number in hero.js
   and these have to move with it, or the scroll uncovers an edge. */
.hero__media {
  position: absolute;
  inset: -2%;
  will-change: transform;
  /* The scrub writes yPercent and scale; the pointer parallax in main.js
     writes x and y on the same element. Separate GSAP channels, one matrix —
     they compose rather than fight. Do not add a second writer to either. */
  transform-origin: 62% 45%;
}

.hero__photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* WHERE THE CROP SITS, and it is the one value in this section tuned to the
     file rather than to the layout. The photograph is 16:9 and the frame is
     about 1.87:1, so on a desktop `cover` takes a sliver off the height and
     nothing at all off the width — this value does almost nothing there, which
     is the point of a photograph framed for its frame.

     IT EARNS ITS KEEP ON A NARROW ONE. A tablet in portrait gives the frame a
     ratio under 1, and `cover` then keeps less than half the width — centred,
     that half is the patient and the doctor's coat with his head cut off the
     right edge. Biased to 62% the crop holds both faces and gives up the empty
     left end of the room, which is the half you can afford to lose: the wash
     is over it and the headline is on top of that. */
  object-position: 62% 50%;
  /* The placeholder is being enlarged well past its own pixel size, which
     flattens it. A point of contrast and saturation puts the snap back
     without looking processed. Harmless on a full-size photograph. */
  filter: contrast(1.05) saturate(1.06);
}

/* Until the photograph decodes — and if it never arrives at all, because
   main.js drops a broken [data-photo] out of the DOM — this is what the type
   is read against. A finished gradient, not a grey box. */
.hero__media {
  background:
    radial-gradient(85% 70% at 78% 34%, rgba(174, 207, 246, .85) 0%, rgba(174, 207, 246, 0) 62%),
    linear-gradient(120deg, #f4fafd 0%, #dceaf8 46%, #bcd8f2 100%);
}

/* THE WASH. What makes a photograph legible under 54px of serif without
   turning it into a flat panel: opaque where the first word starts, gone by
   the time it reaches the subject. Two gradients, not one — the horizontal
   holds the copy column and the vertical hands the bottom of the frame over
   to the card rail. */
.hero__wash {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(101deg,
      rgba(255, 255, 255, .97) 0%,
      rgba(255, 255, 255, .93) 24%,
      rgba(255, 255, 255, .70) 40%,
      rgba(255, 255, 255, .26) 55%,
      rgba(255, 255, 255, 0)   72%),
    linear-gradient(to top,
      rgba(240, 248, 251, .92) 0%,
      rgba(240, 248, 251, .34) 16%,
      rgba(240, 248, 251, 0)   34%),
    /* A little light under the navbar so the white pill never lands on the
       darkest part of the photograph. */
    linear-gradient(to bottom,
      rgba(255, 255, 255, .58) 0%,
      rgba(255, 255, 255, 0) 18%);
  will-change: opacity;
}

/* Warm colour grade. Screen-blended so it adds light rather than laying a
   translucent sheet over the render. */
.hero__grade {
  position: absolute;
  inset: 0;
  background: radial-gradient(70% 60% at 84% 26%,
              rgba(255, 214, 170, .34) 0%,
              rgba(255, 214, 170, 0) 68%);
  mix-blend-mode: screen;
}

.hero__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(122% 92% at 56% 42%,
              rgba(255, 255, 255, 0) 54%,
              rgba(52, 88, 124, .10) 84%,
              rgba(38, 70, 102, .20) 100%);
}

.hero__grain {
  position: absolute;
  inset: -50%;
  opacity: .035;
  mix-blend-mode: multiply;
  background-image: 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='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}


/* --- The copy column ------------------------------------------------------
   PADDING, NOT A CENTRED SHELL. The copy is measured from the frame's own left
   edge now, so the sentence keeps the same distance from the rounded corner at
   every width — a centred --shell would walk it toward the middle of a 2560px
   monitor and lose the reference's left-set composition. --measure still caps
   the line length, so the column never sprawls.
   -------------------------------------------------------------------------- */
.hero__inner {
  min-width: 0;
  width: 100%;
  display: flex;
  align-items: center;
  padding-inline: clamp(1.25rem, 3.4vw, 4rem);
  padding-block: clamp(1rem, 4vh, 3.5rem);
}

.hero__copy {
  position: relative;
  max-width: var(--measure);
  will-change: transform, opacity;
}

/* THE HEADLINE. Three authored lines, each one a mask its words rise through.
   The masks are why the line breaks are written into the markup rather than
   left to the browser: a line that wraps is a line with two visual rows inside
   one overflow:hidden box, and the second row is simply never seen. */
.hero__title {
  position: relative;
  font-family: var(--f-display);
  font-size: var(--fs-title);
  font-weight: 400;
  line-height: 1.07;
  letter-spacing: -0.014em;
  color: var(--c-ink);
}

.hero__title .line {
  display: block;
  overflow: hidden;
  /* Room for the descenders and for the 2.5° the words carry while they are
     still under the mask. Without it the tails of "g" and "y" are shaved. */
  padding-bottom: 0.1em;
  margin-bottom: -0.06em;
}
/* THE MASK'S CONTENT is what the intro rises, so this is the element that
   carries the promotion — and only while it is moving, which is why the
   timeline clears the filter it animates rather than leaving a blur(0px)
   behind. */
.hero__title .line__inner { display: block; }

/* The words are no longer animated individually — the headline arrives a line
   at a time now. They stay marked up because a future reveal may want them and
   they cost nothing, but `will-change` came off: a promotion on twelve spans
   that never move is twelve render layers re-rasterised on every scroll frame
   for no motion at all. */
.hero__title .word { display: inline-block; }

/* THE ONE ITALIC PHRASE, and the site's only warm accent above the fold. It is
   a colour and a slope and nothing else — no rule, no highlight — because the
   sentence already has a blue button under it and two accents competing for
   the same glance is one too many. */
.hero__title em {
  font-style: italic;
  color: var(--c-accent);
}

/* THE SHEEN IS GONE. A pass of light used to cross the finished headline at
   1.55s, and it was the one move in the opening that drew attention to itself
   rather than to the sentence — a highlight travelling over type reads as a
   loading shimmer, which is the opposite of what a headline that has just
   finished arriving should say. The markup span went with it; nothing else
   referenced either. */

.hero__lede {
  max-width: 42ch;
  margin-top: var(--sp-head);
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: 1.62;
  color: var(--c-ink-soft);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(.75rem, 1.4vw, 1.1rem);
  margin-top: var(--sp-lede);
}


/* ==========================================================================
   06b. THE CARD STRIP — glass, straddling the frame's floor, looping forever
   ---------------------------------------------------------------------------
   THE OVERLAP IS ARITHMETIC. The strip is one card tall and it is pulled up by
   exactly half of that, so half of every card is over the photograph and half
   is over the page. Both halves come from --hcard-h, which comes from
   --hcard-w, so the composition holds at every width instead of being a margin
   tuned against one screenshot.

   THE LOOP. The row is not a scroller and it is not a scrub any more: hero.js
   clones the authored set until the track is wider than the strip, then slides
   it left by exactly one set's pitch and repeats. Because the pitch is
   measured between the first original and the first clone, the frame it
   restarts on is pixel-identical to the frame it left — there is no seam to
   see. It pauses under the pointer and while anything inside it has focus.

   WHY THE CLIP IS ONE-AXIS. `overflow-x: clip` cuts the row off at the strip's
   two ends, which is the point. `overflow-y: visible` lets the cards' float,
   their hover lift and their shadows out of the top and bottom, which a plain
   `overflow: hidden` would shave flat. The pair is legal and deliberate: `clip`
   on one axis does not force the other to become a scrollport, which `hidden`
   would.
   ========================================================================== */
.hero__rail {
  position: relative;
  /* Over the frame it hangs into. The frame clips its own contents, so this
     only has to win against them, not escape them. */
  z-index: 2;
  min-width: 0;
  /* Inset to the same line as the floating header, so the strip's ends and the
     pill's ends sit on one vertical. */
  width: calc(100% - 2 * var(--hero-pad));
  margin-inline: auto;
  /* HALF A CARD UP. Layout-affecting, so the section below still starts where
     the strip actually ends — a transform would overlap the frame and leave a
     card-shaped hole in the flow underneath. */
  margin-top: calc(var(--hcard-h) / -2);
  overflow-x: clip;
  overflow-y: visible;
}

.hero__track {
  display: flex;
  align-items: stretch;
  gap: var(--hcard-gap);
  list-style: none;
  /* hero.js writes `x` here and nothing else does. */
  will-change: transform;
}

/* WIDTH AND HEIGHT, BOTH STATED. The height used to come from `aspect-ratio`,
   which is a preferred size rather than a fixed one — content taller than the
   ratio grows the box, and a strip whose height wanders is a strip whose
   overlap wanders with it. The overlap above is half of --hcard-h, so
   --hcard-h has to be what the card actually measures.

   `flex: 0 0 auto`, so the row never shrinks a card to fit: the row is
   supposed to run past both ends of the strip. */
.hcard {
  position: relative;
  flex: 0 0 auto;
  width: var(--hcard-w);
  height: var(--hcard-h);
  border-radius: 18px;
  overflow: hidden;
  /* TWO CHANNELS, AND THEY MUST NOT BE THE SAME ONE. The float below animates
     `transform`; the hover lift writes `translate`. A CSS animation outranks
     any ordinary declaration, so a hover lift written as a transform would be
     silently ignored for the whole life of the page — whereas `translate` is a
     separate property that composes with the animation instead of losing to
     it. */
  transition: translate var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              background-color var(--t-med) var(--ease-out);
}

/* --- The float --------------------------------------------------------------
   Seven pixels over seven seconds. It is meant to be felt rather than seen: a
   row of glass panels that are all perfectly still reads as printed, and one
   that moves any further than this reads as a carousel with a fault.

   The delays are dealt out by position so no two neighbours are ever in phase
   — with one shared delay the whole strip would rise and fall as a single
   plank. `6n` and not `odd`, because the authored set is six and the clones
   continue the count: this way the pattern repeats with the set rather than
   drifting against it.
   -------------------------------------------------------------------------- */
@keyframes hcard-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -7px, 0); }
}

.hcard { animation: hcard-float 7s var(--ease-soft) infinite; }
.hcard:nth-child(6n + 1) { animation-delay: -0.0s; }
.hcard:nth-child(6n + 2) { animation-delay: -1.1s; }
.hcard:nth-child(6n + 3) { animation-delay: -2.3s; }
.hcard:nth-child(6n + 4) { animation-delay: -3.5s; }
.hcard:nth-child(6n + 5) { animation-delay: -4.6s; }
.hcard:nth-child(6n + 6) { animation-delay: -5.8s; }

/* The lift. The float is paused under the pointer so the card holds still at
   the top of the lift rather than continuing to bob while it is being read. */
.hcard:hover {
  translate: 0 -10px;
  animation-play-state: paused;
}

/* --- The written cards: glass, not white ------------------------------------
   0.45 alpha is a real translucency rather than a tint — enough of the
   photograph reads through the top half of the card to tell you it is glass,
   and the blur behind it is what keeps the copy legible over a photograph that
   has detail in it. Two borders do the edge: a cool hairline all the way round
   and a brighter inset line along the top, which is the highlight a pane of
   glass catches and the single cheapest thing that stops this looking like a
   flat translucent rectangle.
   -------------------------------------------------------------------------- */
.hcard--stat {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(.85rem, 1.5vw, 1.35rem);
  background: rgba(255, 255, 255, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.55);
  box-shadow: 0 2px 6px rgba(11, 45, 82, .06),
              0 18px 40px rgba(11, 45, 82, .10),
              inset 0 1px 0 rgba(255, 255, 255, .70);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  backdrop-filter: blur(20px) saturate(170%);
}
.hcard--stat:hover {
  background: rgba(255, 255, 255, 0.56);
  box-shadow: 0 6px 18px rgba(11, 45, 82, .08),
              0 34px 70px rgba(11, 45, 82, .14),
              inset 0 1px 0 rgba(255, 255, 255, .80);
}

/* The figure. Poppins Light at display size — this is the one number on the
   page you are meant to read as a headline rather than as data, and the thin
   geometric cut is what the reference design sets it in. Lining, tabular
   figures so the counter does not shuffle its own width as it counts. */
/* NO `gap` HERE, and that is the whole reason this is three explicit spans
   rather than one string. A flex gap would apply between every part equally,
   and the two figures want different spacing: "140+" is one word and must
   close up, "8 pm" is a number and a unit and must not. Margins are stated on
   the parts that need them and nowhere else. */
/* SIZED OFF THE CARD, not off the viewport. The figure is the largest thing
   inside a box whose width is already a clamp, so measuring it in vw meant two
   independent curves and a figure that crowded its own card at some widths and
   rattled around in it at others. A fraction of --hcard-w keeps the reference's
   proportion — the number filling most of the card's top third — at every size
   the card can take, phone included. */
.hcard__figure {
  display: flex;
  align-items: baseline;
  font-family: var(--f-ui);
  font-weight: 300;
  font-size: calc(var(--hcard-w) * 0.26);
  font-variant-numeric: var(--nums-tab);
  line-height: 1;
  letter-spacing: -.035em;
  color: #0a0f14;
}
/* "Uptill*" — a qualifier, not part of the figure, so it is set at label size
   and sits on the figure's own baseline rather than above it. */
.hcard__qualifier {
  margin-right: .34em;
  font-size: .26em;
  font-weight: 400;
  letter-spacing: 0;
  white-space: nowrap;
}
.hcard__unit--word { margin-left: .14em; }

/* The icon card carries a mark where the others carry a number, and it is
   sized to the same optical weight so the row does not develop a hole. */
.hcard__mark {
  display: block;
  width: calc(var(--hcard-w) * 0.29);
  height: auto;
  margin-top: .25rem;
  color: #0a0f14;
}

.hcard__label {
  margin-top: auto;
  font-family: var(--f-ui);
  font-size: clamp(.68rem, .78vw, .78rem);
  font-weight: 600;
  letter-spacing: .045em;
  text-transform: uppercase;
  color: var(--hcard-accent, var(--c-blue-500));
}

.hcard__text {
  margin-top: .42em;
  font-family: var(--f-sans);
  font-size: clamp(.78rem, .95vw, .95rem);
  font-weight: 400;
  line-height: 1.36;
  color: #123244;
}

/* Each card names its own accent. The label reads from it and nothing else
   does, so a new card is one custom property and no new rule. */
.hcard--reviews { --hcard-accent: #74b71b; }
.hcard--hours   { --hcard-accent: #ff6b00; }
.hcard--parking { --hcard-accent: #00b2e3; }

/* --- The photographic cards ------------------------------------------------
   These ship with their caption set INTO the artwork, so there is no text node
   over them to style — the <img> is the whole card. The alt text carries what
   the caption says, which is the only reason that is acceptable.
   -------------------------------------------------------------------------- */
.hcard--photo {
  background: #dce9f5;
  /* The same two-layer shadow the glass cards carry, so the row sits on one
     plane. Only the fill differs between the two kinds of card. */
  box-shadow: 0 2px 6px rgba(11, 45, 82, .06),
              0 18px 40px rgba(11, 45, 82, .10);
}
.hcard--photo:hover {
  box-shadow: 0 6px 18px rgba(11, 45, 82, .08),
              0 34px 70px rgba(11, 45, 82, .14);
}

.hcard__photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--ease-out);
}
.hcard--photo:hover .hcard__photo { transform: scale(1.06); }

/* A hairline over the photograph, inside the radius, so the photo cards have
   the same drawn edge as the written ones without a border cutting into the
   image. */
.hcard--photo::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid rgba(255, 255, 255, .34);
  pointer-events: none;
}


/* --- Touch ------------------------------------------------------------------
   THE STRIP KEEPS MOVING HERE, and that is the change: it used to become a
   native scroller on a coarse pointer, because a row that overflowed and never
   resolved was a row you had to be able to push. The loop resolves it by
   itself now — every card comes past on its own — so a scroller would only add
   a gesture that competes with the page's own vertical one and leaves the strip
   parked wherever a thumb let go of it. The composition is the same on a phone
   as on a desktop; only the card size changes.

   What DOES have to go is hover: `:hover` on a touch screen sticks after a tap,
   so the lift and the paused float would latch on whichever card was last
   touched.
   -------------------------------------------------------------------------- */
@media (hover: none), (pointer: coarse) {
  .hcard:hover {
    translate: none;
    animation-play-state: running;
  }
  .hcard--photo:hover .hcard__photo { transform: none; }
}


/* ==========================================================================
   07. WHY CHOOSE US
   The first ordinary section on the page. Everything above it is pinned and
   painted on a canvas; this is where the site becomes a normal document.

   Its background is FLAT AND OPAQUE on purpose, and it is the same single
   token <body> uses. Two separate things depend on that:

     • the environment is position:fixed and never stops rendering on its
       own, so anything translucent here leaves the tooth drifting behind the
       body copy — which is exactly what was happening;
     • the hero dissolves the environment out to --c-page at the end of its
       pin, so this section arrives on a field that is already its own
       colour and there is no seam at the join.

   No gradient, no pattern. The section below the film is meant to read as
   ordinary paper.
   ========================================================================== */
.why {
  position: relative;
  z-index: 1;
  overflow: hidden;              /* the aura is oversized and must not scroll */
  padding-block: clamp(4.5rem, 11vh, 8.5rem);
  background: var(--c-page);
}

/* Soft key light behind the media — a light source, not a background
   treatment: it is a single wide bloom well under a tenth opacity, which
   keeps the field flat while stopping it going dead. Delete this element from
   the markup if you want the section absolutely bare.
   Pointer parallax picks it up via its data-depth attribute — no extra JS. */
.why__aura {
  position: absolute;
  top: 4%;
  right: -12%;
  width: clamp(380px, 46vw, 820px);
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(80px);
  background: radial-gradient(circle at 50% 50%,
              rgba(120, 186, 246, 0.18) 0%,
              rgba(127, 227, 212, 0.10) 46%,
              rgba(127, 227, 212, 0) 72%);
}

.why__inner {
  position: relative;
  z-index: 1;
  width: var(--shell);
  margin-inline: auto;
  display: grid;
  /* The media column is given the larger share: the photo is the thing that
     has to hold the right of the frame, and a 1:1 split leaves the copy
     running to an uncomfortable measure at this width. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
  align-items: center;
  gap: clamp(2.5rem, 5vw, 5.5rem);
}

/* --- Copy ----------------------------------------------------------------- */
.why__copy { max-width: 34rem; }

.why__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
/* A drawn rule rather than the hero's dot — the same idea, stated differently
   so the two eyebrows are relatives and not duplicates. */
.why__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-mint), var(--c-blue-500));
  transform-origin: 0 50%;
}

.why__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.85rem, 0.6rem + 2vw + 0.85vh, 3.1rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
/* The accent word, and the rule under it, exactly as the hero does it. The
   shared .word__rule rule needs a positioned inline-block to hang off. */
.why__title em {
  position: relative;
  display: inline-block;
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

.why__lede {
  max-width: 46ch;
  margin-top: clamp(1rem, 2vh, 1.6rem);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.78;
  color: var(--c-ink-soft);
}

.why__actions { margin-top: clamp(1.75rem, 3.5vh, 2.6rem); }

/* --- Media ----------------------------------------------------------------
   Three layers: an offset hairline frame behind, the photo itself, and a
   glass badge overlapping the corner. The offset is what stops a single
   rounded rectangle reading as a stock content block.
   -------------------------------------------------------------------------- */
.why__media {
  position: relative;
  isolation: isolate;   /* keeps the sheen's blend inside the figure */
}

.why__frame-echo {
  position: absolute;
  inset: 7% -4% -7% 4%;
  border: 1px solid rgba(120, 170, 225, 0.34);
  border-radius: 26px;
  pointer-events: none;
}

.why__frame {
  position: relative;
  overflow: hidden;
  border-radius: 24px;
  aspect-ratio: 16 / 11;
  /* Stands in for the photo until the file is dropped in, and sits under it
     afterwards — so a slow load resolves out of a designed panel rather than
     out of nothing. */
  background:
    radial-gradient(90% 80% at 30% 20%, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(150deg, #cfe6fb 0%, #b9d9f7 46%, #cdeae6 100%);
  box-shadow: var(--sh-lg);
}

/* Absolutely placed rather than width/height:100%. A percentage height inside
   an aspect-ratio box does resolve, but only because the box has a definite
   height — and it stops resolving the moment anyone gives .why__frame a
   min-height or drops the ratio. inset:0 does not care. */
.why__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 40%;   /* faces sit above centre in a group shot */
}

/* A single fixed highlight across the glass, not an animation — it gives the
   frame a surface without asking the compositor for anything per frame. */
.why__frame-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(112deg,
              rgba(255, 255, 255, 0.22) 0%,
              rgba(255, 255, 255, 0) 34%,
              rgba(255, 255, 255, 0) 100%);
}

.why__badge {
  position: absolute;
  left: clamp(-1.5rem, -2vw, -0.75rem);
  bottom: clamp(1.25rem, 4vh, 2.5rem);
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .85rem 1.2rem .85rem 1.05rem;
  background: rgba(255, 255, 255, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-radius: 18px;
  box-shadow: var(--sh-md), inset 0 1px 0 rgba(255, 255, 255, 0.95);
  -webkit-backdrop-filter: blur(20px) saturate(165%);
  backdrop-filter: blur(20px) saturate(165%);
}
.why__badge-value {
  font-family: var(--f-display);
  font-size: 2.1rem;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--c-blue-700);
}
.why__badge-label {
  font-size: 0.744rem;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--c-ink-faint);
}


/* ==========================================================================
   08. THREE STEPS — the dark panel
   The one place on the site where the footage is background rather than
   subject. Everything here is set against a heavy scrim, not against the
   video, so the copy's contrast does not depend on which frame is up.
   ========================================================================== */
.steps-section {
  position: relative;
  isolation: isolate;            /* keeps the video and scrim behind the copy */
  overflow: hidden;
  padding-block: clamp(4.5rem, 12vh, 9rem);
  /* The floor under the video. Also what you see if the file never loads, if
     decoding is refused, or under reduced motion — in every one of those
     cases the section is a flat dark panel and nothing about it looks broken. */
  background: #16232e;
  color: #fff;
}

/* A hairline of light along the top edge. A dark panel butted against a light
   one wants a stated seam — without it the join reads as a rendering
   accident rather than as a deliberate change of ground. */
.steps-section::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  z-index: 2;
  background: linear-gradient(90deg,
              rgba(127, 227, 212, 0) 0%,
              rgba(127, 227, 212, 0.5) 35%,
              rgba(255, 255, 255, 0.35) 65%,
              rgba(255, 255, 255, 0) 100%);
}

.steps-section__video {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  opacity: 0;                    /* faded up by JS as the section arrives */
  pointer-events: none;
}

/* The reason the copy is readable. Deliberately heavy and close to flat: a
   light scrim would leave the text riding whatever the footage is doing, and
   the footage is bright. Warmed slightly to the right so the panel is not a
   dead grey rectangle. */
.steps-section__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(110% 80% at 82% 18%, rgba(26, 114, 214, 0.28) 0%, rgba(26, 114, 214, 0) 62%),
    linear-gradient(94deg,
      rgba(12, 27, 38, 0.94) 0%,
      rgba(12, 27, 38, 0.90) 42%,
      rgba(14, 32, 44, 0.84) 100%);
}

.steps-section__inner {
  position: relative;
  z-index: 2;                    /* above the video and the scrim */
  width: var(--shell);
  margin-inline: auto;
}

/* --- Intro ---------------------------------------------------------------- */
.steps-section__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-mint);
}
.steps-section__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(127, 227, 212, 0.25), var(--c-mint));
  transform-origin: 0 50%;
}

.steps-section__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.85rem, 0.6rem + 2vw + 0.85vh, 3.1rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: #fff;
}
.steps-section__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-mint);
}

/* --- The three steps -------------------------------------------------------
   --steps-gap is referenced twice and the two uses must agree: it sets the
   space between columns AND the negative margin that lets each dashed run
   cross that space to reach the next marker. Change it in one place only.
   ---------------------------------------------------------------------------- */
.steps {
  --steps-gap: clamp(1.5rem, 3vw, 3rem);
  --marker: clamp(52px, 5vw, 64px);

  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--steps-gap);
  margin-top: clamp(2.75rem, 7vh, 5rem);
  list-style: none;
}

.step__marker {
  display: flex;
  align-items: center;
}

.step__num {
  display: grid;
  place-items: center;
  flex: none;
  width: var(--marker);
  height: var(--marker);
  font-family: var(--f-display);
  font-size: clamp(.95rem, 1.4vw, 1.15rem);
  font-style: italic;
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--c-mint);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18),
              0 10px 26px rgba(0, 0, 0, 0.28);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

/* The dashed run to the next marker. It reaches across the grid gap via the
   negative margin, so the dashes meet the next circle instead of stopping
   short at the column edge. */
.step__link {
  flex: 1 1 auto;
  height: 2px;
  min-width: 0;
  margin-left: clamp(.6rem, 1vw, 1rem);
  margin-right: calc(var(--steps-gap) * -1);
  border-radius: 2px;
  background: repeating-linear-gradient(90deg,
              rgba(255, 255, 255, 0.42) 0 8px,
              rgba(255, 255, 255, 0) 8px 16px);
  transform: scale(0);
  transform-origin: 0 50%;
}
/* Nothing to connect to after the last one. */
.step:last-child .step__link { display: none; }

.step__body { margin-top: clamp(1.1rem, 2.4vh, 1.6rem); }

.step__title {
  font-family: var(--f-display);
  font-size: clamp(.98rem, 0.85rem + 0.3vw, 1.12rem);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.005em;
  color: #fff;
}
.step__text {
  max-width: 34ch;
  margin-top: .55rem;
  font-size: clamp(.84rem, 0.8rem + 0.16vw, .92rem);
  font-weight: 400;
  line-height: 1.72;
  /* Not pure white: a full-strength body copy at this size on a dark panel
     glares and stops the titles leading. Still ~7:1. */
  color: rgba(255, 255, 255, 0.72);
}


/* ==========================================================================
   09. WHAT WE OFFER — the service cards
   Light ground again after the dark panel. Each card owns an accent as a
   custom property and the tag, the link and the glow behind it all read from
   that one value — so a new card is a colour and a copy change, nothing else.
   ========================================================================== */
.services {
  position: relative;
  z-index: 1;
  padding-block: clamp(4.5rem, 11vh, 8.5rem);
  background: var(--c-page);
}

.services__inner {
  width: var(--shell);
  margin-inline: auto;
}

/* --- Intro ---------------------------------------------------------------- */
.services__intro { max-width: min(60rem, 100%); }

.services__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
.services__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-mint), var(--c-blue-500));
  transform-origin: 0 50%;
}

.services__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.95vw + 0.8vh, 3rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.services__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

.services__lede {
  max-width: 56ch;
  margin-top: clamp(0.9rem, 1.8vh, 1.4rem);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}

/* --- The cards ------------------------------------------------------------ */
.cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.6vw, 2.25rem);
  margin-top: clamp(2.5rem, 6vh, 4.5rem);
  list-style: none;
}

/* The <li> is the positioning context and carries the accent; .card__panel is
   the visible card. They are separate elements because the panel has to clip
   its own photo, and a glow inside a clipping box is a glow you cannot see. */
.card {
  --card-accent: var(--c-blue-500);
  --card-ink:    var(--c-blue-700);
  --card-glow:   rgba(26, 114, 214, 0.22);

  position: relative;
  display: flex;
}
.card--restore {
  --card-accent: #0f8a7d;   /* mint, taken down to a weight type can sit at */
  --card-ink:    #0b6f65;
  --card-glow:   rgba(127, 227, 212, 0.34);
}

.card__glow {
  position: absolute;
  inset: -9% -7% -4%;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(48px);
  background: radial-gradient(circle at 50% 45%, var(--card-glow) 0%, rgba(255, 255, 255, 0) 68%);
}

.card__panel {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow: hidden;
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.22);
  border-radius: 22px;
  box-shadow: var(--sh-md);
  transition: transform var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              border-color var(--t-med) var(--ease-out);
}
.card:hover .card__panel {
  transform: translateY(-6px);
  border-color: rgba(120, 170, 225, 0.4);
  box-shadow: var(--sh-lg);
}

.card__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  /* Stands in until the photo exists, and sits under it after — a slow load
     resolves out of a designed panel rather than out of nothing. */
  background:
    radial-gradient(90% 80% at 28% 18%, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 60%),
    linear-gradient(150deg, #d3e8fb 0%, #bcdcf8 48%, #cfeae7 100%);
}
.card__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Slower than the panel's lift on purpose: the card should feel like it
     moves and the image like it drifts, not like both snap together. */
  transition: transform 1.1s var(--ease-out);
}
.card:hover .card__photo { transform: scale(1.05); }

.card__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1;
  padding: clamp(1.25rem, 2.2vw, 1.85rem);
}

.card__tag {
  font-size: 0.744rem;
  font-weight: 600;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--card-accent);
}

.card__title {
  margin-top: .7rem;
  font-family: var(--f-display);
  font-size: clamp(1.2rem, 0.9rem + 0.6vw, 1.6rem);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.016em;
  color: var(--c-blue-900);
}

.card__text {
  margin-top: .7rem;
  font-size: clamp(.84rem, 0.8rem + 0.16vw, .92rem);
  font-weight: 400;
  line-height: 1.72;
  color: var(--c-ink-soft);
}

/* margin-top:auto pins the link to the foot of the card, so two cards with
   different copy lengths still line their links up. */
.card__link {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  margin-top: auto;
  padding-top: clamp(1rem, 2vh, 1.5rem);
  font-size: 0.952rem;
  font-weight: 600;
  color: var(--card-ink);
}
.card__arrow {
  width: 17px;
  height: 17px;
  transition: transform var(--t-med) var(--ease-out);
}
.card__link:hover .card__arrow,
.card:hover .card__arrow { transform: translateX(4px); }

/* The underline draws from the left on hover, matching the nav links. */
.card__link span {
  position: relative;
}
.card__link span::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-med) var(--ease-out);
}
.card__link:hover span::after { transform: scaleX(1); }


/* ==========================================================================
   10. THE PROMO BAND
   One offer on one card. Three things carry it: a gradient hairline rim, a
   highlight that tracks the pointer across the panel, and a single pass of
   light when it arrives. All three are decoration layers inside the card —
   none of them touch the type, so none of them can cost it any sharpness.
   ========================================================================== */
.promo {
  position: relative;
  z-index: 1;
  /* No top padding: this band belongs to the cards above it, and the gap is
     the services section's own bottom padding. */
  padding-block: 0 clamp(4.5rem, 11vh, 8.5rem);
  background: var(--c-page);
}

.promo__inner {
  width: var(--shell);
  margin-inline: auto;
}

.promo__card {
  --mx: 50%;               /* pointer position, written by main.js */
  --my: 50%;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-template-columns: clamp(180px, 20vw, 250px) minmax(0, 1fr);
  align-items: center;
  gap: clamp(1.5rem, 3.5vw, 3.25rem);
  padding: clamp(1.5rem, 2.6vw, 2.25rem);
  background: #fff;
  border-radius: 26px;
  box-shadow: var(--sh-md);
}

/* Gradient hairline rim. The mask pair punches the middle out of a filled
   gradient, leaving only a 1px edge — which is how you get a gradient border
   at all, since border-image cannot follow a border-radius.

   Behind @supports on purpose: without mask-composite this rule would paint
   the gradient across the entire card instead of its edge, which is not a
   graceful degradation, it is a solid block over the content. Browsers
   without it simply get the plain white card. */
@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
  .promo__card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 3;
    padding: 1px;
    border-radius: inherit;
    pointer-events: none;
    background: linear-gradient(115deg,
                rgba(127, 227, 212, 0.9) 0%,
                rgba(111, 178, 244, 0.75) 34%,
                rgba(214, 232, 251, 0) 62%,
                rgba(127, 227, 212, 0.55) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
  }
}

/* Tracks the pointer. Held at zero until the pointer is actually over the
   card, so it never sits on as a static blob. */
.promo__spot {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .45s var(--ease-out);
  background: radial-gradient(280px 280px at var(--mx) var(--my),
              rgba(26, 114, 214, 0.13) 0%,
              rgba(127, 227, 212, 0.09) 38%,
              rgba(255, 255, 255, 0) 72%);
}
.promo__card:hover .promo__spot { opacity: 1; }

.promo__sheen {
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: 0;
  z-index: 2;
  width: 42%;
  opacity: 0;
  pointer-events: none;
  transform: translateX(-140%) rotate(8deg);
  background: linear-gradient(100deg,
              rgba(255, 255, 255, 0) 0%,
              rgba(255, 255, 255, 0.7) 50%,
              rgba(255, 255, 255, 0) 100%);
}

/* --- The tile ------------------------------------------------------------- */
.promo__media {
  position: relative;
  z-index: 1;
  overflow: hidden;
  aspect-ratio: 1;
  border-radius: 18px;
  /* The finished empty state, and what sits under a slow-loading photo. */
  background:
    radial-gradient(80% 70% at 32% 24%, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(155deg, #e6f1fb 0%, #d5e9f9 52%, #dff2ef 100%);
}
.promo__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- The copy ------------------------------------------------------------- */
.promo__body {
  position: relative;
  z-index: 1;
  padding-right: clamp(0rem, 2vw, 2rem);
}

.promo__brand { display: flex; align-items: center; }
.promo__logo { width: auto; height: clamp(26px, 2.6vw, 34px); }

/* Shown only once main.js has removed an <img> that could not load — see
   initMedia(). The brand set in our own type, never a redrawn mark. */
.promo__brand-text { display: none; }
.promo__brand.is-photo-missing .promo__brand-text {
  display: inline-block;
  font-family: var(--f-display);
  font-size: clamp(1.25rem, 1rem + 0.8vw, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--c-blue-700);
}
.promo__brand-text sup { font-size: .48em; top: -0.75em; }

.promo__title {
  margin-top: clamp(.9rem, 1.8vh, 1.35rem);
  font-family: var(--f-display);
  font-size: clamp(1.35rem, 0.85rem + 1.25vw, 2rem);
  font-weight: 500;
  line-height: 1.18;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.promo__title sup { font-size: .5em; top: -0.7em; }

.promo__text {
  max-width: 62ch;
  margin-top: .7rem;
  font-size: clamp(.86rem, 0.82rem + 0.18vw, .95rem);
  font-weight: 400;
  line-height: 1.72;
  color: var(--c-ink-soft);
}

.promo__actions { margin-top: clamp(1.15rem, 2.4vh, 1.75rem); }


/* ==========================================================================
   11. MEET THE TEAM
   The one centred block on the site. The photos carry a brand tint that
   lifts on hover — which does two useful things at once: it holds two
   portraits shot in different places to a single look, and it gives the card
   somewhere to go when you point at it.
   ========================================================================== */
.team {
  position: relative;
  z-index: 1;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: var(--c-page);
}

.team__inner {
  width: var(--shell);
  margin-inline: auto;
}

/* --- Intro ---------------------------------------------------------------- */
.team__intro {
  max-width: min(46rem, 100%);
  margin-inline: auto;
  text-align: center;
}

.team__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .9rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}

/* Two rules that draw OUTWARD from the label. Each is anchored at the edge
   nearest the text, so the pair opens from the centre rather than sliding
   across — which is the difference between a centred device and a
   left-aligned one that happens to be centred. */
.team__rule {
  flex: none;
  width: clamp(24px, 4vw, 52px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(26, 114, 214, 0), var(--c-blue-500));
}
.team__rule--l { transform-origin: 100% 50%; }
.team__rule--r {
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--c-blue-500), rgba(26, 114, 214, 0));
}

.team__title {
  margin-top: clamp(.85rem, 1.8vh, 1.3rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.95vw + 0.8vh, 3rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.team__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

/* --- The cards -------------------------------------------------------------
   Held to a narrower measure than the section: two portraits stretched to a
   1440px shell stop reading as people and start reading as banners.
   ---------------------------------------------------------------------------- */
.doctors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.6vw, 2.25rem);
  max-width: min(58rem, 100%);
  margin: clamp(2.5rem, 6vh, 4.25rem) auto 0;
  list-style: none;
}

.doctor { display: flex; }

.doctor__panel {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow: hidden;
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.2);
  border-radius: 22px;
  box-shadow: var(--sh-md);
  transition: transform var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              border-color var(--t-med) var(--ease-out);
}
.doctor:hover .doctor__panel {
  transform: translateY(-6px);
  border-color: rgba(120, 170, 225, 0.4);
  box-shadow: var(--sh-lg);
}

.doctor__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1;
  background:
    radial-gradient(80% 70% at 34% 22%, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(158deg, #dceaf8 0%, #c6dcf3 50%, #d5ece8 100%);
}
.doctor__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;   /* faces sit high in a portrait crop */
  transition: transform 1.1s var(--ease-out);
}
.doctor:hover .doctor__photo { transform: scale(1.045); }

/* The tint. Holds two portraits shot in different rooms to one look, and
   lifts on hover so the person arrives in full colour when you engage. */
.doctor__tint {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 1;
  mix-blend-mode: multiply;
  transition: opacity var(--t-slow) var(--ease-out);
  background: linear-gradient(168deg,
              rgba(198, 224, 248, 0.55) 0%,
              rgba(214, 235, 250, 0.3) 46%,
              rgba(127, 227, 212, 0.28) 100%);
}
.doctor:hover .doctor__tint { opacity: 0; }

.doctor__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1;
  padding: clamp(1.1rem, 2vw, 1.5rem) clamp(1.15rem, 2vw, 1.6rem) clamp(1.35rem, 2.4vw, 1.75rem);
}

.doctor__role {
  font-size: 0.732rem;
  font-weight: 600;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}

.doctor__name {
  position: relative;
  margin-top: .6rem;
  padding-bottom: .3rem;
  font-family: var(--f-display);
  font-size: clamp(1.18rem, 0.9rem + 0.6vw, 1.55rem);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.016em;
  color: var(--c-blue-900);
}
/* A hairline that grows under the name on hover — the card's only reply to
   being pointed at besides the lift, and it lands on the person's name. */
.doctor__name::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  max-width: 3.5rem;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-blue-500), var(--c-mint));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--t-med) var(--ease-out);
}
.doctor:hover .doctor__name::after { transform: scaleX(1); }

.doctor__bio {
  margin-top: .65rem;
  font-size: clamp(.82rem, 0.79rem + 0.15vw, .9rem);
  font-weight: 400;
  line-height: 1.7;
  color: var(--c-ink-soft);
}


/* ==========================================================================
   12. OUR OFFICE — the gallery mosaic
   ========================================================================== */
.office {
  position: relative;
  z-index: 1;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: #fff;
}

.office__inner {
  width: var(--shell);
  margin-inline: auto;
}

/* --- Intro ---------------------------------------------------------------- */
.office__intro { max-width: min(58rem, 100%); }

.office__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
.office__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-mint), var(--c-blue-500));
  transform-origin: 0 50%;
}

.office__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.9vw + 0.8vh, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.office__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

.office__lede {
  max-width: 62ch;
  margin-top: clamp(.85rem, 1.8vh, 1.3rem);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}

/* --- The mosaic ------------------------------------------------------------
   Rows are sized explicitly rather than by an aspect-ratio on the container.
   A ratio would have the tile height follow the shell width, and the shell
   caps at 1440px — so past that width the row heights would freeze while the
   columns kept growing, and the tiles would slowly go letterbox.
   ---------------------------------------------------------------------------- */
.office__gallery {
  display: grid;
  grid-template-columns: 1.55fr 1fr 1fr;
  grid-template-rows: repeat(2, clamp(135px, 15.5vw, 205px));
  gap: clamp(.65rem, 1.2vw, 1rem);
  margin-top: clamp(2.25rem, 5.5vh, 4rem);
  list-style: none;
}

.office__tile {
  position: relative;
  overflow: hidden;
  border-radius: 14px;
  /* The finished empty state, and what sits under a slow-loading photo. */
  background:
    radial-gradient(80% 70% at 30% 22%, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(158deg, #dceaf8 0%, #c6dcf3 52%, #d5ece8 100%);
}
.office__tile--lead {
  grid-row: span 2;
  border-radius: 18px;
}

.office__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.1s var(--ease-out);
}
.office__tile:hover .office__photo { transform: scale(1.05); }

/* A veil that lifts on hover. Same idea as the portraits: it holds a set of
   photographs taken at different times to one tonality, and gives each tile
   somewhere to go when you point at it. */
.office__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 1;
  mix-blend-mode: multiply;
  transition: opacity var(--t-slow) var(--ease-out);
  background: linear-gradient(165deg,
              rgba(198, 224, 248, 0.34) 0%,
              rgba(214, 235, 250, 0.16) 50%,
              rgba(127, 227, 212, 0.2) 100%);
}
.office__tile:hover .office__veil { opacity: 0; }

/* --- The View All tile ---------------------------------------------------- */
.office__more {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: .6rem;
  color: #fff;
  background: rgba(10, 26, 38, 0.52);
  transition: background var(--t-med) var(--ease-out);
}
.office__more:hover { background: rgba(10, 26, 38, 0.66); }

.office__more-label {
  font-size: clamp(.9rem, 0.85rem + 0.25vw, 1.05rem);
  font-weight: 500;
  letter-spacing: .01em;
  text-shadow: 0 1px 10px rgba(0, 0, 0, .3);
}
.office__more-icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  transition: transform var(--t-med) var(--ease-out);
}
.office__more-icon svg { width: 18px; height: 18px; }
.office__more:hover .office__more-icon { transform: translateY(-2px); }


/* ==========================================================================
   13. WHAT PATIENTS SAY — the review marquee
   ========================================================================== */
.reviews {
  position: relative;
  z-index: 1;
  overflow: hidden;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: var(--c-page);
}

.reviews__inner {
  width: var(--shell);
  margin-inline: auto;
}

/* --- Intro ---------------------------------------------------------------- */
.reviews__intro {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(1.5rem, 4vw, 3rem);
}
.reviews__lead { max-width: 40rem; }

.reviews__aside {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex: none;
  /* Sits on the button row, not the last line of copy */
  padding-bottom: .35rem;
}

.reviews__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
.reviews__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-mint), var(--c-blue-500));
  transform-origin: 0 50%;
}

.reviews__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.9vw + 0.8vh, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.reviews__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

.reviews__lede {
  max-width: 54ch;
  margin-top: clamp(.85rem, 1.8vh, 1.3rem);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}

/* --- The pause control -----------------------------------------------------
   Required, not decorative. Content that moves on its own for more than five
   seconds needs a way to stop it, and pausing on hover does not help anyone
   who is not holding a mouse.
   -------------------------------------------------------------------------- */
.reviews__toggle {
  display: grid;
  place-items: center;
  flex: none;
  width: 42px;
  height: 42px;
  color: var(--c-blue-700);
  background: var(--glass-bg);
  border: 1px solid var(--glass-brd-cool);
  border-radius: 50%;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.reviews__toggle:hover { background: #fff; border-color: var(--c-blue-300); transform: translateY(-1px); }
.reviews__toggle svg { width: 14px; height: 14px; }
/* One button, two glyphs: whichever matches the CURRENT state is hidden. */
.reviews__toggle-play { display: none; }
.reviews__toggle.is-paused .reviews__toggle-pause { display: none; }
.reviews__toggle.is-paused .reviews__toggle-play { display: block; }

/* --- The strip -------------------------------------------------------------
   Full-bleed, and faded at both edges with a mask so cards leave the frame
   instead of stopping dead against a margin. The mask is what makes an
   endless loop read as endless.
   -------------------------------------------------------------------------- */
.reviews__viewport {
  overflow: hidden;
  margin-top: clamp(2.25rem, 5.5vh, 3.75rem);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
}

.reviews__track {
  display: flex;
  align-items: stretch;
  gap: clamp(.9rem, 1.6vw, 1.35rem);
  /* Starts where the shell starts, so the first card lines up with the
     heading above it before the loop takes over. */
  padding-inline: max(var(--gutter), calc((100% - var(--shell)) / 2));
  list-style: none;
  will-change: transform;
}

.review {
  display: flex;
  flex-direction: column;
  flex: none;
  width: clamp(255px, 25vw, 340px);
  padding: clamp(1.15rem, 1.8vw, 1.6rem);
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.2);
  border-radius: 20px;
  box-shadow: var(--sh-sm);
}

.review__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* Named in words rather than drawn as someone else's mark. */
.review__source {
  font-size: 0.696rem;
  font-weight: 600;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--c-ink-faint);
}

/* Rating driven by one number on the wrapper: set data-rating and the stars
   past it dim. Amber because a rating star is amber everywhere — it is the
   one place a brand palette should not win over the convention. */
.stars { display: inline-flex; gap: 2px; }
.star {
  width: 13px;
  height: 13px;
  fill: var(--c-star);
}
.stars[data-rating="4"] .star:nth-child(n + 5),
.stars[data-rating="3"] .star:nth-child(n + 4),
.stars[data-rating="2"] .star:nth-child(n + 3),
.stars[data-rating="1"] .star:nth-child(n + 2) { fill: rgba(120, 145, 170, 0.28); }

.review__quote {
  margin-top: 1rem;
  font-size: clamp(.82rem, 0.79rem + 0.16vw, .9rem);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}
.review__quote::before { content: "\201C"; }
.review__quote::after  { content: "\201D"; }

.review__name {
  margin-top: auto;
  padding-top: 1.1rem;
  font-size: 0.941rem;
  font-weight: 600;
  color: var(--c-blue-900);
}
.review__name::before { content: "— "; }


/* ==========================================================================
   14. COMMON QUESTIONS — the accordion
   Native <details> underneath. The styling never assumes JS: [open] is a real
   attribute the browser sets, so the chevron, the border and the accent bar
   are all driven by it and work with the script absent.
   ========================================================================== */
.faq {
  position: relative;
  z-index: 1;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: #fff;
}

.faq__inner {
  width: var(--shell);
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: start;
  gap: clamp(2rem, 4.5vw, 4.5rem);
}

.faq__title {
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.9vw + 0.8vh, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.faq__title em {
  font-style: italic;
  font-weight: 500;
  color: var(--c-blue-700);
}

.faq__lede {
  max-width: 48ch;
  margin-top: clamp(.8rem, 1.6vh, 1.2rem);
  font-size: clamp(.86rem, 0.82rem + 0.18vw, .95rem);
  font-weight: 400;
  line-height: 1.7;
  color: var(--c-ink-soft);
}

/* --- The list ------------------------------------------------------------- */
.faq__list {
  display: flex;
  flex-direction: column;
  gap: clamp(.6rem, 1.1vw, .85rem);
  margin-top: clamp(1.75rem, 4vh, 2.75rem);
}

.faq__item {
  position: relative;
  overflow: hidden;
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.3);
  border-radius: 14px;
  transition: border-color var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out);
}
.faq__item:hover { border-color: var(--c-blue-300); }
.faq__item[open] {
  border-color: var(--c-blue-300);
  box-shadow: 0 6px 20px rgba(11, 45, 82, .07);
}

.faq__q {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: clamp(.9rem, 1.6vw, 1.15rem) clamp(1rem, 1.8vw, 1.35rem);
  font-size: clamp(.85rem, 0.8rem + 0.22vw, .95rem);
  font-weight: 600;
  line-height: 1.45;
  color: var(--c-blue-900);
  cursor: pointer;
  /* Both are needed: the first kills the marker in most engines, the second
     in WebKit, and a stray triangle inside a designed row is very visible. */
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }

/* The accent bar grows down the left edge as the item opens. Driven by
   [open], so it is correct even before the script has loaded. */
.faq__q::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--c-blue-500), var(--c-mint));
  transform: translateY(-50%);
  transition: height var(--t-med) var(--ease-out);
}
.faq__item[open] .faq__q::before { height: 58%; }

.faq__chevron {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  color: var(--c-blue-500);
  border-radius: 50%;
  background: rgba(26, 114, 214, .08);
  transition: transform var(--t-med) var(--ease-out),
              background var(--t-med) var(--ease-out);
}
.faq__chevron svg { width: 15px; height: 15px; }
.faq__item[open] .faq__chevron {
  transform: rotate(180deg);
  background: rgba(26, 114, 214, .16);
}

/* Two elements, and they have to stay two. The wrapper is what the height
   tween touches, so it carries no padding of its own — padding on an element
   animating from height:0 gets crushed and the text jumps at the end of the
   open. The padding lives on the inner, which never moves. */
.faq__panel { overflow: hidden; }
.faq__a {
  padding: 0 clamp(1rem, 1.8vw, 1.35rem) clamp(1rem, 1.8vw, 1.25rem);
  font-size: clamp(.82rem, 0.79rem + 0.16vw, .89rem);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}

/* --- The help panel ------------------------------------------------------- */
.faq__help {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  margin-top: clamp(1rem, 2vh, 1.5rem);
  padding: clamp(1.1rem, 2vw, 1.5rem);
  background: var(--c-page);
  border: 1px solid rgba(120, 170, 225, 0.22);
  border-radius: 16px;
}
.faq__help-text {
  font-size: clamp(.83rem, 0.8rem + 0.16vw, .9rem);
  font-weight: 400;
  line-height: 1.6;
  color: var(--c-ink-soft);
}

/* --- The photo ------------------------------------------------------------ */
.faq__media {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  aspect-ratio: 4 / 5;
  background:
    radial-gradient(80% 70% at 32% 22%, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(158deg, #dceaf8 0%, #c6dcf3 52%, #d5ece8 100%);
  box-shadow: var(--sh-md);
}
.faq__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.faq__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
  background: linear-gradient(168deg,
              rgba(198, 224, 248, 0.28) 0%,
              rgba(214, 235, 250, 0.12) 52%,
              rgba(127, 227, 212, 0.18) 100%);
}


/* ==========================================================================
   15. WHERE TO FIND US
   ========================================================================== */
.visit {
  position: relative;
  z-index: 1;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: var(--c-page);
}

.visit__inner {
  width: var(--shell);
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  align-items: center;
  gap: clamp(2rem, 4.5vw, 4.5rem);
}

.visit__eyebrow {
  display: flex;
  align-items: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
.visit__eyebrow-rule {
  flex: none;
  width: clamp(28px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--c-mint), var(--c-blue-500));
  transform-origin: 0 50%;
}

.visit__title {
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.9vw + 0.8vh, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.visit__title em { font-style: italic; font-weight: 500; color: var(--c-blue-700); }

.visit__address {
  max-width: 40ch;
  margin-top: clamp(.85rem, 1.8vh, 1.3rem);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.75;
  color: var(--c-ink-soft);
}

.visit__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .7rem;
  margin-top: clamp(1.4rem, 3vh, 2.1rem);
}

/* --- The hours card ------------------------------------------------------- */
.hours {
  padding: clamp(1.1rem, 2vw, 1.6rem);
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.22);
  border-radius: 22px;
  box-shadow: var(--sh-md);
}

.hours__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: clamp(.85rem, 1.6vw, 1.15rem);
}
.hours__title {
  font-family: var(--f-display);
  font-size: clamp(.95rem, 0.88rem + 0.3vw, 1.08rem);
  font-weight: 600;
  color: var(--c-blue-900);
}
.hours__note {
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-ink-faint);
}

/* Two per row, and the closed day takes the full width — it is the exception
   and it should look like one. */
.hours__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .5rem;
  list-style: none;
}

.hours__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .7rem .9rem;
  background: var(--c-page);
  border: 1px solid transparent;
  border-radius: 11px;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.hours__row--closed {
  grid-column: 1 / -1;
  background: rgba(255, 241, 235, 0.75);
}

/* Written by main.js from data-day. A ring rather than a fill, so the row
   still reads as one of a set rather than as a different kind of thing. */
.hours__row.is-today {
  background: #fff;
  border-color: var(--c-blue-300);
  box-shadow: 0 0 0 3px rgba(26, 114, 214, .1);
}

.hours__day {
  font-size: 0.888rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--c-blue-900);
}
.hours__time {
  font-size: 0.888rem;
  font-weight: 400;
  white-space: nowrap;
  color: var(--c-ink-soft);
}
.hours__row--closed .hours__time { color: var(--c-ink-faint); }


/* ==========================================================================
   16. BOOK AN APPOINTMENT — the stepped form
   ========================================================================== */
.book {
  position: relative;
  z-index: 1;
  padding-block: clamp(4rem, 10vh, 7.5rem);
  background: #fff;
}

.book__inner {
  width: var(--shell);
  margin-inline: auto;
}

.book__intro { max-width: min(46rem, 100%); margin-inline: auto; text-align: center; }

.book__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .8rem;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-blue-500);
}
.book__eyebrow-rule {
  flex: none;
  width: clamp(24px, 4vw, 46px);
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(26, 114, 214, 0), var(--c-blue-500));
  transform-origin: 100% 50%;
}

.book__title {
  margin-top: clamp(.85rem, 1.8vh, 1.3rem);
  font-family: var(--f-display);
  font-size: clamp(1.8rem, 0.55rem + 1.9vw + 0.8vh, 2.9rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--c-blue-900);
}
.book__title em { font-style: italic; font-weight: 500; color: var(--c-blue-700); }

/* --- The shell ------------------------------------------------------------ */
.book__shell {
  position: relative;
  max-width: min(50rem, 100%);
  margin: clamp(2.25rem, 5vh, 3.5rem) auto 0;
  padding: clamp(1.35rem, 3vw, 2.5rem);
  background: var(--c-page);
  border: 1px solid rgba(120, 170, 225, 0.24);
  border-radius: 24px;
  box-shadow: var(--sh-md);
}

/* --- Progress ------------------------------------------------------------- */
.book__steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .5rem;
  list-style: none;
}
.book__dot {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-width: 0;
  font-size: 0.84rem;
  font-weight: 500;
  color: var(--c-ink-faint);
  transition: color var(--t-med) var(--ease-out);
}
.book__dot span {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  font-size: 0.816rem;
  font-weight: 600;
  color: var(--c-ink-faint);
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.35);
  border-radius: 50%;
  transition: color var(--t-med) var(--ease-out),
              background var(--t-med) var(--ease-out),
              border-color var(--t-med) var(--ease-out);
}
.book__dot em {
  overflow: hidden;
  font-style: normal;
  letter-spacing: .04em;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.book__dot.is-active { color: var(--c-blue-900); }
.book__dot.is-active span,
.book__dot.is-done span {
  color: #fff;
  background: var(--c-blue-500);
  border-color: var(--c-blue-500);
}
.book__dot.is-done span { background: var(--c-mint); border-color: var(--c-mint); color: var(--c-blue-900); }

.book__bar {
  overflow: hidden;
  height: 3px;
  margin: .85rem 0 clamp(1.35rem, 3vh, 2rem);
  background: rgba(120, 170, 225, 0.25);
  border-radius: 3px;
}
.book__bar-fill {
  display: block;
  width: 25%;                 /* one of four; main.js drives it from here */
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--c-blue-500), var(--c-mint));
  transform-origin: 0 50%;
  transition: width var(--t-med) var(--ease-out);
}

/* --- Steps ---------------------------------------------------------------- */
.book__step { border: 0; }
.book__legend {
  padding: 0;
  font-family: var(--f-ui);
  font-size: clamp(1.15rem, 0.95rem + 0.5vw, 1.45rem);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--c-blue-900);
}
.book__hint {
  margin-top: .35rem;
  font-size: 0.896rem;
  line-height: 1.6;
  color: var(--c-ink-faint);
}

/* --- Choice cards --------------------------------------------------------- */
.book__choices {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .6rem;
  margin-top: clamp(1rem, 2.2vh, 1.5rem);
}
.choice { display: flex; }
/* The input stays in the DOM and focusable — it is what carries the state,
   the validation and the keyboard behaviour. It is only visually replaced. */
.choice input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.choice__box {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  flex: 1;
  padding: .85rem .95rem;
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.3);
  border-radius: 14px;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.choice__box:hover { border-color: var(--c-blue-300); transform: translateY(-1px); }
.choice__mark {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: .1rem;
  border: 1.5px solid rgba(120, 170, 225, 0.6);
  border-radius: 50%;
  transition: border-color var(--t-fast) var(--ease-out);
}
.choice__mark::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--c-blue-500);
  transform: scale(0);
  transition: transform var(--t-fast) var(--ease-out);
}
.choice__text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.choice__text strong {
  font-size: 0.952rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--c-blue-900);
}
.choice__text em {
  font-style: normal;
  font-size: 0.864rem;
  line-height: 1.4;
  color: var(--c-ink-faint);
}

.choice input:checked + .choice__box {
  border-color: var(--c-blue-500);
  box-shadow: 0 0 0 3px rgba(26, 114, 214, .12);
}
.choice input:checked + .choice__box .choice__mark { border-color: var(--c-blue-500); }
.choice input:checked + .choice__box .choice__mark::after { transform: scale(1); }
/* The visible box has to show focus, because the real input cannot. */
.choice input:focus-visible + .choice__box {
  outline: 2px solid var(--c-blue-500);
  outline-offset: 3px;
}

/* --- Pills ---------------------------------------------------------------- */
.book__pills {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .55rem;
}
.pill { display: flex; }
.pill input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.pill span {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .6rem 1.05rem;
  font-size: 0.918rem;
  font-weight: 600;
  color: var(--c-blue-900);
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.3);
  border-radius: 100px;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out);
}
.pill span em {
  font-style: normal;
  font-size: 0.792rem;
  font-weight: 400;
  color: var(--c-ink-faint);
  transition: color var(--t-fast) var(--ease-out);
}
.pill span:hover { border-color: var(--c-blue-300); }
.pill input:checked + span {
  color: #fff;
  background: var(--c-blue-500);
  border-color: var(--c-blue-500);
}
.pill input:checked + span em { color: rgba(255, 255, 255, .8); }
.pill input:focus-visible + span { outline: 2px solid var(--c-blue-500); outline-offset: 3px; }

/* --- Fields --------------------------------------------------------------- */
.book__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .8rem;
  margin-top: clamp(1rem, 2.2vh, 1.5rem);
}
.field { display: flex; flex-direction: column; gap: .35rem; margin-top: clamp(.9rem, 2vh, 1.25rem); }
.book__grid .field { margin-top: 0; }
.field--wide { grid-column: 1 / -1; }

.field__label {
  font-size: 0.864rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--c-blue-900);
}
.field__label--group { margin-top: clamp(.9rem, 2vh, 1.25rem); }
.field__optional { font-weight: 400; color: var(--c-ink-faint); }

.field__input {
  width: 100%;
  padding: .7rem .9rem;
  font-family: var(--f-ui);
  font-size: 0.974rem;
  color: var(--c-ink);
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.35);
  border-radius: 11px;
  transition: border-color var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out);
}
.field__input:focus {
  outline: none;
  border-color: var(--c-blue-500);
  box-shadow: 0 0 0 3px rgba(26, 114, 214, .12);
}
.field__input--area { resize: vertical; min-height: 5rem; line-height: 1.6; }
/* Only after the field has been through a failed submit — styling :invalid
   from the start marks every empty required field red before it is touched. */
.field__input.is-invalid { border-color: #d7513f; box-shadow: 0 0 0 3px rgba(215, 81, 63, .12); }

/* --- Summary -------------------------------------------------------------- */
.summary {
  display: grid;
  gap: .5rem;
  margin-top: clamp(1rem, 2.2vh, 1.5rem);
}
.summary__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: .7rem .9rem;
  background: #fff;
  border: 1px solid rgba(120, 170, 225, 0.22);
  border-radius: 11px;
}
.summary__row dt {
  flex: none;
  font-size: 0.816rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--c-ink-faint);
}
.summary__row dd {
  font-size: 0.941rem;
  font-weight: 500;
  text-align: right;
  color: var(--c-blue-900);
}

.book__smallprint {
  margin-top: 1rem;
  font-size: 0.864rem;
  line-height: 1.6;
  color: var(--c-ink-faint);
}

/* --- Controls ------------------------------------------------------------- */
.book__controls {
  display: flex;
  justify-content: flex-end;
  gap: .6rem;
  margin-top: clamp(1.35rem, 3vh, 2rem);
}
.book__controls .btn:first-child:not([hidden]) { margin-right: auto; }

.book__error {
  margin-top: .9rem;
  font-size: 0.936rem;
  font-weight: 500;
  color: #b83c2b;
}

/* --- Confirmation --------------------------------------------------------- */
.book__done { text-align: center; padding-block: clamp(1rem, 3vh, 2rem); }
.book__done-mark { display: inline-grid; place-items: center; color: var(--c-blue-500); }
.book__done-mark svg { width: 52px; height: 52px; }
.book__done-title {
  margin-top: .9rem;
  font-family: var(--f-display);
  font-size: clamp(1.25rem, 1rem + 0.7vw, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.014em;
  color: var(--c-blue-900);
}
.book__done-text {
  max-width: 34rem;
  margin: .55rem auto 0;
  font-size: 0.974rem;
  line-height: 1.7;
  color: var(--c-ink-soft);
}
.book__done .btn { margin-top: 1.35rem; }


/* ==========================================================================
   17. FOOTER
   The page's one deep ground. Blue at the left running to teal at the right,
   which is the site's two accents finally meeting — everything above uses one
   or the other, never both at full strength.
   ========================================================================== */
.foot {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(3rem, 8vh, 5.5rem) clamp(1.5rem, 3vh, 2.25rem);
  color: #fff;
  background:radial-gradient(90% 120% at 82% 8%, rgba(127, 227, 212, 0.16) 0%, rgba(127, 227, 212, 0) 62%), linear-gradient(104deg, #324860 0%, #186d9f 46%, #0c4a5c 100%);
}

/* Hairline along the top edge, drawn on by the reveal. The seam between the
   last white section and this has to be stated or it reads as an accident. */
.foot__rule {
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  transform-origin: 0 50%;
  background: linear-gradient(90deg,
              var(--c-blue-500) 0%,
              var(--c-mint) 42%,
              rgba(127, 227, 212, 0) 100%);
}

/* The navbar's own mark, blown up and taken almost to nothing. It reads as
   watermarked stationery rather than as a logo someone left too big. */
.foot__watermark {
  position: absolute;
  right: -6%;
  bottom: -22%;
  width: clamp(320px, 34vw, 560px);
  color: rgba(255, 255, 255, 0.05);
  pointer-events: none;
}
.foot__watermark svg { width: 100%; height: auto; }

.foot__inner {
  position: relative;
  z-index: 1;
  width: var(--shell);
  margin-inline: auto;
}

.foot__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.8fr) minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 4rem);
}

/* --- Brand ---------------------------------------------------------------- */
.logo--foot { color: #fff; }
.logo--foot .logo__mark {
  color: var(--c-mint);
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.2);
  box-shadow: none;
}
.logo--foot:hover .logo__mark { box-shadow: 0 8px 24px rgba(0, 0, 0, .25); }
.logo--foot .logo__sub { color: rgba(255, 255, 255, 0.6); }

.foot__blurb {
  max-width: 38ch;
  margin-top: clamp(1rem, 2vh, 1.5rem);
  font-size: 0.963rem;
  font-weight: 400;
  line-height: 1.75;
  color: rgba(255, 255, 255, 0.72);
}

/* --- Columns -------------------------------------------------------------- */
.foot__head {
  font-family: var(--f-display);
  font-size: 1.03rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: #fff;
}

.foot__list,
.foot__contact,
.foot__legal { list-style: none; }

.foot__list { margin-top: 1.1rem; display: flex; flex-direction: column; gap: .7rem; }
.foot__list a {
  position: relative;
  display: inline-block;
  font-size: 0.963rem;
  color: rgba(255, 255, 255, 0.72);
  transition: color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
/* The rule draws from the left and the label steps with it, so the link
   moves as one thing rather than growing an underline underneath itself. */
.foot__list a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: var(--c-mint);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-med) var(--ease-out);
}
.foot__list a:hover { color: #fff; transform: translateX(3px); }
.foot__list a:hover::after { transform: scaleX(1); }

.foot__contact { margin-top: 1.1rem; display: flex; flex-direction: column; gap: .9rem; }
.foot__contact li {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  font-size: 0.963rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.72);
}
.foot__contact a { color: inherit; transition: color var(--t-fast) var(--ease-out); }
.foot__contact a:hover { color: #fff; }

.foot__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  color: var(--c-mint);
  background: rgba(127, 227, 212, 0.12);
  border-radius: 8px;
}
.foot__icon svg { width: 15px; height: 15px; }

.foot__quick {
  display: flex;
  flex-wrap: wrap;
  gap: 1.4rem;
  margin-top: 1.25rem;
}
.foot__quick a {
  position: relative;
  font-size: 0.963rem;
  font-weight: 600;
  color: var(--c-mint);
  transition: color var(--t-fast) var(--ease-out);
}
.foot__quick a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-med) var(--ease-out);
}
.foot__quick a:hover { color: #fff; }
.foot__quick a:hover::after { transform: scaleX(1); }

/* --- Bottom bar ----------------------------------------------------------- */
.foot__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: clamp(2.5rem, 6vh, 4rem);
  padding-top: clamp(1.1rem, 2.4vh, 1.6rem);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.foot__copy {
  font-size: 0.936rem;
  color: rgba(255, 255, 255, 0.55);
}
.foot__legal { display: flex; gap: 1.6rem; }
.foot__legal a {
  font-size: 0.936rem;
  color: rgba(255, 255, 255, 0.55);
  transition: color var(--t-fast) var(--ease-out);
}
.foot__legal a:hover { color: #fff; }

/* --- Back to top ---------------------------------------------------------- */
.foot__top {
  position: absolute;
  right: clamp(1rem, 4vw, 3rem);
  top: clamp(1.75rem, 5vh, 3rem);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 50%;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.foot__top svg { width: 18px; height: 18px; }
.foot__top:hover {
  background: rgba(127, 227, 212, 0.18);
  border-color: var(--c-mint);
  transform: translateY(-3px);
}


/* ==========================================================================
   19. ANIMATION ENTRY STATES
   Set here (not in JS) so there is no flash of unstyled content.
   `.no-js` is removed by main.js in the same frame it sets the GSAP start
   values; if JS or GSAP never arrives, the class stays and everything below
   simply renders visible.
   ========================================================================== */
.line__inner,
[data-reveal],
[data-why-reveal],
[data-steps-reveal],
[data-services-reveal],
[data-promo-reveal],
[data-team-reveal],
[data-office-reveal],
[data-reviews-reveal],
[data-faq-reveal],
[data-visit-reveal],
[data-book-reveal],
[data-foot-col],
.hours,
.book__shell,
.promo__brand,
.faq__item,
.reviews__viewport,
.office__more,
.doctor__body > *,
.why__badge,
.step__num,
.step__body,
.card__glow,
.card__body > *,
/* THE STRIP, NOT THE CARDS. It arrives as one object — see the note on
   revealRail() in hero.js — and hiding the cards individually would leave the
   clones hero.js makes at whatever opacity the original had at the moment it
   copied them. */
.hero__rail { opacity: 0; }

/* The media panel is revealed with a clip wipe, so it starts fully masked
   rather than transparent — fading a photo in and wiping it in at the same
   time reads as neither. */
.why__frame,
.card__media,
.promo__media,
.doctor__media,
.office__tile,
.faq__media { clip-path: inset(100% 0 0 0); }
.why__frame-echo { opacity: 0; }

.no-js .line__inner,
.no-js [data-reveal],
.no-js [data-why-reveal],
.no-js [data-steps-reveal],
.no-js [data-services-reveal],
.no-js [data-promo-reveal],
.no-js [data-team-reveal],
.no-js [data-office-reveal],
.no-js [data-reviews-reveal],
.no-js [data-faq-reveal],
.no-js [data-visit-reveal],
.no-js [data-book-reveal],
.no-js [data-foot-col],
.no-js .hours,
.no-js .book__shell,
.no-js .faq__item,
.no-js .reviews__viewport,
.no-js .office__more,
.no-js .promo__brand,
.no-js .doctor__body > *,
.no-js .card__glow,
.no-js .card__body > *,
.no-js .why__badge,
.no-js .why__frame-echo,
.no-js .step__num,
.no-js .step__body,
.no-js .hero__rail { opacity: 1; }

/* No marquee means the strip never moves, so it has to be reachable by hand.
   Without this the reviews past the right edge are simply unavailable. */
.no-js .reviews__viewport {
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.no-js .review { scroll-snap-align: start; }
.no-js .reviews__toggle { display: none; }

/* With nothing to run the loop, the cards past the right edge are simply
   unreachable — so the strip becomes a plain scroller and you can push it.
   `overflow-y: hidden` has to be restated: the live strip runs `clip` on one
   axis and `visible` on the other, and `visible` beside `auto` computes to
   `auto`, which would put a vertical scrollbar on the row. */
.no-js .hero__rail {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.no-js .hcard { scroll-snap-align: start; animation: none; }

.no-js .why__frame,
.no-js .card__media,
.no-js .promo__media,
.no-js .doctor__media,
.no-js .office__tile,
.no-js .faq__media { clip-path: none; }

/* The dashed runs are drawn on. With nothing to draw them, they are drawn. */
.no-js .step__link,
.no-js .steps-section__eyebrow-rule,
.no-js .services__eyebrow-rule,
.no-js .team__rule,
.no-js .office__eyebrow-rule,
.no-js .reviews__eyebrow-rule,
.no-js .visit__eyebrow-rule,
.no-js .book__eyebrow-rule,
.no-js .foot__rule { transform: none; }

/* The rule under the accent word is drawn on by the intro. With no intro to
   draw it, it is simply already drawn. */
.no-js .word__rule { transform: none; }



/* ==========================================================================
   20. RESPONSIVE
   ========================================================================== */

/* --- Laptop --------------------------------------------------------------- */
@media (max-width: 1180px) {
  .nav__list { gap: 1.4rem; }
}

/* --- Short viewports: tighten the rhythm rather than overflow the frame ---
   The hero is a 100svh box with a card rail nailed to the floor of it, so the
   copy stack has to fit in what is left. These are the same proportions,
   compressed — not a different composition.
   -------------------------------------------------------------------------- */
@media (max-height: 820px) {
  :root {
    /* Same 8pt grid, one step down each — a short laptop gets the compressed
       proportions, not a different system. */
    --sp-eyebrow: clamp(8px,  1.1vh, 16px);
    --sp-head:    clamp(16px, 1.7vh, 24px);
    --sp-lede:    clamp(24px, 2.3vh, 32px);
    --sp-cta:     clamp(16px, 2vh,   24px);
    --sp-top:     clamp(16px, 2.2vh, 24px);
  }
  /* The cards are the first thing to give up room: they are supporting proof,
     and the sentence is the reason to be here. One token, and the height, the
     overlap, the figure size and the frame's own min-height all follow it
     down together. */
  :root { --hcard-w: clamp(10rem, 17vw, 15rem); }
}

/* --- Narrow desktop --------------------------------------------------------
   The photograph's clear space runs out before the viewport does. The wash
   turns from a left-to-right gradient into a bottom-up one, so the copy is
   read against the floor of the frame rather than against the subject.
   -------------------------------------------------------------------------- */
@media (max-width: 1200px) {
  /* Three authored lines have to stay three lines, so the column keeps enough
     width for the longest of them and the display size steps down to match.
     Raising one without the other is what would rag it into four. */
  :root { --fs-title: clamp(1.85rem, 0.55rem + 4.3vmin, 3rem); }

  .hero__lede { max-width: 38ch; }

  .hero__wash {
    background:
      linear-gradient(101deg,
        rgba(255, 255, 255, .94) 0%,
        rgba(255, 255, 255, .84) 30%,
        rgba(255, 255, 255, .44) 52%,
        rgba(255, 255, 255, .08) 74%,
        rgba(255, 255, 255, 0)   88%),
      linear-gradient(to top,
        rgba(240, 248, 251, .95) 0%,
        rgba(240, 248, 251, .52) 22%,
        rgba(240, 248, 251, 0)   44%),
      linear-gradient(to bottom,
        rgba(255, 255, 255, .62) 0%,
        rgba(255, 255, 255, 0) 20%);
  }

  /* The two columns are still side by side here, but the copy needs the
     larger share once the photo can no longer be generous. */
  .why__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr); }
  .why__badge-value { font-size: 1.8rem; }
}

/* --- The section stacks ----------------------------------------------------
   Below this the two columns cannot both hold a usable measure, so the copy
   takes the full width and the photo follows it. Copy first in the DOM and
   first on screen: the heading is what tells you what you are looking at.
   -------------------------------------------------------------------------- */
@media (max-width: 860px) {
  .why__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2.25rem, 6vw, 3.25rem);
  }
  .why__copy { max-width: none; }
  .why__lede { max-width: 54ch; }

  /* Wider than it is tall on a narrow screen — a 16/11 panel at full width
     eats most of a phone viewport on its own. */
  .why__frame { aspect-ratio: 16 / 12; }
  .why__frame-echo { inset: 5% -3% -5% 3%; }

  /* Back inside the frame: there is no longer any gutter for it to hang in. */
  .why__badge {
    left: auto;
    right: clamp(0.9rem, 3vw, 1.5rem);
    bottom: clamp(0.9rem, 3vw, 1.5rem);
  }

  .why__aura { top: 40%; right: -30%; }
}

/* --- The steps stack -------------------------------------------------------
   Three columns of body copy stop being readable well before the viewport
   does, so the list turns into a vertical timeline: marker column on the
   left, copy on the right, and the dashed run rotates to join each marker to
   the one below it. The reveal scales BOTH axes, so the same tween draws the
   line whichever way round it is — that is why it is written that way.
   -------------------------------------------------------------------------- */
@media (max-width: 780px) {
  .steps {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .step {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: clamp(1rem, 4vw, 1.4rem);
  }

  /* The marker column becomes the full height of the row so the dash can run
     from under this circle all the way to the next one. */
  .step__marker {
    flex-direction: column;
    align-items: center;
    height: 100%;
  }

  .step__link {
    width: 2px;
    height: auto;
    flex: 1 1 auto;
    min-height: 1.5rem;
    margin: clamp(.5rem, 2vw, .75rem) 0 0 0;
    transform-origin: 50% 0;
    background: repeating-linear-gradient(180deg,
                rgba(255, 255, 255, 0.42) 0 8px,
                rgba(255, 255, 255, 0) 8px 16px);
  }

  .step__body {
    margin-top: 0;
    padding-bottom: clamp(1.75rem, 5vw, 2.5rem);
    /* Optically centres the title against its circle rather than aligning
       box tops, which reads as the text sitting low. */
    padding-top: calc((var(--marker) - 1.5em) / 2);
  }
  .step:last-child .step__body { padding-bottom: 0; }
  .step__text { max-width: 46ch; }
}

/* --- The opening hours go to one column FIRST -------------------------------
   Earlier than everything else on purpose, and this is the width where it
   matters: between 781px and 900px the visit section is still two columns AND
   the hours list is still two columns, so a day name and a time range are
   sharing about 150px. `.hours__time` is `white-space: nowrap` — it has to
   be, times must not break across lines — so instead of wrapping, the row
   pushed 7px past the right edge of the viewport and put a horizontal
   scrollbar on the whole page.

   One column here, and the times have room at every width above the point
   where the section itself stacks.
   -------------------------------------------------------------------------- */
@media (max-width: 980px) {
  .hours__list { grid-template-columns: minmax(0, 1fr); }
}

/* --- The cards stack -------------------------------------------------------
   Two cards side by side stop earning their width before the viewport runs
   out: the photo gets small and the copy measure gets tight at the same time.
   One column, full-width photos.
   -------------------------------------------------------------------------- */
@media (max-width: 780px) {
  .cards,
  .doctors { grid-template-columns: minmax(0, 1fr); }

  /* The button drops under the copy rather than being squeezed beside it. */
  .reviews__intro { flex-direction: column; align-items: flex-start; gap: 1.5rem; }
  .reviews__aside { padding-bottom: 0; }

  /* Questions first, photo after: the answers are the reason to be here. */
  .faq__inner { grid-template-columns: minmax(0, 1fr); }
  .faq__media { aspect-ratio: 4 / 3; }

  .visit__inner { grid-template-columns: minmax(0, 1fr); }
  .book__choices,
  .book__grid { grid-template-columns: minmax(0, 1fr); }
  /* Only the number survives; four labels at a phone width truncate to
     nothing useful. */
  .book__dot em { display: none; }
  .book__steps { justify-items: center; }

  /* Brand block over the full width, the two link columns paired beneath. */
  .foot__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem 1.5rem; }
  .foot__brand { grid-column: 1 / -1; }
  .foot__bar { justify-content: flex-start; }
  /* It would sit on top of the logo at this width. */
  .foot__top { display: none; }

  /* The lead photo takes the full width and the four others pair off under
     it — a 1.55fr column beside two more is unreadable at this size. */
  .office__gallery {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: clamp(150px, 40vw, 260px) repeat(2, clamp(105px, 26vw, 170px));
  }
  .office__tile--lead { grid-column: 1 / -1; grid-row: auto; }
  /* A square portrait at full phone width is most of a screen. */
  .doctor__media { aspect-ratio: 4 / 3; }

  /* The tile stops being a sidebar and becomes a lead image. Capped in
     height so it does not eat the whole viewport before the offer is read. */
  .promo__card {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1.15rem, 4vw, 1.6rem);
  }
  .promo__media {
    aspect-ratio: 16 / 10;
    max-height: 46vh;
  }
  .promo__body { padding-right: 0; }
  /* A pointer-tracked highlight is meaningless without a pointer. */
  .promo__spot { display: none; }

  .card__media { aspect-ratio: 16 / 9; }
  .card__glow { inset: -6% -4% -3%; filter: blur(38px); }
}

/* --- Tablet: the navigation collapses ------------------------------------- */
@media (max-width: 900px) {
  :root { --nav-h: 72px; }

  .nav__menu {
    position: fixed;
    /* Lined up with the pill it drops from, which now measures its own insets
       off the hero frame rather than off the page gutter. */
    inset: var(--nav-h) calc(var(--hero-inset) + var(--hero-pad)) auto;
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    padding: 1.5rem;
    background: rgba(255,255,255,.8);
    border: 1px solid var(--glass-brd);
    border-radius: 22px;
    box-shadow: var(--sh-lg);
    -webkit-backdrop-filter: blur(26px) saturate(170%);
    backdrop-filter: blur(26px) saturate(170%);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-12px);
    transition: opacity var(--t-med) var(--ease-out),
                transform var(--t-med) var(--ease-out),
                visibility var(--t-med);
  }
  .nav.is-open .nav__menu { opacity: 1; visibility: visible; transform: translateY(0); }

  .nav__list { flex-direction: column; align-items: stretch; gap: .25rem; }
  .nav__item { position: static; }
  .nav__link {
    display: flex;
    justify-content: space-between;
    width: 100%;
    padding: .7rem .25rem;
    font-size: 1rem;
  }
  .nav__link::after { display: none; }

  /* Inline and always open. A hover-triggered flyout on a touch screen is a
     panel nobody can reach, and a tap that both opens a menu and could
     navigate is a tap that does the wrong thing half the time. */
  .nav__panel {
    position: static;
    min-width: 0;
    margin: 0 0 .5rem;
    padding: .25rem 0 .25rem .9rem;
    background: none;
    border: 0;
    border-left: 2px solid rgba(120, 170, 225, .35);
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .nav__panel::before { display: none; }
  .nav__panel a { padding: .5rem .25rem; }
  .nav__caret { display: none; }

  .nav__actions { flex-direction: column; align-items: stretch; gap: .9rem; }
  .nav__phone { justify-content: center; }
  .nav__cta { width: 100%; }

  /* The pill itself is inherited — it is the header's one design at every
     width and every scroll position now, and it keeps the hero frame's insets
     here too so it still floats INSIDE the rounded card on a tablet. All this
     breakpoint changes is the padding, so the logo and the menu button are not
     pressed against the ends. */
  .nav__inner { padding: 6px 6px 6px .9rem; }

  .nav__toggle { display: grid; }
}

/* --- Phone ---------------------------------------------------------------- */
@media (max-width: 560px) {
  /* Tracks width directly here rather than vmin: on a phone it is the width
     the two-line headline is fighting, and a tall phone would otherwise be
     handed a size its own column cannot hold. */
  :root { --fs-title: clamp(1.65rem, 7.4vw, 2.4rem); }

  /* Two cards to a screen, and the vw term is abandoned here on purpose:
     19.5% of a 360px phone is 70px, which is not a card. At this width the
     card is sized to the screen rather than to the frame — but the strip is
     still the same strip, still half in and half out, still looping. Only the
     count you can see at once changes. */
  :root {
    --hcard-w:     min(46vw, 12.5rem);
    --hcard-gap:   .7rem;
    --hero-inset:  8px;
    --hero-radius: 22px;
  }
  .hcard { border-radius: 15px; }

  .hero__actions { flex-direction: column; align-items: stretch; }
  .btn { width: 100%; }

  .why { padding-block: clamp(3.5rem, 9vh, 5rem); }
  .why__frame { aspect-ratio: 4 / 3; border-radius: 18px; }
  .why__frame-echo { border-radius: 20px; }
  .why__badge { padding: .7rem 1rem .7rem .85rem; gap: .65rem; border-radius: 15px; }
  .why__badge-value { font-size: 1.65rem; }
  .why__badge-label { font-size: 0.672rem; letter-spacing: .1em; }

  .logo__sub { letter-spacing: .28em; }
  .hero__grain { opacity: .06; }
}

/* --- Short landscape screens: keep the frame breathable -------------------
   There is not enough height here for a sentence, a button and a row of
   cards. The sentence and the button win; the cards are one scroll away.
   -------------------------------------------------------------------------- */
@media (max-height: 620px) and (min-width: 901px) {
  .hero__lede { display: none; }
  :root { --hcard-w: clamp(8rem, 14vw, 11.5rem); }
}


/* ==========================================================================
   21. REDUCED MOTION / PRINT
   The sequence stays scrubbable — it is entirely user-driven — but every
   autonomous animation stops.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  /* Nothing stays hidden waiting for an animation that will not play */
  .line__inner,
  [data-reveal],
  [data-why-reveal],
  [data-steps-reveal],
  [data-services-reveal],
  [data-promo-reveal],
  [data-team-reveal],
  [data-office-reveal],
  [data-reviews-reveal],
  [data-faq-reveal],
  [data-visit-reveal],
  [data-book-reveal],
  [data-foot-col],
  .hours,
  .book__shell,
  .promo__brand,
  .faq__item,
  .reviews__viewport,
  .office__more,
  .doctor__body > *,
  .card__glow,
  .card__body > *,
  .why__badge,
  .why__frame-echo,
  .step__num,
  .step__body,
  .hero__rail { opacity: 1 !important; }

  .why__frame,
  .card__media,
  .promo__media,
  .doctor__media,
  .office__tile,
  .faq__media { clip-path: none !important; }

  /* Same as the no-js case: nothing is going to loop the strip for you —
     hero.js checks this preference and never clones or starts it — so it has
     to be pushable by hand or the cards past the edge do not exist.
     `overflow-y: hidden` is restated because the live strip runs `visible`
     there, and `visible` beside `auto` computes to `auto`. */
  .hero__rail { overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity; }
  .hcard {
    scroll-snap-align: start;
    animation: none !important;
    transform: none !important;
    translate: none !important;
  }
  .hero__track { transform: none !important; }

  .reviews__viewport { overflow-x: auto; scroll-snap-type: x proximity; }
  .review { scroll-snap-align: start; }
  .reviews__toggle { display: none; }
  .step__link,
  .steps-section__eyebrow-rule,
  .services__eyebrow-rule,
  .team__rule,
  .office__eyebrow-rule,
  .reviews__eyebrow-rule,
  .visit__eyebrow-rule,
  .book__eyebrow-rule,
  .foot__rule { transform: none !important; }

  /* A looping video behind content is precisely the thing this preference is
     asking us not to run. The panel keeps its flat dark background. */
  .steps-section__video { display: none; }

  .word__rule { transform: none !important; }
  .hero__grain { display: none; }

  /* The one button on the page that breathes. Not here it does not. */
  .hero__actions .btn--primary { animation: none !important; }
}

@media print {
  .hero__stage, .nav__toggle { display: none; }
  .hero__frame {
    min-height: 0;
    padding-bottom: 0;
    border: 1px solid #ccd8e4;
  }
  /* Nothing hangs off anything on paper: the strip drops back into the flow
     and wraps, because a clipped row prints as one row and loses four cards. */
  .hero__rail { overflow: visible; margin-top: 1rem; }
  .hero__track { flex-wrap: wrap; transform: none !important; }
  .hcard { animation: none !important; }
}

.why{
  padding: clamp(56px, 8vw, 120px) clamp(20px, 5vw, 72px);
  /* --c-page, not --bg. `--bg` is not a token this stylesheet defines, so this
     declaration was invalid at computed-value time and the section was
     rendering TRANSPARENT — which matters more than it sounds, because this is
     the element the hero hands over to. Scene seven cranes the environment
     upward and dissolves it as this section arrives, and "arrives" means
     "covers what is underneath". A transparent panel means the two are
     cross-fading over each other instead of one replacing the other, and the
     hand-off loses the very continuity it exists to create.

     It has to stay equal to <body>'s own background for the same reason: if
     the two ever drift apart there is a visible seam at the moment of the
     hand-off. One token, used by both. */
  background: var(--c-page);
  overflow: hidden;
}
.why__inner{
  max-width: 1240px;
  margin-inline: auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(40px, 6vw, 88px);
}

/* ============ TEXT COLUMN ============ */
.eyebrow{
  margin:0 0 18px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
/* animated rule that draws itself next to the eyebrow */
.eyebrow::after{
  content:"";
  width: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), transparent);
  transition: width 1s var(--ease) .35s;
}
.is-in .eyebrow::after{ width: 54px; }

.headline{
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(34px, 4.4vw, 54px);
  line-height: 1.16;
  letter-spacing: -.01em;
  color: var(--ink);
  margin: 0 0 22px;
  max-width: 15ch;
}
/* word-by-word mask reveal */
.headline .w{ display:inline-block; overflow:hidden; vertical-align:bottom; }
.headline .w > span{
  display:inline-block;
  transform: translateY(110%);
  transition: transform .9s var(--ease);
  transition-delay: calc(var(--i) * 70ms);
}
.is-in .headline .w > span{ transform: translateY(0); }

.copy{
  margin: 0 0 34px;
  max-width: 52ch;
  color: var(--ink-soft);
  font-size: clamp(15px, 1.1vw, 16.5px);
  line-height: 1.75;
}

/* staggered fade-up for copy + button */
.rise{
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay: calc(var(--d, 0) * 1ms);
}
.is-in .rise{ opacity:1; transform:none; }

/* ============ CTA ============ */
.cta{
  --pad: 18px;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: var(--pad) 34px;
  border-radius: 999px;
  border: 0;
  cursor: pointer;
  font-family: var(--body);
  font-size: 15px;
  font-weight: 500;
  color: #fff;
  text-decoration: none;
  background: var(--teal);
  box-shadow: 0 10px 26px -10px rgba(24,159,208,.75);
  overflow: hidden;
  transition: transform .45s var(--ease), box-shadow .45s var(--ease);
}
/* liquid fill that rises on hover */
.cta::before{
  content:"";
  position:absolute; inset:0; z-index:-1;
  background: linear-gradient(140deg, var(--teal-deep), #0b6b91);
  transform: translateY(101%);
  transition: transform .5s var(--ease);
}
.cta:hover, .cta:focus-visible{
  transform: translateY(-3px);
  box-shadow: 0 18px 34px -12px rgba(15,126,168,.8);
}
.cta:hover::before, .cta:focus-visible::before{ transform: translateY(0); }
.cta svg{ transition: transform .45s var(--ease); }
.cta:hover svg, .cta:focus-visible svg{ transform: translateX(5px); }
.cta:active{ transform: translateY(-1px); }

/* ============ MEDIA COLUMN ============ */
.media{
  position: relative;
  margin: 0;
}
/* soft brand glow behind the photo */
.media::before{
  content:"";
  position:absolute;
  inset: 12% -6% -10% 8%;
  background: radial-gradient(60% 60% at 60% 50%, rgba(24,159,208,.28), transparent 70%);
  filter: blur(46px);
  opacity: 0;
  transition: opacity 1.2s var(--ease) .3s;
}
.is-in .media::before{ opacity: 1; }

.media__frame{
  position: relative;
  border-radius: 18px;
  overflow: hidden;
  aspect-ratio: 6 / 5;
  background: #dfeaf0;
  box-shadow: 0 30px 60px -34px rgba(16,60,80,.55);
  /* curtain wipe reveal */
  clip-path: inset(0 0 0 100%);
  transition: clip-path 1.1s var(--ease) .15s;
}
.is-in .media__frame{ clip-path: inset(0 0 0 0%); }

.media__frame img{
  width:100%; height:100%;
  object-fit: cover;
  display:block;
  transform: scale(1.14);
  transition: transform 1.4s var(--ease) .2s;
}
.is-in .media__frame img{ transform: scale(1); }
.media__frame:hover img{ transform: scale(1.05); transition-duration: .9s; }

/* teal light-sweep that runs across once revealed */
.media__frame::after{
  content:"";
  position:absolute; inset:0;
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.42) 50%, transparent 65%);
  transform: translateX(-120%);
  pointer-events:none;
}
.is-in .media__frame::after{ animation: sweep 1.5s var(--ease) 1s 1; }
@keyframes sweep{ to{ transform: translateX(120%);} }

/* gentle ambient float once everything has landed */
.is-in .media{ animation: float 7s ease-in-out 1.6s infinite; }
@keyframes float{
  0%,100%{ transform: translateY(0); }
  50%    { transform: translateY(-12px); }
}

/* ============ RESPONSIVE ============ */
@media (max-width: 980px){
  .why__inner{ grid-template-columns: 1fr; gap: 44px; }
  .media{ order: -1; }               /* photo leads on tablet + mobile */
  .headline{ max-width: 100%; }
  .copy{ max-width: 100%; }
  .is-in .media{ animation: none; }  /* no float on small screens */
}
@media (max-width: 520px){
  .media__frame{ aspect-ratio: 4 / 3; border-radius: 14px; }
  .cta{ width: 100%; justify-content: center; padding-inline: 24px; }
}

/* ============ ACCESSIBILITY ============ */
/* --c-blue-700, not --teal-deep. `--teal-deep` is not a token this stylesheet
   defines, so this declaration was invalid at computed-value time — and an
   invalid `outline` on a :focus-visible rule this late in the file does not
   fall back to the sensible one in the reset block, it resolves to `unset`.
   The site had no keyboard focus ring at all. */
:focus-visible{ outline: 3px solid var(--c-blue-700); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation: none !important;
    transition-duration: .01ms !important;
  }
  .headline .w > span{ transform:none; }
  .rise{ opacity:1; transform:none; }
  .media__frame{ clip-path: none; }
  .media__frame img{ transform:none; }
}
