/* Supplemental utilities.
   assets/styles.css is the original page's compiled Tailwind, which only
   contains the classes the original markup used. The 5-up step grid added
   for the "How EVO ICL works" section needs two utilities that weren't in
   that build. They follow Tailwind's own naming and definitions (lg = 64rem,
   matching the syntax used in styles.css), so if this page is ever rebuilt
   with Tailwind proper, these are generated automatically and this file
   can simply be dropped. */

@media (width>=64rem) {
  .lg\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .lg\:gap-6 { gap: calc(var(--spacing) * 6); }
}


/* ------------------------------------------------------------------
   Page width
   ------------------------------------------------------------------
   The original page pinned every section to Tailwind's max-w-6xl
   (72rem / 1152px), so on anything wider than a small laptop the
   content sat in a narrow centred column with large empty margins.

   These rules widen it in ONE place. The container is now fluid — it
   grows with the viewport and keeps a consistent gutter on each side —
   up to a maximum of --page-max.

   To tune the page width, change --page-max only:
     1280px  conservative
     1440px  current — a good balance
     1760px  wider still. This used to be discouraged because two ~565px
             sources would visibly soften past ~1500px, but the hero lens
             image has since been removed and the lens animation is now a
             veiled background where softness does not show. The widest
             remaining constraint is icl-anatomy.webp / the photos, all of
             which are 1920px, so going wider is now safe.
     none    edge-to-edge, limited only by --page-gutter
   ------------------------------------------------------------------ */

:root {
  --page-max: 1440px;   /* hard cap on section width           */
  --page-gutter: 3vw;   /* breathing room on each side         */
  --prose-max: 46rem;   /* measure for headings + intro copy   */
  --faq-max: 60rem;     /* the FAQ reading column              */

  /* The width every full-width section shares. Both .max-w-6xl and the
     stat band read this one value: they have to agree, or the figures in
     the band stop lining up with section content. */
  --container-w: clamp(72rem, calc(100vw - (2 * var(--page-gutter))), var(--page-max));
}

/* Section containers (header, hero, stat band, all sections, footer).
   clamp() rather than min(): the lower bound is the original 72rem, so the
   gutter can only ever ADD width, never take any away. Below ~1226px this
   behaves exactly as the original did; above it the container keeps growing
   with the viewport until it hits --page-max. */
.max-w-6xl {
  max-width: var(--container-w);
}

/* The FAQ column stays narrower than the rest, but scales with the page.
   Same idea: never below its original 48rem. */
.max-w-3xl {
  max-width: clamp(48rem, calc(100vw - (2 * var(--page-gutter))), var(--faq-max));
}

/* Headings and intro paragraphs keep a comfortable line length rather
   than stretching the full width of the wider container. */
.max-w-2xl { max-width: var(--prose-max); }

/* ------------------------------------------------------------------
   Image framing at the wider width
   ------------------------------------------------------------------
   The surgeon portraits use a fixed height with object-cover, so as the
   container got wider their crop got taller-and-thinner. This height
   restores the framing the original had at its narrower width. It is a
   plain Tailwind utility the purged build lacked.

   (There was a matching .lg:h-72 for the lens image that sat above the
   hero form; that image has since been removed, so the rule went with
   it.) */

@media (width>=64rem) {
  .lg\:h-80 { height: calc(var(--spacing) * 80); }  /* surgeon portraits */
}

/* ------------------------------------------------------------------
   Type scale
   ------------------------------------------------------------------
   Every text utility in styles.css is defined as
       font-size: var(--text-X); line-height: var(--tw-leading, var(--text-X--line-height))
   so overriding these tokens moves the whole page at once and keeps
   every role internally consistent — one size for headlines, one for
   subheadlines, one for body, and so on.

   Line-height tokens are deliberately left alone: they're unitless
   ratios, so they scale with the font size. Body copy additionally uses
   leading-relaxed (1.625) and headlines leading-tight (1.25), both of
   which still apply.

   The scale has been raised twice. Both steps are shown so the
   progression stays legible; only the last column is live.

                     stock    prev     now    role
   ------------------------------------------------------------------ */
:root {
  --text-xs:   0.875rem;   /* 12 -> 13 -> 14  eyebrows, form labels, fine print */
  --text-sm:   1.0625rem;  /* 14 -> 16 -> 17  body copy, buttons, list items    */
  --text-base: 1.125rem;   /* 16 -> 17 -> 18  form inputs (mobile)              */
  --text-lg:   1.375rem;   /* 18 -> 20 -> 22  lead paragraphs                   */
  --text-xl:   1.5rem;     /* 20 -> 22 -> 24  reviewer names                    */
  --text-2xl:  1.875rem;   /* 24 -> 28 -> 30  SUBHEADLINES (h3, card titles)    */
  --text-3xl:  2.25rem;    /* 30 -> 34 -> 36  stat figures, form card title     */
  --text-4xl:  2.8125rem;  /* 36 -> 42 -> 45  HEADLINES (mobile), step numbers  */
  --text-5xl:  3.75rem;    /* 48 -> 56 -> 60  HEADLINES (desktop)               */
  --text-6xl:  4.5rem;     /* 60 -> 68 -> 72  hero h1 (desktop)                 */
  --text-7xl:  5.375rem;   /* 72 -> 80 -> 86  (unused, kept in step)            */
}

/* The five step titles sit in a narrow 5-up grid. They used to reserve
   two lines (min-height: 2.67em) so that the cards whose titles wrapped
   stayed on the same baseline as the ones that didn't — but that reserved
   the second line on EVERY card, including the four that never wrap, and
   at the current subheadline size that reads as a hole under the title.

   Sizing them to fit instead. Re-measured after the grid moved inside the
   gradient card, whose side padding narrows every column. The longest
   title, "Tucked behind the iris", is the binding one:

     viewport   column   largest size that stays on one line
       1024      160px            21px
       1200      186px            24px
       1440      226px            29px
       1920      243px            32px  (capped at the 30px subheadline)

   The size tracks the viewport between those points, a shade under each
   limit, and tops out at the normal subheadline size — full size once
   there is room for it, stepped down only as far as one line requires.

   Scoped to the 5-up range: below 64rem the grid collapses to fewer,
   wider columns and the titles fit at full size already. */
@media (width >= 64rem) {
  #how-it-works ol h3 {
    font-size: clamp(1.25rem, calc(1.923vw + 0.3px), var(--text-2xl));
  }
}

/* ------------------------------------------------------------------
   Narrow-screen fixes that the larger type made necessary
   ------------------------------------------------------------------ */
@media (width < 40rem) {

  /* The submit buttons carry whitespace-nowrap, so their label sets a
     minimum width the panel cannot go below. At 14px "Submit my
     consultation request" only just fitted a 375px screen; at 16px it
     pushed the whole form panel past the viewport edge and the right
     border was clipped. Letting the label wrap (and the button grow)
     removes that floor. Shorter buttons are unaffected — they stay at
     their h-10 height. */
  form button[type="submit"] {
    white-space: normal;
    height: auto;
    min-height: 2.75rem;   /* 44px touch target, matching the floor below */
    padding-inline: calc(var(--spacing) * 4);
    padding-block: calc(var(--spacing) * 2);
  }

  /* The EVO ICL vs LASIK rows are a fixed 3-column grid, which leaves
     about 82px per cell on a phone — not enough for single words like
     "Reversibility" or "permanently" even at the original sizes, so they
     spilled out of their cells. Giving the label its own line and putting
     the two values side by side fixes it properly. The teal value is
     still EVO ICL, the dark one still LASIK.

     .cmp-row is on the table's header row as well as its data rows, so
     the EVO ICL / LASIK column headings collapse to the same two columns
     and stay lined up with the values beneath them. (This used to be
     scoped to `#what dl`; the table now lives in its own #compare
     section, and the class travels with it.) */
  .cmp-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmp-row > :first-child { grid-column: 1 / -1; }
}

/* The two consultation-location buttons sit in a fixed 2-up field grid.
   When the form is in the narrow half-width column (around 1024-1200px)
   that cell is only ~185px, and the pair needs more than that — they
   already spilled slightly at the original type size and more at the
   larger one. Letting the row wrap stacks them instead of overflowing. */
div:has(> [data-loc-toggle]) { flex-wrap: wrap; }

/* ------------------------------------------------------------------
   Section background animation
   ------------------------------------------------------------------
   The EVO ICL lens clip sits behind a section rather than in a card. It
   started in the hero and now backs #compare (EVO ICL vs. LASIK), which
   is why the class is .section-media and not .hero-media. To move it
   again: relocate the markup, and give the new section `relative
   overflow-hidden` plus `relative` on its inner container.

   It is layered so the brand colour still reads through:

     1. the section's own gradient (navy + teal/magenta radials)
     2. this clip, desaturated and blended so it contributes movement and
        light but takes its HUE from the gradient underneath — that's what
        keeps the page's colour visible
     3. a scrim on top that re-asserts the brand colour and keeps the
        type side dark enough to read
     4. the section content, which paints above all of it — it must be
        .relative, or the absolutely positioned clip covers it

   Opacity is deliberately low: the source is only 570x327, so at full
   strength across a wide section it would look soft. Held back like this
   it reads as texture and the upscaling is not visible.
   ------------------------------------------------------------------ */

.section-media {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.section-media video {
  /* Sized at its own 570x327 aspect and scaled up past the hero width, so
     the iris fills the panel and reads as a large soft form behind the
     copy rather than as a small picture of an eye. Adjust `width` to
     change how close in it sits. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max(112%, 52rem);
  height: auto;
  opacity: 0.75;
  /* screen keeps the lens and iris readable on the dark ground: light parts
     of the frame glow, the pupil stays as deep as the navy behind it. */
  mix-blend-mode: screen;
  filter: saturate(0.6) contrast(1.12);
  /* Fade the frame's edges so it melts into the gradient instead of
     ending on a hard rectangle. */
  -webkit-mask-image: radial-gradient(78% 78% at 50% 50%, #000 55%, transparent 100%);
          mask-image: radial-gradient(78% 78% at 50% 50%, #000 55%, transparent 100%);
}

/* Scrim: brand colour back over the top, strongest on the left where the
   headline sits, lifting toward the right so the animation stays visible. */
.section-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(oklch(15% 0.018 240 / 0.6), oklch(15% 0.018 240 / 0.6)),
    radial-gradient(80% 90% at 100% 0,
      oklab(68% -0.0156283 -0.0886327 / 0.42), #0000 65%),
    radial-gradient(80% 90% at 0 100%,
      oklab(62% 0.155639 0.10898 / 0.48), #0000 62%);
}

/* On phones the headline runs the full width, so the animation needs to
   sit further back for the text to stay legible. */
@media (width < 40rem) {
  .section-media video { opacity: 0.42; width: max(118%, 22rem); }
  .section-media::after {
    background:
      linear-gradient(180deg,
        oklch(15% 0.018 240 / 0.9) 0%,
        oklch(15% 0.018 240 / 0.8) 55%,
        oklch(15% 0.018 240 / 0.6) 100%),
      radial-gradient(80% 90% at 0 100%,
        oklab(62% 0.155639 0.10898 / 0.4), #0000 62%);
  }
}

/* Reduced motion is handled in app.js: the video is paused so its poster
   frame stays put, rather than being animated. */

/* ------------------------------------------------------------------
   Hero height
   ------------------------------------------------------------------
   The hero's vertical size comes from the padding on its content grid
   (py-12 / lg:py-16 in the markup). The stylesheet is prebuilt, so it
   only ships the spacing steps the page already uses — the next one up
   in markup would be lg:py-28, a much bigger jump than wanted. Setting
   the padding here keeps the increase small and gives the lens
   animation a little more room behind the copy.

   This used to key off `.hero-media + div` — the element right after the
   video layer. The lens animation has since moved to the #compare section,
   so the hero carries an id and its two children are addressed directly:
   first child is the content grid, last child is the stat band.
   ------------------------------------------------------------------ */
#hero > div:first-child { padding-block: 3.75rem; }

@media (width >= 64rem) {
  #hero > div:first-child { padding-block: 5.25rem; }
}

/* Extra breathing room above and below the headline column. It is the
   taller of the two columns, so this drives the hero's height; the
   consult card stays vertically centred against it (lg:items-center).
   Scoped to the two-column layout — below 64rem the columns stack and
   this would only push the form further down the page. */
@media (width >= 64rem) {
  #hero > div:first-child > div:first-child { padding-block: 2.5rem; }
}

/* ------------------------------------------------------------------
   Section background rhythm
   ------------------------------------------------------------------
   The page ran .brand-gradient-soft on 9 of its 14 sections, in runs of
   two and one run of three, so the same radial gradient kept repeating
   back to back and the seams between those sections disappeared.

   There are now four treatments that alternate, and no two adjacent
   sections share one:

     .brand-gradient        dark, full strength   hero + closing CTA
     .brand-gradient-soft   dark, quiet           what / guru / drs / videos
     .light-band            light grey  #F5F5F5   reasons / procedure /
                                                  reviews / second CTA
     .light-band-blue       light blue  #B9C8D7   how it works / candidacy /
                                                  first CTA / FAQ

   .light-band already worked by swapping the palette tokens for the
   section rather than by painting a background, so everything inside it
   — panels, borders, muted copy, the teal — recolours itself. The blue
   band is the same idea on a darker ground, so its foreground, border
   and primary tokens are pulled down to hold contrast against it. All
   of the text pairings below are at or above WCAG AA (4.5:1).
   ------------------------------------------------------------------ */

/* Match the grey band to the exact colour asked for; it was a fractionally
   blue-tinted near-white before.

   --primary is deepened for the same reason it was on the blue band: the
   stock teal is a light tint built to sit on the dark ground, and on this
   one it measured 3.68:1 — under AA. That was tolerable while the teal
   was only accents, but the EVO ICL vs LASIK table now lives here and its
   entire EVO ICL column is teal, at 17px. At 50% lightness it reads 5.0:1
   on the band, 5.3:1 on a card and 5.3:1 for white text on a teal button. */
.light-band {
  --background: #F5F5F5;
  --primary: oklch(50% 0.11 200);
}

/* --gradient-panel is declared once on :root, so its var() references to
   --primary/--card resolve there — against the DARK palette — and stay
   dark even inside a light band. No light section used to contain a
   .panel, so this never showed; three of them do now (candidacy and both
   CTA strips). Re-declaring it on each band re-resolves it against that
   band's own tokens. The two-step @supports shape mirrors the original
   declaration in styles.css. */
.light-band,
.light-band-blue {
  --gradient-panel: linear-gradient(160deg, var(--primary), var(--card) 60%);
}
@supports (color: color-mix(in lab, red, red)) {
  .light-band,
  .light-band-blue {
    --gradient-panel: linear-gradient(160deg,
      color-mix(in oklab, var(--primary) 8%, var(--card)), var(--card) 60%);
  }
}

.light-band-blue {
  --background: #B9C8D7;
  --foreground: oklch(22% 0.02 240);
  --card: oklch(98% 0.004 240);
  --card-foreground: oklch(22% 0.02 240);
  --muted: oklch(83% 0.022 235);
  --muted-foreground: oklch(36% 0.022 240);
  --border: oklch(67% 0.028 235);
  --input: oklch(67% 0.028 235);
  --primary: oklch(40% 0.115 205);   /* deepened: the stock teal reads 3.9:1 on this ground */
  --primary-foreground: oklch(99% 0.003 240);
  --secondary: oklch(83% 0.022 235);
  --secondary-foreground: oklch(22% 0.02 240);
  --accent: oklch(60% 0.11 85);
  --accent-foreground: oklch(99% 0.003 240);
  background-color: var(--background);
  color: var(--foreground);
}

/* ------------------------------------------------------------------
   Header
   ------------------------------------------------------------------
   Light (#F5F5F5, the same ground as the grey band), a little taller, and
   lifted off the page with a soft shadow instead of the hairline border it
   used to carry (border + shadow together read as a double rule). The
   shadow matters more now than when this was pure white: the header and a
   grey band beneath it are the same colour, so the shadow is the only
   thing separating them.

   Going light means the header's contents have to invert, so it swaps
   the palette tokens the same way .light-band does rather than just
   painting a background: the phone number, the Book Consult button and
   the focus ring all recolour themselves from these.

   The wordmark is the one thing tokens cannot fix — images/msh-logo-white.png
   is white artwork on transparency and would have vanished. The markup
   now points at images/msh-logo-dark.webp, generated from that same file
   with its alpha channel untouched and only the RGB replaced, so the
   letterforms and their antialiasing are identical.
   ------------------------------------------------------------------ */
.site-header {
  --background: #F5F5F5;
  --foreground: oklch(22% 0.02 240);
  --muted-foreground: oklch(45% 0.02 240);
  --border: oklch(90% 0.006 240);
  --input: oklch(90% 0.006 240);
  --primary: oklch(45% 0.115 205);      /* deepened: the stock teal is a
                                           light tint built for dark ground */
  --primary-foreground: oklch(99% 0.003 240);
  --ring: oklch(45% 0.115 205);

  background-color: var(--background);
  color: var(--foreground);

  /* Two stacked layers: a tight contact shadow that draws the edge, and a
     wider, softer one for depth. Kept low-opacity so it separates the
     header without announcing itself. */
  box-shadow:
    0 1px 2px oklch(22% 0.02 240 / 0.06),
    0 6px 20px -6px oklch(22% 0.02 240 / 0.14);
}

/* Slightly taller. The compiled stylesheet only ships the padding steps the
   original markup used (py-3 / sm:py-4), and the next one up is a bigger jump
   than wanted, so the height is set here instead. */
/* Height is padding x2 plus the tallest control. On mobile that control is
   a 44px touch target, on desktop the 32px logo/button row. Raised from
   0.75rem/1.25rem so the bar reads taller without the logo growing:
     mobile   1rem   x2 + 44 = 76px   (was 68px)
     >= 40rem 1.75rem x2 + 32 = 88px  (was 72px)
   Kept as one rule per breakpoint — this previously carried two identical
   40rem blocks, where only the later one applied. */
.site-header > div { padding-block: 1rem; }

@media (width >= 40rem) {
  .site-header > div { padding-block: 1.75rem; }
}

/* ------------------------------------------------------------------
   Stat band: full-bleed bar, container-aligned figures
   ------------------------------------------------------------------
   The band carried .max-w-6xl like every other container, so the whole
   thing stopped at the 1440px cap and left the hero gradient showing at
   both edges.

   Only the BAR should reach the edges — the four figures stay lined up
   with the rest of the page. So the grid goes full width, and the two
   outer columns are widened by exactly one gutter each and given that
   much extra padding on their outer side. The bar's fill is the cells'
   own background, so widening the end cells extends the colour to the
   screen edge with no seam, and the divider hairlines stay at the
   container's quarter points where the figures are.

   The cell width subtracts the 3px of gap-px so the six tracks add up to
   exactly 100vw and the figures land on the same x positions they had
   when the grid was capped. Above the cap the gutter is a real number;
   below it --stat-container exceeds the viewport, max() floors the
   gutter at 0 and this collapses back to the plain 4-up grid.

   Height is untouched — only width and the two outer paddings change.
   ------------------------------------------------------------------ */
@media (width >= 64rem) {
  #hero > div:last-child {
    /* min(..., 100vw) matters: the shared container clamp has a 72rem FLOOR,
       so between 1024px and ~1226px it reports a width wider than the screen.
       Without the min() the four cells summed to 1152px inside a 1024px band
       and the hero's overflow-hidden quietly clipped the fourth stat. */
    --stat-container: min(var(--container-w), 100vw);
    --stat-gutter: max(0px, calc((100vw - var(--stat-container)) / 2));
    --stat-cell: calc((var(--stat-container) - 3px) / 4);

    max-width: none;
    grid-template-columns:
      calc(var(--stat-gutter) + var(--stat-cell))
      var(--stat-cell)
      var(--stat-cell)
      calc(var(--stat-gutter) + var(--stat-cell));
  }

  /* px-6 (1.5rem) on the cells, plus the gutter on the two outer edges. */
  #hero > div:last-child > :first-child { padding-left: calc(var(--stat-gutter) + 1.5rem); }
  #hero > div:last-child > :last-child { padding-right: calc(var(--stat-gutter) + 1.5rem); }
}

/* ------------------------------------------------------------------
   Six-reasons checkmarks
   ------------------------------------------------------------------
   Each reason card leads with a tick, matching the ✓ convention already
   used in the hero list and the candidacy panel.

   The title is a flex row aligned on the BASELINE, not the box top: the
   tick is set smaller than the title, so top-aligning the two boxes left
   it floating about 10px high. Baseline alignment uses the title's first
   line, so a title that wraps to two lines still keeps its tick beside
   line one. align-items is set here because the prebuilt stylesheet has
   no items-baseline utility.

   The tick is sized in em so it tracks the title if the type scale moves
   again; flex:none stops it being squeezed in a narrow column.
   ------------------------------------------------------------------ */
.reason-title { align-items: baseline; }

.reason-check {
  flex: none;
  font-size: 0.72em;
  line-height: 1;
}


/* ------------------------------------------------------------------
   "What it is": image spans headline to last baseline
   ------------------------------------------------------------------
   The right column is a grid item, so it already stretches to the row's
   height — it was the image inside it that kept its own square aspect and
   left a gap underneath. height:100% hands the image the full column, and
   the object-cover it already carries crops the overflow.

   The crop is horizontal and safe: the eye occupies the middle 852px of
   the 1280px square, leaving 16.7% spare on each side, so the artwork
   survives down to a 1.5:1 height-to-width ratio before anything of the
   eye is lost. The text column would have to get far taller than the
   image is wide for that to happen.

   Scoped to the two-column layout; below 64rem the columns stack and the
   image stays square.
   ------------------------------------------------------------------ */
@media (width >= 76rem) {
  /* The image spans from the section HEADLINE down to the last line of the
     body copy: top edge on the cap of "A tiny lens…", bottom edge on the
     final paragraph's baseline.

     Both ends are trimmed rather than set to a box edge, because a line box
     is taller than the letters inside it — aligning to the box would leave
     the image visibly overhanging the text at both ends. The bottom ratio
     is measured against this column's type (22px at leading-relaxed 1.625):

       bottom  0.244em   half-leading + descender space, down to the baseline

     Multiplying the type token keeps it correct if the scale moves, since
     both the size and the leading ratio travel with that token.
     (CSS text-box-trim will do this natively once support is broad.) */
  #what .grid > div:last-child {
    position: relative;
    --cap-trim: calc(0.646 * var(--text-lg));   /* to the first cap     */
    --base-trim: calc(0.244 * var(--text-lg));  /* to the last baseline */

    /* How far the image reaches ABOVE its grid column to meet the section
       headline. The grid starts below the heading block, so this is the
       mt-10 between them plus the headline's line box, less the same
       leading-to-cap gap as above — landing the edge on the cap of
       "A tiny lens…" rather than the top of its line box:

         2.5rem     the mt-10 between heading block and grid
         1.25em     the h2 line box (leading-tight)
       - 0.1933em   its leading + em-box-to-cap gap
       = 2.5rem + 1.0567em of --text-5xl  (103.4px at the current scale)

       The headline is one line at every width this rule covers, and its
       text ends ~46px short of the image column, so the image clears it. */
    --lift: calc(2.5rem + (1.0567 * var(--text-5xl)));
  }

  /* Absolute, so the image contributes NOTHING to the grid row's height.
     In flow it sized the row itself once the column got wide enough for a
     square to out-measure the text (past ~1500px), and the row then grew
     to the image rather than the text — so the two only appeared to match
     because both were reporting the same stretched row. Out of flow, the
     row is the text's height alone, and the image is then positioned
     against it — reaching up past the column to the headline.

     Height is set explicitly rather than via a bottom inset: for a replaced
     element with an intrinsic aspect ratio, top+bottom insets do not stretch
     it — the aspect wins and `bottom` is ignored, which left it square. */
  #what .grid > div:last-child > img {
    position: absolute;
    top: var(--cap-trim);
    left: 0;
    width: 100%;
    height: calc(100% - var(--cap-trim) - var(--base-trim));
  }
}

/* ------------------------------------------------------------------
   …and above 88rem it reaches further up, to the headline
   ------------------------------------------------------------------
   The top edge rises from the first paragraph's cap to the cap of
   "A tiny lens…", while the bottom stays on the last baseline — --lift is
   added to the height exactly as it is subtracted from the top, so the
   image only grows upward.

   88rem is where there is actually room for it. Two things fail below it,
   both measured:

     viewport   headline-to-image gap   image ratio
       1216           -60px               1.68
       1280           -33px               1.54
       1320           -14px               1.42
       1360            +6px               1.38
       1400           +26px               1.34
       1440           +46px               1.24

   Negative gap means the headline runs UNDER the image column, and since
   the image is absolutely positioned it paints over the text — at 1216 it
   swallowed the word "eye". The ratio column is the crop: past 1.5 the
   artwork loses the sides of the eye (the eye occupies the middle 852px
   of the 1280px square). Both clear comfortably by 1408px.

   Between 76rem and 88rem the rule above still applies, so the image runs
   from the first paragraph's cap instead — no overlap, no over-crop.
   ------------------------------------------------------------------ */
@media (width >= 88rem) {
  #what .grid > div:last-child > img {
    top: calc(-1 * var(--lift));
    height: calc(100% - var(--base-trim) + var(--lift));
  }
}

/* 64-76rem is the exception. The column is at its narrowest here while the
   text column is at its tallest, which drives the box past 1.5:1 — at
   1024px it reaches 1.85:1 and takes visible bites out of both sides of
   the eye. 1.5 is a hard ceiling set by the artwork, not by the crop:
   under object-cover the visible width is always sourceHeight / ratio, so
   re-cutting from the wider 1920px original gives exactly the same limit.
   Through this band the image keeps its square shape and centres instead. */
@media (width >= 64rem) and (width < 76rem) {
  #what .grid > div:last-child { align-self: center; }
}

/* ------------------------------------------------------------------
   Primary buttons: one appearance on every band
   ------------------------------------------------------------------
   The button recipe paints itself from --primary / --primary-foreground,
   and each band redefines those, so the same CTA came out two different
   ways: deep teal with a white label on the light bands and in the header,
   bright teal with a dark label on the dark ones. Same button, same job,
   two looks.

   Deep teal is the one that survives everywhere. Measured as the surface
   against the band it sits on (WCAG 1.4.11 wants 3:1 for a control's
   edge), and as the label against the button:

                      label   on grey   on blue   on dark
     deep  + white     5.32     5.02      3.21      3.42   <- all pass
     bright + ink      9.55     1.74      1.11      9.88   <- light bands fail

   Bright teal is only legible as a shape on the dark ground; on #F5F5F5
   and the blue it dissolves into the band. So the fill is pinned here.

   The tokens are redefined ON the button rather than hard-coding the
   colours, because .bg-primary and hover:bg-primary/90 both resolve
   var(--primary) — pinning the token keeps the rest and hover in step.

   Scoped to the full button recipe (bg-primary AND text-primary-foreground
   on an a/button), which is exactly the 8 CTAs. The other 13 .bg-primary
   elements — form progress segments, the round play buttons over video
   thumbnails — keep their band colour and are untouched.
   ------------------------------------------------------------------ */
a.bg-primary.text-primary-foreground,
button.bg-primary.text-primary-foreground {
  --primary: oklch(50% 0.11 200);
  --primary-foreground: oklch(99% 0.003 240);
}

/* ------------------------------------------------------------------
   Closing section: form aligned to the headline, not the eyebrow
   ------------------------------------------------------------------
   Both columns start at the same y, so the form's top edge lined up with
   the "Next step" eyebrow while the headline began well below it. Dropping
   the form by the height of everything above that headline puts its edge
   on the first line of "Book your EVO ICL evaluation":

     1.5rem    the eyebrow's own line box (text-xs)
     0.75rem   the mt-3 between eyebrow and h2
     0.183em   h2 leading + the gap from em-box top to cap height, so the
               panel edge meets the CAP of the letters rather than the
               invisible top of their line box

   Tied to --text-5xl (the h2's size above 64rem) so it tracks the type
   scale. Scoped to the two-column layout — below 64rem the columns stack
   and the form should stay flush.

   The child combinator is required: the form itself contains grids, so a
   descendant selector would match those too.
   ------------------------------------------------------------------ */
/* The form column used to be dropped by a calc()'d offset so the top edge
   of a bare, heading-less iframe met the cap height of the lead paragraph.
   It now holds a titled card ("Schedule your EVO ICL Consultation") that
   states its own purpose, so the offset left it hanging below the left
   column and past its bottom. Both columns start at the row top again. */

/* ------------------------------------------------------------------
   "Two specialists, one operating standard" on one line
   ------------------------------------------------------------------
   The heading needs 738px at its 60px size; the block it sits in is
   .max-w-2xl, which this file caps at --prose-max (46rem / 736px). It was
   missing a single line by 2px.

   That 46rem measure exists to keep headings and intro copy at a readable
   line length, and it earns its keep in sections where a paragraph sits
   under the heading. Here the block holds only the eyebrow and the h2, so
   widening it costs nothing — and it keeps the h2 at the same size as
   every other section heading rather than shrinking this one to fit.

   56rem leaves ~150px of headroom, so the line survives a modest type
   bump. The section container still constrains it on narrower screens,
   where the heading wraps as before.
   ------------------------------------------------------------------ */
#surgeons .max-w-2xl { max-width: 56rem; }

/* ------------------------------------------------------------------
   Candidacy: heading + stacked cards beside one tall photo
   ------------------------------------------------------------------
   Two columns. The left one holds the heading AND the two stacked lists;
   the photo has the right one to itself and runs the full height of that
   column — top level with the headline, bottom level with the second card.

   The heading sits INSIDE the left column rather than above the grid, and
   that is the whole point of the structure. Lifting the photo up past a
   full-width heading block would have put it over the lead paragraph:
   measured at 1440px, the headline cleared the photo's edge by 98px but
   the lead's second line ran 41px underneath it, and an image stacks above
   text. Confining the heading to the column makes the overlap impossible
   rather than something to tune per breakpoint.

   The photo is then nudged down to start at the headline's cap rather than
   at the eyebrow above it, and the same amount comes off its height so the
   bottom stays put:

     1.5rem     the eyebrow's line box (text-xs)
     0.75rem    the mt-3 between eyebrow and h2
     0.1933em   the h2's leading + em-box-to-cap gap

   Below 64rem the grid is one column: heading, cards, then photo, and the
   offset is dropped so the photo sits normally in the stack.
   ------------------------------------------------------------------ */
@media (width >= 64rem) {
  .candidacy-grid {
    grid-template-columns: 1fr 0.9fr;
    align-items: start;
  }

  .candidacy-grid > img {
    --cand-cap: calc(1.5rem + 0.75rem + (0.1933 * var(--text-5xl)));
    align-self: stretch;
    margin-top: var(--cand-cap);
    height: calc(100% - var(--cand-cap));
  }
}


/* ------------------------------------------------------------------
   Gradient card
   ------------------------------------------------------------------
   Lets a light band hold a piece of dark brand artwork: the section keeps
   its light blue ground, and the content sits on a card carrying the same
   .brand-gradient used by the hero and the closing section. The gradient
   itself comes from that class in the markup, so it stays literally the
   same artwork — this class supplies the shape and, more importantly, the
   palette.

   The palette reset is the part that matters. .light-band-blue swaps the
   colour tokens for the whole section, so everything inside it is painted
   for a light ground. Dropping dark artwork in the middle of that would
   leave dark text on a dark card. These are the :root values restored, so
   the content inside the card renders exactly as it does on the page's
   dark sections.

   --gradient-panel is restored too: the light bands redefine it (see the
   panel fix above), so any .panel placed inside this card would otherwise
   inherit the light version.
   ------------------------------------------------------------------ */
.gradient-card {
  --background: oklch(16% 0.018 240);
  --foreground: oklch(96% 0.005 240);
  --card: oklch(20% 0.02 240);
  --card-foreground: oklch(96% 0.005 240);
  --muted: oklch(24% 0.02 240);
  --muted-foreground: oklch(71% 0.02 240);
  --border: oklch(30% 0.02 240);
  --input: oklch(30% 0.02 240);
  --primary: oklch(78% 0.13 195);
  --primary-foreground: oklch(17% 0.03 220);
  --secondary: oklch(26% 0.025 240);
  --secondary-foreground: oklch(96% 0.005 240);
  --accent: oklch(86% 0.1 90);
  --accent-foreground: oklch(20% 0.03 60);
  --ring: oklch(78% 0.13 195);

  --gradient-panel: linear-gradient(160deg, var(--primary), var(--card) 60%);

  color: var(--foreground);
  border-radius: 1.5rem;
  /* Lifting a DARK card off a LIGHT ground is not really a shadow problem.
     A single soft shadow is dark and the card is dark, so the shadow only
     reads against the band and the card's own edge stays undefined. Three
     things do the work instead:

       1. a tight contact shadow — the hard edge that says "this object
          ends here", the part a big soft blur cannot give you
       2. a mid shadow for the actual lift
       3. a wide, low ambient one to seat it on the band

     ...plus a 1px inset highlight along the TOP edge. That is the rim
     light: with the light implied from above, the card's upper edge should
     catch it, and that single line is what stops the top edge dissolving
     into the blue. Without it the card reads as a hole cut in the band
     rather than a panel resting on it.

     The shadows are tinted with the band's own hue (240) rather than
     neutral black — a black shadow on a blue-grey ground goes muddy. */
  box-shadow:
    0 1px 2px oklch(30% 0.04 240 / 0.30),
    0 10px 20px -8px oklch(30% 0.04 240 / 0.28),
    0 40px 70px -28px oklch(25% 0.05 240 / 0.45),
    inset 0 1px 0 oklch(100% 0 0 / 0.07);

  /* Vertical padding is generous — that is what makes it read as a card.
     Horizontal padding is the floor only; above 64rem it is recomputed
     further down so the content inside lines up with the other sections.
     It is kept lean here because this card holds the 5-up step grid, and
     every pixel of side padding comes straight out of five already-narrow
     columns. */
  padding-block: 2.5rem;
  padding-inline: 1.5rem;
}

@supports (color: color-mix(in lab, red, red)) {
  .gradient-card {
    --gradient-panel: linear-gradient(160deg,
      color-mix(in oklab, var(--primary) 8%, var(--card)), var(--card) 60%);
  }
}

@media (width >= 40rem) {
  .gradient-card { padding-block: 3rem; padding-inline: 2rem; }
}
@media (width >= 64rem) {
  .gradient-card { padding-block: 4rem; padding-inline: 2.5rem; }
}

/* ------------------------------------------------------------------
   Gradient card: break out to match the band's own spacing
   ------------------------------------------------------------------
   The card sat inside .max-w-6xl, so on a wide screen its side margins
   were whatever was left over once the page container hit its 1440px cap
   — far more than the 7rem (lg:py-28) above and below it. This pulls the
   card back out until the light blue showing at its sides matches the
   light blue showing at its top.

   Only ever widens, never narrows. max(0px, …) means the negative margin
   is zero whenever the container is already narrower than 100vw - 14rem,
   which is every viewport up to about 1640px — so nothing changes at
   1440px and below, and the five step columns keep the widths the type
   was tuned against.

   `100%` in the calc is the container's inner width (margin percentages
   resolve against the containing block), so the sum is: viewport, less
   the two 7rem margins we want, less the width the card already has,
   halved for each side.

   Scrollbar-safe: where 100vw runs wider than the content area, the card
   grows by that difference and its margin lands a few px under 7rem, but
   it cannot reach the edge — the margin would have to fall below zero,
   which needs a scrollbar wider than 14rem.
   ------------------------------------------------------------------ */
@media (width >= 64rem) {
  .gradient-card {
    /* the pull-out, and the same amount put back as padding, so the card
       keeps its wide frame while the content inside it lines up with every
       other section's content. box-sizing is border-box, so growing the
       padding does not change the card's width. */
    margin-inline: calc(-1 * max(0px, (100vw - 14rem - 100%) / 2));
    padding-inline: max(2.5rem, (100vw - 14rem - 100%) / 2);
  }
}

/* ------------------------------------------------------------------
   Survey embed: never let it end up invisible
   ------------------------------------------------------------------
   form_embed.js parks the iframe off-screen on load —

     opacity:0; visibility:hidden; pointer-events:none;
     left:-9999px; position:absolute;

   — and only reveals it once the form inside posts a resize handshake
   back to the parent. Its gate is literally `resizedIframes[id]`, and
   until that flag is set the form stays hidden.

   Observed on this page: the form loads and renders its fields perfectly,
   but posts no message at all (zero received on the parent), so the flag
   is never set and the reveal never runs. The visitor sees an empty
   column where the form should be.

   These four properties undo the parking. Height is deliberately NOT
   overridden, so the script's auto-resize still applies if the handshake
   does come through; without it the iframe keeps the data-height it
   ships with. !important is required because the script sets the hide
   inline, and inline styles beat a normal rule.
   ------------------------------------------------------------------ */
.survey-slot iframe {
  position: static !important;
  left: auto !important;
  opacity: 1 !important;
  visibility: visible !important;
  pointer-events: auto !important;

  /* The old embed shipped a data-height, so it had a fallback size when
     the handshake failed. The survey ships none — it would sit at the
     iframe default of 150px and cut itself off. This floors it just above
     the survey's own content height (521px measured), so a failed
     handshake still shows the whole first page. min-height, not height,
     so the script's resize still wins when it does come through. */
  min-height: 33rem;
}

/* ==================================================================
   Mobile
   ================================================================== */

/* ------------------------------------------------------------------
   Touch targets
   ------------------------------------------------------------------
   Measured at 375px, 14 controls came in under the 44px WCAG 2.5.5
   target: the header's Book CTA at 32px, the hero form's four inputs at
   36px, and the primary buttons at 40px. The type inside them is already
   fine — it is the box that is short — so these set a floor rather than
   restyling anything.

   Phone numbers written into a sentence ("Prefer to call? …") are left
   alone: WCAG exempts links inline in a block of text, and padding them
   out would break the line box around them. The standalone links — the
   reviews link and the two in the footer — do get the floor.
   ------------------------------------------------------------------ */
@media (width < 40rem) {
  header a.h-8,
  .h-9:is(input, select, textarea),
  a.h-10, button.h-10 {
    height: 2.75rem;
    min-height: 2.75rem;
  }

  /* standalone links: grow the box, keep the text where it is */
  header > div > a,
  header a[href^="tel:"],
  #reviews a[href*="reviews"],
  footer a[href^="tel:"],
  footer a[href*="schedule."] {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
  }
}

/* ------------------------------------------------------------------
   Vertical rhythm
   ------------------------------------------------------------------
   Sections carry py-20 (80px top and bottom) at every width below the
   lg breakpoint. That is a desktop-sized gap on a phone: across the 12
   sections that use it, it alone accounts for ~1,900px of the page.
   3.5rem keeps the sections clearly separated while giving back roughly
   600px of scrolling.
   ------------------------------------------------------------------ */
/* Lead each paired section with its picture while the grid is stacked.
   These grids are authored text-first because the desktop layout puts the
   image in the right column; stacked, that buries the image under a long
   block of copy. The columns split at 64rem, so this covers the whole
   stacked range — phone and tablet — and stops exactly where the
   side-by-side layout begins. Ordering is visual only: the DOM order is
   unchanged, so reading order for screen readers stays as authored. */
@media (width < 64rem) {
  #what .grid > div:last-child,
  .candidacy-grid > img { order: -1; }

  /* The FAQ photo is decorative and pairs with the accordion rather than
     with body copy. Stacked, it either pushes all six questions down the
     page or trails below them, so it is dropped on the narrow layout.
     display:none also keeps the lazy image from being fetched at all. */
  #faq .grid > img { display: none; }

  /* "What it is" runs to a single short paragraph on the narrow layout:
     the step-by-step section follows immediately after, so the detail is
     not lost, and a phone reader gets the gist in one screen. */
  #what .grid > div:first-child > p:not([data-what="short"]) { display: none; }
}

/* ...and the long version takes over once there is room for it. */
@media (width >= 64rem) {
  #what p[data-what="short"] { display: none; }
}

@media (width < 40rem) {
  main > section > div.mx-auto { padding-block: 3.5rem; }

  /* ...except the hero's stat band, which is a bar, not a section body.
     Its cells carry their own py-7, so the blanket 3.5rem added 56px of
     the band's own translucent fill above and below the figures — reading
     as two grey stripes wrapping the bar on a phone. */
  #hero > div:last-child { padding-block: 0; }

}

/* ------------------------------------------------------------------
   Step images
   ------------------------------------------------------------------
   "How EVO ICL works" is the longest section on a phone (3,543px). Each
   of the five steps leads with a full-width SQUARE image — 277px tall at
   375px wide — before any text. A 4:3 crop keeps the eye centred and the
   detail legible while returning ~70px per step.
   ------------------------------------------------------------------ */
@media (width < 40rem) {
  #how-it-works ol img { aspect-ratio: 4 / 3; }
}

/* ------------------------------------------------------------------
   Mobile type scale
   ------------------------------------------------------------------
   The scale in :root was tuned at desktop widths and then inherited,
   unchanged, by phones. At 375px that lands headlines at 45px, leads at
   22px and subheadlines at 30px — each one individually defensible, but
   stacked in a single-column layout they all shout at once and nothing
   reads as secondary. Two specific problems:

     - h2 carries `text-4xl sm:text-5xl`, so below 40rem every section
       headline is 45px — about 11 characters per line.
     - h1 carries `text-4xl sm:text-5xl lg:text-6xl`, which means on a
       phone the page title and every section headline are the SAME
       size. The document has no top level.

   Re-pitching the whole scale for phones. Ratios between adjacent steps
   are kept close to the desktop ones so the relationships hold; what
   changes is the overall pitch and the headline-to-body gap, which drops
   from 2.6x to 2.0x. --text-base stays at 16px: iOS zooms a focused
   input below that.

                       desktop   mobile
   ------------------------------------------------------------------ */
@media (width < 40rem) {
  :root {
    --text-xs:   0.8125rem;  /* 14 -> 13  eyebrows, form labels, fine print */
    --text-sm:   1rem;       /* 17 -> 16  body copy, buttons, list items    */
    --text-base: 1rem;       /* 18 -> 16  form inputs (iOS zoom floor)      */
    --text-lg:   1.1875rem;  /* 22 -> 19  lead paragraphs                   */
    --text-xl:   1.25rem;    /* 24 -> 20  reviewer names                    */
    --text-2xl:  1.4375rem;  /* 30 -> 23  SUBHEADLINES (h3, card titles)    */
    --text-3xl:  1.75rem;    /* 36 -> 28  stat figures, form card title     */
    --text-4xl:  2rem;       /* 45 -> 32  HEADLINES, step numbers           */
    --text-5xl:  2.25rem;    /* 60 -> 36  (sm: and up; kept in proportion)  */
    --text-6xl:  2.625rem;   /* 72 -> 42                                    */
    --text-7xl:  3rem;       /* 86 -> 48  (unused)                          */
  }

  /* Give the page one clear top level again. h1 shares text-4xl with
     every h2 on mobile; lifting it a step restores the hierarchy
     without reaching back up to the old 45px. */
  main h1 { font-size: 2.375rem; }
}

/* ------------------------------------------------------------------
   Tablet type scale (the 40rem step)
   ------------------------------------------------------------------
   Dropping the phone scale leaves a cliff at the sm breakpoint: at
   639px an h2 is 32px, at 640px it is 60px, because `sm:text-5xl`
   takes over at exactly the width where the mobile override stops.
   A 28px jump on one pixel of resize is visible on a tablet rotate.

   Sizing the 40–64rem band between the two so the scale steps twice
   instead of once. The band stops below 64rem, so every desktop
   layout calculation that reads --text-5xl or --text-lg (the #what
   image lift, the #visit form offset, the candidacy photo) is
   untouched.

                       mobile  tablet  desktop
   ------------------------------------------------------------------ */
@media (width >= 40rem) and (width < 64rem) {
  :root {
    --text-sm:   1rem;       /* 16 -> 16 -> 17  body copy                */
    --text-base: 1.0625rem;  /* 16 -> 17 -> 18  form inputs              */
    --text-lg:   1.25rem;    /* 19 -> 20 -> 22  lead paragraphs          */
    --text-xl:   1.375rem;   /* 20 -> 22 -> 24  reviewer names           */
    --text-2xl:  1.625rem;   /* 23 -> 26 -> 30  SUBHEADLINES             */
    --text-3xl:  2rem;       /* 28 -> 32 -> 36  stat figures             */
    --text-4xl:  2.375rem;   /* 32 -> 38 -> 45  step numbers             */
    --text-5xl:  2.875rem;   /* 36 -> 46 -> 60  HEADLINES (sm: and up)   */
    --text-6xl:  3.25rem;    /* 42 -> 52 -> 72  hero h1 (lg: and up)     */
  }
}

/* ---------------------------------------------------------------
   GHL survey embed
   The survey draws its own padding inside the iframe. That padding is
   cross-origin, so the frame is pulled out by the same amount instead:
   the survey's fields then land on the card's text column, level with
   the heading above it. The card clips the overhang, which is blank.

   20px is measured FROM THE EMBED, not from the survey loaded on its own
   — it uses roughly twice as much padding standalone (38px), so measuring
   it in a browser tab and reusing the number overshoots and pushes the
   fields past the heading. Check it in the card.

   The value is also per-survey: the LASIK/RLE survey differs again. If
   the fields sit inside the heading, raise it; if they spill past it,
   lower it.
   --------------------------------------------------------------- */
.survey-slot{
  --survey-pad: 20px;          /* its side padding, measured in the card */
  --survey-pad-bottom: 50px;   /* blank space it leaves below the button */
  margin-inline: calc(var(--survey-pad) * -1);
  margin-bottom: calc(var(--survey-pad-bottom) * -1);
}
.survey-slot iframe{ width:100%; border:0; display:block; }

/* ------------------------------------------------------------------
   "Why us" — ICL Guru Pro
   ------------------------------------------------------------------
   The artwork is square, so in an even 2-up its height tracks the COLUMN
   WIDTH: it grows as the viewport widens while the prose column shrinks
   (less wrapping). The two crossed over around 1440px and past that the
   image hung below the card with nothing to align to.

   Two fixes, both scoped to the two-column layout:

   1. A fixed 34rem image column. The image stops scaling with the
      viewport, and the prose column absorbs the extra width instead — so
      there is no dead space between them, only the grid gap.

   2. The image is taken out of flow (absolute inside the padded panel),
      exactly as #what does. In flow its intrinsic square height sized the
      row and left the card ending short; out of flow it contributes no
      height, so the row is the right column's height alone and the image
      fills it — its bottom edge lands on the bottom of the card.

   The resulting crop is fractional (the box stays near 1:1), well inside
   what the artwork tolerates.
   ------------------------------------------------------------------ */
@media (width >= 64rem) {
  #why-us .grid {
    grid-template-columns: 34rem minmax(0, 1fr);
    align-items: stretch;
  }
  #why-us .grid > div:first-child { position: relative; }
  /* Size explicitly rather than by opposing insets: for a replaced element
     with an intrinsic aspect ratio, `width/height: auto` resolve to the
     intrinsic size and the far inset is dropped — which left it square. */
  #why-us .grid > div:first-child > img {
    position: absolute;
    top: 0.5rem;                /* the panel's p-2 */
    left: 0.5rem;
    width: calc(100% - 1rem);
    height: calc(100% - 1rem);
  }
}

/* ==================================================================
   Motion
   ==================================================================
   Reveal-on-scroll, restrained: content settles up into place over a
   long, decelerating curve rather than sliding or popping. The curve
   (0.22, 1, 0.36, 1) spends most of its time easing out, which is what
   reads as unhurried rather than springy.

   The hidden state applies ONLY once the script has set data-anim="on"
   on <html>. With JS off, IntersectionObserver missing, or reduced
   motion preferred, nothing is ever hidden — the page renders exactly as
   it does today. Content is never dependent on an animation to appear.

   Only opacity and transform are animated, so nothing reflows and the
   compositor does the work.
   ------------------------------------------------------------------ */
[data-anim] {
  transition:
    opacity 900ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 900ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--anim-delay, 0ms);
}

html[data-anim-ready] [data-anim]:not(.is-in) {
  opacity: 0;
  transform: translateY(1.25rem);
}

/* Artwork settles out of a slight over-scale instead of travelling: it
   reads as an image coming into focus rather than a card flying in. */
html[data-anim-ready] [data-anim="media"]:not(.is-in) {
  transform: translateY(1.25rem) scale(1.03);
}

/* The hero runs on load rather than on scroll — it is already in view —
   so it gets a slower, longer settle. */
html[data-anim-ready] #hero [data-anim] {
  transition-duration: 1100ms;
}

@media (prefers-reduced-motion: reduce) {
  [data-anim] { transition: none; }
  html[data-anim-ready] [data-anim]:not(.is-in) { opacity: 1; transform: none; }
}
