/* ─── tokens ─── */
:root {
  /* The palette below is the design, in one fixed light scheme — there is no dark
     variant and no prefers-color-scheme branch anywhere in this file. `only light`
     says so to the browser, which stops it substituting its own dark treatment on
     a device set to dark (Chrome's Auto Dark Theme on Android re-colours pages
     that make no declaration). Without this the cream backgrounds and orange
     brand can be machine-inverted into colours nobody approved.
     `only` rather than plain `light`: plain `light` sets the scheme, `only light`
     additionally forbids the UA's automatic darkening. */
  color-scheme: only light;

  --brand:        #F68328;   /* PDF-exact brand orange */
  --brand-soft:   #FDE9D9;   /* peach card / soft fill */
  /* Two families: Nunito is the display face (titles/headers), Mulish carries
     body copy and UI text. --font-body is set on <body> so everything inherits
     it; only h1/h2 and the .title-* classes opt back into --font-head. */
  --font-head:    "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI",
                  Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI",
                  Roboto, "Helvetica Neue", Arial, sans-serif;
  --ink:          #1A1A1A;
  --ink-soft:     #464646;   /* body copy */
  --muted:        #919191;   /* secondary / hints */
  --dot:          #A6A6A6;   /* unwatched progress dot */
  --bg:           #FFFBF7;   /* warm page background — all panels */
  --bg-tint:      #F4F5EE;   /* cool section wash */
  --card:         #FFFFFF;
  --shadow:       0 6px 24px rgba(0,0,0,.08);
  --radius:       16px;
  --radius-sm:    10px;
  --app-vh:       1vh;        /* updated by JS for FB-IAB */
  --safe-bottom:  env(safe-area-inset-bottom, 0px);
  --safe-top:     env(safe-area-inset-top, 0px);
  --bar-h:        58px;       /* floating section-bar height (excl. safe-top) */
  --app-w:        min(420px, 100vw);  /* feed column — caps at the max mobile
                                         width; never fills a wide viewport.
                                         On phones (≤420px) this is 100vw. */
  --app-left:     calc(50% - var(--app-w) / 2);  /* left edge of the centered column */
}

/* ─── reset ─── */
*, *::before, *::after { box-sizing: border-box; min-width: 0; }
html, body { margin: 0; padding: 0; overflow: hidden; }
html, body { background: var(--bg); color: var(--ink); }
/* iOS paints a translucent grey box over whatever you tap. Every interactive
   element here already answers a tap with its own :active state (the buttons and
   cards scale, the bar back button fills), so the default flash adds nothing and
   reads as a rendering glitch when it lands over a full-bleed video panel. */
html { -webkit-tap-highlight-color: transparent; }
body {
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
/* Headings take the display face by tag, so a new h1/h2 needs no extra class.
   Font-family only — colour/alignment stay with the .title-* classes, which
   differ per heading (.lead is not orange, .lead-em is). */
h1, h2 { font-family: var(--font-head); }
img, video { max-width: 100%; height: auto; display: block; }
button {
  font: inherit; color: inherit;
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; text-align: left;
}
/* Button labels use the display face. Must come after the reset above, whose
   `font: inherit` shorthand would otherwise reset font-family back to Mulish.
   .cta-card is listed explicitly because it ships as BOTH <a> and <button> —
   without it the two variants would render in different families. */
button, .btn, .cta-card { font-family: var(--font-head); }
a { color: inherit; text-decoration: none; }

/* ─── feed ───────────────────────────────────────────────
   A fixed, horizontally-centered column capped at the max mobile width
   (--app-w). On phones it spans the full viewport; on tablet/desktop it stays
   that same width centered on the page (never stretches to fill the viewport). */
.feed {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--app-w);
  /* fallback then progressive: */
  height: 100vh;
  height: 100svh;
  height: 100dvh;
  /* NOT a scroll container. The deck moves #deck by transform, so a scroller
     here would be a second, competing source of position — which is precisely
     the native-scroll-plus-JS-correction architecture this rework removes.
     Everything that used to live in this rule (overflow-y, overscroll-behavior,
     scroll-snap-type, -webkit-overflow-scrolling, the scrollbar hiding) existed
     to manage or hide that scroller and went with it. */
  overflow: hidden;
  /* The gesture belongs to the deck, so the browser must not start a pan of its
     own. Inner scroll areas opt back in with `pan-y` further down, and the
     deck's yield rule keeps out of their way (spec §6). */
  touch-action: none;
  background: var(--bg);
}

/* The deck track: all eighteen panels in normal flow, moved as one. transform
   only — it never triggers layout, which is what keeps the animation off the
   main thread and identical between engines. */
.deck {
  will-change: transform;
}

/* ─── panel ─── */
.panel {
  position: relative;
  width: 100%;
  /* min-height guarantees full viewport; JS keeps --app-vh fresh */
  min-height: 100vh;
  min-height: 100svh;
  min-height: calc(var(--app-vh) * 100);
  height: 100dvh;
  background: var(--bg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ─── keyboard focus ───
   The journey is driven by the deck's key bindings acting on the focused feed
   (main.js focuses it on load), so the feed itself holds focus but draws no
   ring — an outline around the whole viewport would be noise, not information.
   Every real control gets a clear ring instead. WCAG 2.4.7 (Level AA); before
   this the stylesheet had exactly one :focus-visible rule in ~1100 lines. */
.feed:focus,
.feed:focus-visible { outline: none; }

.btn:focus-visible,
.cta-card:focus-visible,
.doctor-card:focus-visible,
.sectionbar__back:focus-visible,
#contact-open:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 3px;
}
/* Controls that sit ON the footage: brand orange alone can disappear into a busy
   frame, so invert to a white ring with an orange halo behind it. */
.btn.on-video:focus-visible,
.play-gate:focus-visible,
.vctl-toggle:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 6px rgba(246, 131, 40, .85);
}

.panel__inner {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  /* "safe center" keeps content centred when it fits but falls back to
     top-alignment when it overflows (e.g. large text-zoom) so nothing clips */
  justify-content: safe center;
  gap: clamp(12px, 3vh, 20px);
  padding: clamp(24px, 6vh, 40px)
           clamp(20px, 5vw, 28px)
           calc(clamp(72px, 12vh, 96px) + var(--safe-bottom))
           clamp(20px, 5vw, 28px);
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
}
/* These panels scroll their own content inside the deck. The deck keeps out of
   the way by the yield rule (spec §6): it takes a gesture only when the area
   under the finger cannot scroll further the way the finger is going. So an
   area with nothing to scroll never swallows a swipe — that was the p6 bug —
   and reaching the end of one hands the next swipe to the deck.
   `touch-action: pan-y` is the counterpart to `touch-action: none` on .feed: it
   gives the browser back the vertical pan for this subtree only. */
.panel__inner--scroll {
  justify-content: flex-start;
  overflow-y: auto;
  touch-action: pan-y;
  padding-top: calc(clamp(20px, 6vh, 36px) + var(--safe-top));
}
.panel__inner--scroll::-webkit-scrollbar { display: none; }

/* Content panels: editorial content centered in the upper area, the CTA(s)
   pinned to the bottom just above the chat FAB — matches the PDF, where every
   "Sign up as a Marrow Donor Now" button sits at the bottom of the screen. */
.panel__lead {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: safe center;   /* fall back to top-align if it overflows */
  gap: clamp(12px, 3vh, 20px);
}
/* Second scroller nested inside the first (this one keeps the CTA pinned while
   the copy scrolls). Same yield rule, same pan-y opt-in. */
.panel__inner--scroll .panel__lead {
  justify-content: flex-start;
  overflow-y: auto;
  touch-action: pan-y;
}
.panel__inner--scroll .panel__lead::-webkit-scrollbar { display: none; }
.panel__cta {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 2.2vh, 14px);
}
.panel__cta .swipe-back { margin-top: 6px; }

/* panels that carry the floating bar reserve space for it at the top */
.panel--bar .panel__inner {
  padding-top: calc(var(--bar-h) + var(--safe-top) + 14px);
}
.panel--bar .panel__inner--scroll {
  padding-top: calc(var(--bar-h) + var(--safe-top) + 10px);
}

/* every panel — including the welcome screen — uses the flat warm-white --bg */
.panel--welcome { background: var(--bg); }

/* ─── typography ─── */
/* All headings: rounded Quicksand, brand orange, centered — matches the PDF. */
/* Headings wrap by BALANCE, not by hard <br/>. The copy used to carry fixed line
   breaks tuned for one screen width, which is what produced ragged lines on a
   narrow phone and stranded single words on a wide one — the same break cannot be
   right at 320px and at 430px. `balance` distributes lines evenly at whatever
   width the device actually is. Unsupported engines simply wrap normally. */
.title-xl,
.title-lg {
  font-family: var(--font-head);
  color: var(--brand);
  text-align: center;
  margin: 0;
  text-wrap: balance;
}
.title-xl {
  /* Header reference spec: 28px @ 375px, weight 700. 28/375 = 7.467vw, so the
     375px reference lands exactly on the 1.75rem (28px) floor and scales up
     from there to the 2.4rem cap. .title-lg / .lead-em stay stepped below. */
  font-size: clamp(1.75rem, 7.467vw, 2.4rem);
  line-height: 1.2;
  font-weight: 700;
}
.title-lg {
  font-size: clamp(1.5rem, 6.5vw, 2.1rem);
  line-height: 1.22;
  font-weight: 700;
}
.body {
  font-size: clamp(0.95rem, 3.8vw, 1.05rem);
  line-height: 1.5;
  /* 500, not 400: Nunito's regular is noticeably lighter than Quicksand's was,
     so body copy read as thin after the family swap — especially the orange
     .brand-text runs, which inherit this weight and sit on tinted cards. */
  font-weight: 500;
  color: var(--ink-soft);
  margin: 0;
  text-align: center;
  /* `pretty` is the orphan fix: it prevents a paragraph ending with a single word
     alone on the last line. Purely an enhancement — engines without it wrap as
     before. (Body copy uses `pretty` rather than `balance`: balance is capped at
     a few lines and is meant for headings.) */
  text-wrap: pretty;
}
.lead {
  font-size: clamp(1.15rem, 4.6vw, 1.4rem);
  line-height: 1.35;
  margin: 0;
  font-weight: 600;
  text-align: center;
}
/* Section headline. This used to be byte-for-byte identical to .stat-line below
   — same size, weight, colour and alignment — so two levels of the hierarchy
   rendered as one. It now sits a clear step above it. */
.lead-em {
  /* Spec: Nunito 700, 25px / 34px line-height at the 375px reference width.
     25/375 = 6.667vw, so 375px lands exactly on the 1.5625rem (25px) floor;
     34/25 = 1.36 keeps the leading proportional as it scales up. */
  font-size: clamp(1.5625rem, 6.667vw, 1.9rem);
  line-height: 1.36;
  letter-spacing: -0.01em;
  font-weight: 700;
  margin: 0;
  text-align: center;
  color: var(--brand);
  text-wrap: balance;
}
.brand-text { color: var(--brand); }

/* welcome logo */
.brand-logo {
  width: clamp(96px, 28vw, 140px);
  height: auto;
  margin: 0 auto clamp(8px, 3vh, 16px);
}
/* ─── welcome screen ───
   Brand band on the cream wash, then a full-bleed portrait running to the bottom
   edge, with the title card laid over its lower third. The title used to be
   auto-centred in a plain padded column between the logo and the hint; it now
   lives in the card, so that centring (and the matching hint padding it was
   tuned against) no longer applies here. */
.welcome__brand {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  /* safe-top so the logo clears the status bar / notch, since this panel has no
     floating section bar above it to do that job */
  padding: calc(var(--safe-top) + clamp(14px, 4vh, 28px))
           clamp(16px, 5vw, 24px)
           clamp(12px, 3vh, 20px);
}
.welcome__brand .brand-logo { margin: 0; }   /* the band's padding owns the spacing */

.welcome__photo {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;        /* let it shrink inside the flex column rather than overflow */
  overflow: hidden;
}
.welcome__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The photo (3:4) is wider than this slot on a phone, so cover normally trims
     the SIDES and this has no effect. It matters on a short viewport, where the
     crop turns vertical instead: biasing above centre keeps her face in frame
     rather than centring on the bedsheet — same reasoning as the doctor thumbs. */
  object-position: center 30%;
  display: block;
}
/* The translucent card. Plain rgba rather than a backdrop-filter: the wash only
   has to lift the type off the photo, and this panel is the first paint on a
   cold load — not the place for a filter iOS has to composite. */
.welcome__card {
  position: absolute;
  left: clamp(14px, 4.5vw, 22px);
  right: clamp(14px, 4.5vw, 22px);
  /* Clears the chat FAB, which is fixed 25px up from the bottom and 50px tall. */
  bottom: calc(92px + var(--safe-bottom));
  background: rgba(255, 255, 255, 0.82);
  border-radius: var(--radius);
  padding: clamp(16px, 3.5vh, 26px) clamp(14px, 4vw, 22px) clamp(10px, 2vh, 16px);
}
.welcome__card .title-xl { text-align: center; }
.welcome__card .swipe-hint { padding-top: clamp(6px, 1.6vh, 12px); }


/* swipe hint */
/* Per the approved mockup: a small uppercase label with the chevron stacked
   above it, both in the same muted grey (it used to sit inline and orange). */
.swipe-hint {
  margin: 0;               /* the title's auto margins do the spacing now */
  text-align: center;
  font-size: clamp(0.6875rem, 2.93vw, 0.78rem);   /* 11px at the 375px reference */
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  /* Same value as .brand-logo's margin-bottom. These two are the only fixed
     spacers flanking the auto-centred title, so keeping them identical is what
     makes the visible gaps above and below it equal at any viewport height. */
  padding-top: clamp(8px, 3vh, 16px);
}
.swipe-hint span {
  display: block;          /* chevron on its own line, above the label */
  font-size: 1.7em;
  line-height: 1;
  margin: 0 auto 4px;
  animation: bob 1.6s ease-in-out infinite;   /* owns transform; see @keyframes bob */
}
.swipe-back {
  margin-top: clamp(16px, 4vh, 28px);
  text-align: center;
  font-size: clamp(0.78rem, 3.2vw, 0.85rem);
  color: var(--muted);
}
@keyframes bob {
  0%, 100% { transform: translateY(2px); }
  50%      { transform: translateY(-4px); }
}

/* ─── CTA cards (page 2 & 14) ─── */
.cta-card {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 4px 12px;
  padding: clamp(14px, 4vw, 18px) clamp(16px, 4.5vw, 20px);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  width: 100%;
  min-height: 64px;
  font: inherit;
  font-family: var(--font-head);   /* after `font: inherit`, which resets it */
  text-align: left;
  transition: transform .15s ease;
}
.cta-card:active { transform: scale(.98); }
.cta-card--primary {
  background: var(--brand);
  color: #fff;
}
.cta-card--ghost {
  background: #fff;
  color: var(--ink);
  border: 1px solid #EFE3D5;
}
.cta-card__label {
  grid-column: 1 / 2;
  grid-row: 1 / 2;
  font-weight: 700;
  font-size: clamp(0.98rem, 4vw, 1.08rem);
  line-height: 1.2;
}
.cta-card__sub {
  grid-column: 1 / 2;
  grid-row: 2 / 3;
  font-size: clamp(0.78rem, 3.2vw, 0.88rem);
  opacity: .85;
  line-height: 1.3;
}
.cta-card__time {
  grid-column: 2 / 3;
  grid-row: 1 / 3;
  align-self: center;
  font-size: clamp(0.72rem, 3vw, 0.82rem);
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,.25);
  white-space: nowrap;
}
.cta-card--ghost .cta-card__time {
  background: var(--brand-soft);
  color: var(--brand);
}
.cta-note {
  margin: -4px 0 0;
  font-size: clamp(0.72rem, 3vw, 0.8rem);
  color: var(--muted);
  text-align: center;
}

/* generic button */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: max(44px, 2.75rem);
  padding: 12px 18px;
  border-radius: 999px;
  font-weight: 700;
  font-size: clamp(0.95rem, 4vw, 1.05rem);
  width: 100%;
  text-align: center;
  transition: transform .15s ease, background .15s ease;
}
.btn:active { transform: scale(.98); }
.btn--primary { background: var(--brand); color: #fff; }
.btn--ghost   { background: #fff; color: var(--brand); border: 2px solid var(--brand); }

/* ─── stat lines ─── */
.stat-line {
  font-size: clamp(1.3rem, 5.4vw, 1.7rem);
  line-height: 1.25;
  font-weight: 700;
  margin: 0;
  text-align: center;
  color: var(--brand);
  text-wrap: balance;
}
.stat-line strong { font-weight: 700; }
.stat-num {
  display: inline-block;
  font-size: clamp(2.8rem, 12vw, 4rem);
  font-weight: 900;
  color: var(--brand);
  line-height: 1;
  margin-right: 6px;
  vertical-align: -0.12em;
}

/* bullet lists */
/* Spec block: 278px wide, centred, the items reading as one run of text —
   so no inter-item gap; the 23px line-height alone sets the rhythm. */
.bullet-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 278px;
  display: flex;
  flex-direction: column;
  gap: 0;
}
/* Spec: Mulish 400, 18px / 23px line-height at the 375px reference width.
   18/375 = 4.8vw; 23/18 = 1.28. Weight is 400 here (not the 500 used for
   .body): at 18px the regular face reads solid, where 15px did not. */
.bullet-list li {
  position: relative;
  padding-left: 20px;
  font-size: clamp(1.125rem, 4.8vw, 1.25rem);
  line-height: 1.28;
  font-weight: 400;
  color: var(--ink-soft);
}
.bullet-list li::before {
  content: "";
  position: absolute;
  left: 4px; top: .58em;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;   /* dot matches the copy, as in the mockup */
}

/* ─── video panels ─── */
.panel--video { background: #000; }
.bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.panel__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  gap: clamp(10px, 2.5vh, 16px);
  padding: clamp(20px, 6vh, 36px)
           clamp(20px, 5vw, 28px)
           calc(clamp(80px, 14vh, 110px) + var(--safe-bottom))
           clamp(20px, 5vw, 28px);
  background: linear-gradient(180deg,
    rgba(0,0,0,.25) 0%,
    rgba(0,0,0,0) 25%,
    rgba(0,0,0,0) 55%,
    rgba(0,0,0,.65) 100%);
  color: #fff;
}
.panel__overlay > * { width: 100%; max-width: 520px; margin-inline: auto; }
.on-video { color: #fff; text-shadow: 0 1px 8px rgba(0,0,0,.4); }

/* on bar'd video panels, the video + scrim start below the floating bar
   (the opaque bar covers the panel's black strip above this offset) */
.panel--bar.panel--video .bg-video {
  top: calc(var(--bar-h) + var(--safe-top));
  /* explicit height (not auto): <video> is a replaced element, so height:auto
     would size to the clip's intrinsic ratio and leave a gap below */
  height: calc(100% - (var(--bar-h) + var(--safe-top)));
}
.panel--bar .panel__overlay {
  top: calc(var(--bar-h) + var(--safe-top));
}

/* ─── scrollytelling journey sections ───────────────────────
   Each journey section pins its video while the text card(s) scroll up over it.
   The video auto-pauses once a text card covers it (handled in main.js). The
   intro pages and the doctor/experts pages are NOT wrapped in a .vsection, so
   they keep the plain one-screen-per-panel behaviour. */
.vsection {
  /* Grouping only, and a plain block: its height is just the sum of its panels,
     so they stay in normal flow at index * viewport height like every other
     panel. main.js reads this structure once at startup to tell the deck which
     text cards each video stays pinned behind. */
  position: relative;
}
/* The video stays pinned while its text card(s) rise over it — but NOT by
   position: sticky. A stuck sticky element reports a MOVING offsetTop, and that
   was the root cause of the original jump-back (spec §1 step 3). The deck holds
   this panel still by translating it back by exactly the distance the track
   moved: computed, never measured, so it cannot drift. */
.vmedia { top: 0; }
/* the text card: a near-solid (90% opaque) surface that slides over the video */
.vtext {
  z-index: 1;                                /* paint above the pinned video */
  background: rgba(255, 251, 247, 0.90);     /* var(--bg) #FFFBF7 @ 90% opacity */
  /* z-index alone was not enough on iOS Safari: the card arrived transparent and
     only gained its background once the screen was touched.
     deck.js gives every pin host an inline translate3d to hold it still, which
     puts the pinned VIDEO on the compositing layer. This card had no transform,
     so it stayed unpromoted — and WebKit draws composited layers over
     uncomposited content regardless of z-index, so the video won and the card's
     90% wash was simply not there. A touch forced the re-layerisation that made
     it appear, which is why tapping "fixed" it.
     translateZ(0) promotes the card too, so both are composited and z-index
     decides the order, as it already does everywhere else. Safe here because no
     position:fixed element lives inside a panel (all the floating chrome is a
     sibling of #feed), so the containing block this establishes traps nothing. */
  transform: translateZ(0);
}

.doctor-name {
  font-size: clamp(1.2rem, 5vw, 1.5rem);
  font-weight: 700;
  margin: 0;
  text-align: center;
  color: var(--brand);
}
.doctor-topic {
  font-size: clamp(1rem, 4vw, 1.15rem);
  font-weight: 600;
  margin: 0;
  text-align: center;
  color: var(--ink-soft);
}

/* doctor video panels (p17–19): cream bg, full-width landscape clip, then
   name / topic / CTA stacked below — matches the PDF (not full-bleed video) */
.panel--doctor { background: var(--bg); }
.doctor-stack {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding-top: calc(var(--bar-h) + var(--safe-top));
  /* reserve room for the bottom-left chat FAB so the CTA sits just above it */
  padding-bottom: calc(96px + var(--safe-bottom));
}
/* video + name + topic, centered in the space above the CTA */
.doctor-feature {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(14px, 3vh, 22px);
}
.feature-video {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  /* Fallback for a landscape clip: crop slightly toward the top. No shipped clip
     hits this any more — all three doctor clips are portrait — but it is what a
     landscape replacement would get. Portrait clips are re-laid-out by
     .is-portrait below. */
  object-position: center 22%;
  background: #000;
  display: block;
}
/* Portrait clips (all three doctor clips — 9:16) show as portrait instead of
   being cropped into the 16:9 band: a centred, height-capped portrait card so the
   name/topic and CTA still fit on screen. main.js adds .is-portrait when a clip's
   intrinsic height > width. */
.feature-video.is-portrait {
  width: auto;
  aspect-ratio: 9 / 16;
  height: min(56vh, calc(100dvh - 330px));
  max-width: 100%;
  margin-inline: auto;
  object-position: center;          /* same aspect as source → no crop */
  border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
}
.doctor-meta {
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 1.5vh, 10px);
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
  padding-inline: clamp(20px, 5vw, 28px);
}
/* CTA anchored at the bottom, just above the chat FAB (PDF p17–19) */
.doctor-cta {
  width: calc(100% - 2 * clamp(20px, 5vw, 28px));
  max-width: 520px;
  margin-inline: auto;
}

/* peach quote card (voluntary panel, p8) */
/* Layout-only stack — no card chrome. Per the approved mockup the copy sits
   directly on the panel's warm-white wash (.vtext, 90% opaque over the video),
   so the peach gradient/radius/shadow are gone. The flex gap stays: .body has
   margin:0, so without it the three statements would run together. */
.quote-card {
  /* Vertical padding only. The card has no background and the lines are centred,
     so side padding bought nothing visually but cost ~38px of measure — enough
     to force extra wrapping past the mockup's line breaks on a 320px screen. */
  padding: clamp(20px, 6vw, 30px) 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Matches p9's list copy: 18px / 23px at the 375px reference width. Scoped to
   the quote card so p2 / p4 / p14 body copy keeps its own smaller scale. */
.quote-card .body {
  color: var(--ink-soft);
  font-size: clamp(1.125rem, 4.8vw, 1.25rem);
  line-height: 1.28;
}

/* ─── steps (panel 14) ─── */
.steps-image {
  width: 100%;
  max-width: 320px;
  margin: 8px auto;
}
/* PBSC infographic (p6) — the original artwork has its own peach rounded card */
.pbsc-image {
  width: 100%;
  max-width: 440px;
  height: auto;
  margin: 4px auto 0;
  display: block;
}
.steps-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: none;       /* shown only on narrow screens */
  flex-direction: column;
  gap: 12px;
}
.steps-list li {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: clamp(0.95rem, 3.8vw, 1.05rem);
  font-weight: 600;
}
.steps-list__n {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-weight: 800;
  font-size: 1.05rem;
}

@media (max-width: 359px) {
  .steps-image { display: none; }
  .steps-list  { display: flex; }
}

/* ─── doctor grid (experts list) ─── */
/* Vertical cards: full-width 16:9 thumbnail on top, then name (orange) and
   topic (grey) centered below — matches the PDF. */
.doctor-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 4px;
}
.doctor-card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid #F0E4D5;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0,0,0,.05);
  transition: transform .15s ease;
}
.doctor-card:active { transform: scale(.99); }
.doctor-card__thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--brand);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.doctor-card__thumb img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* portrait source clips crop to a 16:9 band — bias toward the top so the
     subject's face is kept in frame rather than centred on the torso */
  object-position: center 28%;
  z-index: 1;     /* covers the initials fallback once the poster loads */
}
.doctor-card__initials {
  color: #fff;
  font-weight: 800;
  font-size: 2rem;
  letter-spacing: 0.04em;
  /* shown only if the poster image fails to load (sits behind it) */
}

.doctor-card__name {
  display: block;
  text-align: center;
  font-size: clamp(1.05rem, 4.4vw, 1.25rem);
  font-weight: 800;
  color: var(--brand);
  line-height: 1.2;
  margin: 12px clamp(12px, 4vw, 16px) 0;
}
.doctor-card__topic {
  display: block;
  text-align: center;
  font-size: clamp(0.9rem, 3.6vw, 1rem);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 3px clamp(12px, 4vw, 16px) 14px;
}

/* ─── floating section bar (title + progress dots / back) ─── */
/* Spans the centered feed column, not the full viewport. */
.sectionbar {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--app-w);
  z-index: 55;
  height: calc(var(--bar-h) + var(--safe-top));
  padding: calc(var(--safe-top) + 8px) 16px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: var(--bg);                 /* opaque warm-white band, like the PDF */
  box-shadow: 0 2px 10px rgba(0,0,0,.05);
}
.sectionbar[hidden] { display: none; }
.sectionbar__title {
  font-family: var(--font-head);
  color: var(--brand);
  font-weight: 600;
  font-size: clamp(1.05rem, 4.2vw, 1.3rem);
  line-height: 1.2;
  text-align: center;
  margin: 0;
}
.sectionbar__dots {
  list-style: none;
  margin: 0; padding: 0;
  display: flex;
  gap: 7px;
}
.sectionbar__dots li {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--dot);
  transition: background .25s ease;
}
.sectionbar__dots li[data-on="true"] { background: var(--brand); }

/* back button (experts pages); hidden in journey mode */
.sectionbar__back {
  position: absolute;
  left: 12px; top: 50%;
  transform: translateY(-50%);
  margin-top: calc(var(--safe-top) / 2);
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1.5px solid var(--brand);
  color: var(--brand);
  font-size: 0;            /* hide the text glyph; chevron is drawn below */
  display: none;
  align-items: center;
  justify-content: center;
}
/* CSS-drawn chevron — reliably centered (the ‹ glyph has uneven bearings) */
.sectionbar__back::before {
  content: "";
  width: 9px; height: 9px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-radius: 1px;
  transform: translateX(2px) rotate(45deg);   /* points left, optically centered */
}
.sectionbar__back:active { background: var(--brand-soft); }
.sectionbar--experts .sectionbar__dots { display: none; }
.sectionbar--experts .sectionbar__back { display: flex; }

/* ─── play gate (Play button over the current video until audio is unlocked) ─── */
.play-gate {
  position: fixed;            /* top/left set inline by main.js = active video centre */
  transform: translate(-50%, -50%);
  z-index: 58;
  width: 76px; height: 76px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, .45));
  animation: pg-pulse 2.2s ease-in-out infinite;
}
.play-gate[hidden] { display: none; }
.play-gate:active { transform: translate(-50%, -50%) scale(.94); animation: none; }
.play-gate svg { width: 100%; height: 100%; display: block; }
.play-gate .pg-disc { fill: rgba(0, 0, 0, .45); stroke: #fff; stroke-width: 2; }
.play-gate .pg-tri  { fill: #fff; }
@keyframes pg-pulse {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  50%      { transform: translate(-50%, -50%) scale(1.07); }
}
@media (prefers-reduced-motion: reduce) { .play-gate { animation: none; } }

/* ─── video controls (play/pause + time scrubber over the active clip) ───
   position:fixed; left/right/bottom set inline by main.js = active video's band. */
.video-ctl {
  position: fixed;
  z-index: 57;                 /* below the play-gate (58), above the video scrim */
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .42);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #fff;
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;        /* off while faded out */
}
.video-ctl[hidden] { display: none; }
.video-ctl.is-visible { opacity: 1; pointer-events: auto; }

.vctl-toggle {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.vctl-toggle svg { width: 24px; height: 24px; display: block; fill: #fff; }
.vctl-toggle:active { transform: scale(.92); }
/* toggle glyph: show play OR pause via the bar's data-playing state */
.vctl-icon-pause { display: none; }
.video-ctl[data-playing="true"] .vctl-icon-play  { display: none; }
.video-ctl[data-playing="true"] .vctl-icon-pause { display: block; }

.vctl-time {
  flex: 0 0 auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  min-width: 30px;
  text-align: center;
  color: rgba(255, 255, 255, .92);
}

/* scrubber: a padded hit-area wrapping a thin track (bigger touch target) */
.vctl-scrub {
  flex: 1 1 auto;
  min-width: 90px;
  padding: 12px 0;             /* vertical hit-area; track is centred */
  cursor: pointer;
  touch-action: none;          /* let JS own the horizontal drag (no scroll) */
  outline: none;
}
.vctl-scrub-track {
  position: relative;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .3);
}
.vctl-scrub-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0%;
  border-radius: 2px;
  background: #fff;
}
.vctl-scrub-knob {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.vctl-scrub:focus-visible .vctl-scrub-track {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .7);
}

/* Compact bar for narrow clips — the portrait doctor videos (.is-portrait) are
   slim cards, and on a short viewport the clip can be ~174px wide. main.js adds
   .is-compact when the bar has to fit under ~240px; without it the toggle, both
   time labels and the scrub track cannot fit and the bar overhangs the video. */
.video-ctl.is-compact { padding: 6px 8px; gap: 6px; }
.video-ctl.is-compact .vctl-toggle { width: 26px; height: 26px; }
.video-ctl.is-compact .vctl-toggle svg { width: 18px; height: 18px; }
.video-ctl.is-compact .vctl-time { font-size: 10px; min-width: 24px; }
.video-ctl.is-compact .vctl-scrub { min-width: 36px; padding: 10px 0; }
.video-ctl.is-compact .vctl-scrub-knob { width: 11px; height: 11px; }

@media (prefers-reduced-motion: reduce) {
  .video-ctl { transition: none; }
}

/* ─── portrait-only gate ───────────────────────────────────
   Hidden by default; shown only when a phone is held in landscape (short
   landscape viewport). Covers everything incl. the chat FAB (z 99999). */
#rotate-gate {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100000;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 32px;
  text-align: center;
  background: var(--bg);
  color: var(--brand);
}
.rotate-gate__icon {
  width: 76px; height: 76px;
  transform-origin: 50% 50%;
  animation: rotate-hint 2.6s ease-in-out infinite;
}
.rotate-gate__title {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.3rem;
  margin: 0;
  color: var(--brand);
}
.rotate-gate__sub {
  font-size: 0.95rem;
  margin: 0;
  color: var(--ink-soft);
}
@keyframes rotate-hint {
  0%, 50%   { transform: rotate(0deg); }
  72%, 100% { transform: rotate(-90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .rotate-gate__icon { animation: none; }
}
/* Phones in landscape have a SHORT viewport; desktop & tablet landscape are
   tall, so they never trigger this. */
/* Phone held sideways. `pointer: coarse` is load-bearing, not decoration: the
   size test alone also matches a DESKTOP window shorter than 600px, so anyone
   who shrank their browser vertically was told to rotate a device that does not
   rotate — with the feed hidden behind the message. Touch input is what makes
   "rotate" actionable advice, so the gate is now limited to it. */
@media (orientation: landscape) and (max-height: 600px) and (pointer: coarse) {
  #rotate-gate { display: flex; }
}

/* ─── wider than the column (tablet / desktop) ─────────────
   The feed stays capped at --app-w and centered. Here we just style the
   surrounding page so the column reads as a distinct surface — a subtle
   neutral backdrop and a soft ambient shadow on the column. NO phone-frame
   bezel, rounded device corners, or hardware chrome. */
@media (min-width: 421px) {   /* once the viewport exceeds --app-w (420px) */
  html, body {
    background: #ECE7E1;   /* soft neutral surround */
  }
  .feed {
    box-shadow: 0 0 50px rgba(0, 0, 0, .10);
  }
}

/* ─── reduced motion ─── */
@media (prefers-reduced-motion: reduce) {
  /* The deck's own transitions drop to 0ms in JS (it tracks this query live),
     so there is nothing to disable here for the feed itself. */
  .swipe-hint span { animation: none; }
  .cta-card, .btn, .doctor-card { transition: none; }
}

/* ─── BMDP contact widget — bottom-left FAB, matching the PDF ─── */
/* Anchored to the bottom-left of the centered feed column (not the viewport
   edge), so on tablet/desktop it sits just inside the column like on mobile. */
/* ─── end-of-feed notice ──────────────────────────────────────────
   Fades in when the visitor keeps swiping past the last panel, then fades out
   on its own. pointer-events:none throughout, so it can never swallow a swipe
   or a tap on the CTA beneath it. Anchored to the feed column (not the
   viewport) so it stays centred over the column on tablet/desktop. */
.end-toast {
  position: fixed;
  left: var(--app-left);
  width: var(--app-w);
  bottom: calc(var(--safe-bottom) + 100px);   /* clears the chat FAB */
  z-index: 90;                                /* over panels, under the FAB */
  display: flex;
  justify-content: center;
  padding: 0 20px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .3s ease, transform .3s ease;
}
.end-toast.is-visible { opacity: 1; transform: none; }
.end-toast__pill {
  max-width: 300px;
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(255, 251, 247, .97);
  color: var(--ink-soft);
  box-shadow: var(--shadow);
  font-size: clamp(0.8125rem, 3.47vw, 0.9rem);   /* 13px at the 375px reference */
  line-height: 1.35;
  font-weight: 500;
  text-align: center;
}
.end-toast__pill:empty { display: none; }   /* no bare bubble before first show */
/* Must sit AFTER the rules above: the base .end-toast re-declares transition and
   transform, so an earlier reduced-motion block would simply be overridden. */
@media (prefers-reduced-motion: reduce) {
  .end-toast { transition: none; transform: none; }
  .end-toast.is-visible { transform: none; }
}

/* iOS long-press. contact.css sets `user-select: none` unprefixed, which Safari
   ignored until 17 — so on iOS 16 and earlier a long-press raised the text
   selection callout over the chat button. It also only sets it on the label,
   never on the parts you actually tap (#contact-open, .contact-btn). Covered
   here for the whole widget, with -webkit-touch-callout to suppress the
   long-press menu itself. */
#contact-open, #contact-plugin,
#contact-open *, #contact-plugin * {
  -webkit-user-select: none;
          user-select: none;
  -webkit-touch-callout: none;
}

/* The widget's own contact.css hard-codes Roboto on .contact-open-label and
   loads AFTER this file, so an id-level selector is needed to win. Keeps the
   Chat / Phone / WhatsApp / Email labels on the display face like every other
   button on the page. */
#contact-open .contact-open-label,
#contact-plugin .contact-open-label { font-family: var(--font-head); }

#contact-plugin,
#contact-open {
  right: auto !important;
  left: calc(var(--app-left) + max(18px, env(safe-area-inset-left, 0px))) !important;
  bottom: max(20px, calc(var(--safe-bottom) + 18px)) !important;
  z-index: 99999 !important;
}

/* Hide the "Contact us" pill tooltip (collapsed by default) */
#contact-open .contact-open-label,
#contact-open .contact-open-label.contact-one,
#contact-plugin .contact-open-label {
  display: none !important;
}

/* Closed-state FAB — the whole button is assets/chat.svg (rounded square, white
   speech bubble, "Chat" wordmark and drop shadows are all baked into the export),
   so this element draws nothing itself: no background, no radius, no shadow, no
   ::after label. It is just the hit area the artwork is pinned to. */
#contact-open {
  /* On-screen edge of the SVG's orange square. The artwork is scaled from this:
     in the 70x70 export the square is 46x46 at (12,2), so the image renders at
     70/46 of this size and is offset by -12/46 and -2/46 of it to line the
     square up with this element's box. Change this one value to resize the FAB. */
  --chat-sq: 50px;

  /* display:flex (NO !important) so contact.js can still toggle inline
     display:none/flex when opening/closing. */
  display: flex;
  align-items: center !important;
  justify-content: center !important;
  width: var(--chat-sq) !important;
  height: var(--chat-sq) !important;
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  padding: 0 !important;
  overflow: visible !important;
  transition: transform .18s ease, opacity .25s ease;
}
#contact-open:hover  { transform: translateY(-1px); }
#contact-open:active { transform: scale(.96); }

/* Fade the FAB out while a clip is playing (main.js sets .chat-dim on <body>),
   so it never covers a caption burnt into the video image. The layout can reserve
   space for the button, but a caption inside the picture itself cannot be moved
   out from under it. Returns as soon as the clip is paused or the screen has no
   video. Deliberately opacity + pointer-events only: contact.js drives `display`
   on this element, so setting display here would fight it.
   Keyboard focus overrides the fade — otherwise the button would still be
   focusable while invisible, which fails WCAG 2.4.7. */
/* !important is load-bearing here, not decoration. contact.js leaves its
   open/close animations (rotateCWIn / rotateCCWIn, `forwards`) applied to this
   element, and a CSS animation outranks a normal author declaration — so without
   !important the fade below silently stops working the moment a visitor has
   opened and closed the chat once. An important author declaration does outrank
   an animation, which is what makes this stick. */
body.chat-dim #contact-open { opacity: 0 !important; pointer-events: none !important; }
/* :focus as well as :focus-visible — :focus-visible does not match when focus
   arrives programmatically, and a focused-but-invisible control is exactly the
   failure this guard exists to prevent. Revealing it on any focus is harmless. */
body.chat-dim #contact-open:focus,
body.chat-dim #contact-open:focus-visible { opacity: 1 !important; pointer-events: auto !important; }
@media (prefers-reduced-motion: reduce) {
  #contact-open { transition: none; }
}

/* neutralize contact.js's inner wrappers so the icon + label can stack */
#contact-open #contact-rotate,
#contact-open .contact-btn1 {
  background: transparent !important;
  border-radius: 0 !important;
  width: auto !important;
  height: auto !important;
  box-shadow: none !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* The exported artwork, pinned so its orange square sits exactly on #contact-open's
   box. The export's shadow padding (12 left/right, 2 top, 22 bottom of a 70x70
   canvas) is allowed to overflow, so the shadow is not clipped. */
#contact-open .chat-svg {
  position: absolute;
  width:  calc(var(--chat-sq) * 70 / 46);
  height: calc(var(--chat-sq) * 70 / 46);
  left:   calc(var(--chat-sq) * -12 / 46);
  top:    calc(var(--chat-sq) * -2 / 46);
  max-width: none;
  pointer-events: none;
}

/* the "Chat" wordmark now lives inside chat.svg */
#contact-open::after { content: none !important; }

/* Expanded state — keep stack of action buttons compact, no labels */
#contact-plugin {
  gap: 10px !important;
}
#contact-plugin .phones {
  width: 56px !important;
  height: 56px !important;
}
#contact-plugin .contact-btn,
#contact-plugin .contact-btn1 {
  width: 56px !important;
  height: 56px !important;
  border-radius: 50% !important;
  box-shadow: 0 8px 20px rgba(0,0,0,.18), 0 2px 4px rgba(0,0,0,.08) !important;
}
#contact-plugin .contact-btn svg,
#contact-plugin .contact-btn1 svg {
  width: 56px;
  height: 56px;
}

/* Close ("Hide") button — match the chat-icon FAB look: orange disc, white X */
#contact-plugin #rotate-wrapper {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand);
  box-shadow: 0 10px 24px rgba(246, 131, 40, .35),
              0 2px 6px rgba(0, 0, 0, .12);
}
#contact-plugin #contact-close {
  background: transparent !important;
  border-radius: 0 !important;
  width: 100% !important;
  height: 100% !important;
  box-shadow: none !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}
#contact-plugin #contact-close svg {
  width: 28px !important;
  height: 28px !important;
}
#contact-plugin #contact-close svg ellipse { fill: transparent !important; }
#contact-plugin #contact-close svg rect    { fill: #ffffff !important; }

/* ─── desktop / tablet: phone-shaped stage ────────────────────────
   The column is already width-capped (--app-w = 420px), but on a tall viewport
   the panels still stretched to the full window height, distorting the phone
   framing the design was approved at. Cap the height at the mockup's aspect
   (420 x 910 ≈ the 375 x 812 reference) and centre the stage vertically.

   min-width 600px: no portrait phone is that wide, so phones keep the current
   behaviour exactly — including tall ones (a 932px Pro Max must NOT be capped).
   min(100vh, cap) means a window shorter than the cap resolves to full height
   and a zero gutter, i.e. this block is inert unless the stage would have been
   stretched.

   The play gate and video control bar need nothing here: they are positioned
   by main.js from live getBoundingClientRect() reads, so they follow the feed
   wherever it sits. Only the statically-placed chrome (section bar, chat FAB,
   end toast) must share the gutter. */
@media (min-width: 600px) {
  :root {
    --app-h:      min(100vh, 910px);
    --app-gutter: calc((100vh - var(--app-h)) / 2);
  }
  /* DESIGN_GUIDE §layout: "the surrounding page is a soft neutral backdrop with
     a light ambient shadow on the column — no phone-frame bezel or device
     chrome, just a centered surface". Hence a wash and a shadow and nothing
     else: no rounded corners, no bezel.
     The backdrop reuses --bg-tint, the palette's cool section wash, which the
     dead-CSS sweep had left declared but unused. The column keeps --bg, so the
     warm white now reads as a lit surface against the cooler page. */
  html, body { background: var(--bg-tint); }
  .feed {
    top: var(--app-gutter);
    height: var(--app-h);
    /* Ambient, not a drop shadow: wide blur, low opacity, barely any offset, so
       the column lifts off the backdrop without implying a device edge. Costs
       nothing per frame — the feed never moves, only its content scrolls. */
    box-shadow: 0 6px 40px rgba(0, 0, 0, .08);
  }
  .panel {
    /* Both equal to the feed height: the deck places panel i at i * viewport
       height, so panel height must equal .feed's height exactly or every panel
       past the first sits at the wrong offset. */
    min-height: var(--app-h);
    height: var(--app-h);
  }
  .sectionbar { top: var(--app-gutter); }
  .end-toast  { bottom: calc(var(--app-gutter) + 100px); }
  #contact-plugin,
  #contact-open {
    bottom: calc(var(--app-gutter) + 20px) !important;
  }
  /* Portrait doctor clips size themselves against the viewport on the
     assumption that panel height == 100dvh; on the capped stage that assumption
     breaks (a tall window would size the clip taller than the panel), so the
     panel-height term switches to the stage height. */
  .feature-video.is-portrait {
    height: min(56vh, calc(var(--app-h) - 330px));
  }
}

