/* Modern Health, Everlab direction: round 1 rebuild of index.html and
   patient-journey.html (8 Sept founders' review). Linked on both pages
   directly after styles/base.css. Every new component introduced by this
   round lives here, not inline in either page, so the M component and its
   behaviour can be shared verbatim between the two pages without
   duplicating CSS. Reuses base.css tokens and conventions throughout
   (--radius-lg, --line, --bg-card, --accent, .reveal/.reveal-N, .eyebrow,
   .section-head, .btn, .service-card family) rather than reinventing them.

   Owned files only: index.html, patient-journey.html, this stylesheet,
   round1-home.js, motion-effects.js, hero-landing.js, img/ additions. Does
   not touch services.html, team.html, about.html, booking.html or
   styles/round1-pages.css, which another agent is editing at the same
   time. */

/* =====================================================================
   1. HERO: full-bleed cinematic video, warm charcoal overlay, centred
      copy, one CTA + quiet link, bottom ticker row of three facts.
      Rebuilt 2026-09-16 (design-round-1) to match the everlab.com.au
      hero register. The dot-grid canvas, low-opacity ripple photo layer
      and four marker cards this replaced are gone; the "Five therapies"
      section directly below index.html's hero already carries that
      content verbatim, so nothing is lost. Frame's own size/centring
      rules stay in index.html's inline <style> (unique to that page);
      every rule below is this round's new hero componentry.
   ===================================================================== */

/* Session cards removed, 2026-10-05, round 2, founders' request: the
   cluster that used to sit beside hero-content (and the 1200px row
   layout that gave it room to do so) is gone, so hero-inner now holds
   only hero-content and the hero reads centred at every width. Kept as
   a column wrapper rather than removed outright: hero-content still
   needs a positioned, full-width ancestor above the video/overlay. */
.hero-inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.75rem;
  width: 100%;
}

.hero-video {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  animation: hero-video-drift 24s ease-in-out infinite alternate;
}
@keyframes hero-video-drift {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-video { animation: none; }
}

/* Warm charcoal gradient so paper text passes 4.5:1 everywhere it sits:
   lighter at the top (the plaster wall still reads through), a firm
   centre band across the headline/sub/CTA column, rising further at the
   bottom under the ticker row. --bg-black via color-mix so a tokens.css
   brand-book swap still only ever touches that one file.

   Measured against this build's sunlit hero-shadows.mp4 frame (a lit
   patch of plaster, close to the brightest plausible background):
   --paper-soft body copy in the centre band needed alpha ~0.74 with a
   safe margin to clear 4.5:1 against it (0.55, the visually "about
   right" first pass, only cleared ~3.7:1), so the centre band sits well
   above the lighter 0.55 the brief sketches; see the 2026-09-16 contrast
   verification report for the measured numbers. The eyebrow chip, which
   sits higher up in the lighter top band, gets its own small fill below
   rather than dragging that whole band down to match. */
.hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--bg-black) 32%, transparent) 0%,
    color-mix(in srgb, var(--bg-black) 74%, transparent) 40%,
    color-mix(in srgb, var(--bg-black) 74%, transparent) 68%,
    color-mix(in srgb, var(--bg-black) 84%, transparent) 100%
  );
}

/* The eyebrow sits higher in the frame, in the gradient's lighter top
   band (by design, so the plaster still reads there for everyone else).
   --paper-soft text can't clear 4.5:1 against that lighter band alone
   without darkening the whole top of the frame, so the chip gets its
   own small translucent fill and blur, same idiom as a chip floating
   over any other photograph on this site, rather than the eyebrow
   forcing the top band to match the darker centre band. */
.hero .eyebrow {
  background: color-mix(in srgb, var(--bg-black) 60%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* The shared [data-tone="dark"] .fade rule (styles/base.css) reads
   --paper-faint, quiet enough for a solid charcoal field; the hero's
   video-and-overlay field needs the firmer --paper-soft the brief calls
   for, so this overrides it. Specificity (element + class + attribute +
   two classes) beats the base rule regardless of stylesheet order. */
header.hero[data-tone="dark"] .u-display .fade {
  color: var(--paper-soft);
}

.hero-explore-link {
  color: var(--paper-soft);
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  text-underline-offset: 3px;
  transition: color 0.25s ease;
}
.hero-explore-link:hover,
.hero-explore-link:focus-visible {
  color: var(--paper);
  text-decoration: underline;
  text-decoration-color: currentColor;
}

/* Ticker row: three quiet facts pinned to the bottom edge of the frame,
   mono/uppercase/letterspaced, separated by thin hairlines with a small
   currentColor SVG mark ahead of each. */
.hero-ticker {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 1.75rem var(--pad-x);
  list-style: none;
}
.hero-ticker-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 1.4rem;
  border-left: 1px solid var(--paper-line);
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-soft);
  white-space: nowrap;
}
.hero-ticker-item:first-child { border-left: 0; padding-left: 0; }
.hero-ticker-item:last-child { padding-right: 0; }
.hero-ticker-icon {
  width: 14px;
  height: 14px;
  flex: none;
  color: currentColor;
}

@media (max-width: 720px) {
  /* Bottom padding is deliberately generous, not just "a bit more than
     the ticker's height": the three-line stacked ticker plus its own
     padding runs to roughly 9rem tall at narrow widths, and this is the
     clearance the centred content column above needs to never run under
     it (checked at 390 wide against the live ticker height, not guessed). */
  .hero-frame { min-height: 78vh; padding-top: clamp(6rem, 15vh, 8rem); padding-bottom: 9.5rem; }
  .hero-content { gap: 1.15rem; }
  .hero-cta-row { gap: 0.75rem; }
  .hero-ticker {
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
    padding: 0.85rem 1.25rem 1.25rem;
  }
  .hero-ticker-item {
    border-left: 0;
    padding: 0;
    white-space: normal;
    text-align: center;
  }
}

/* =====================================================================
   2/3/4. CAROUSEL BLOCKS: Therapies and Consultations run as drifting
   belts again, 2026-10-05 round 2 (founders' request). The card anatomy
   (.carousel/.service-card/.service-thumb/.service-detail/.carousel-
   rail*) and the carousel-block wrapper (.carousel-block .section-head,
   .carousel-block-hr) now live in styles/carousel.css, shared verbatim
   with services.html, rather than duplicated here: both pages link that
   file directly after styles/base.css. The static photo-card grid this
   replaced, and index.html's own separate .carousel-block rules (a
   leftover from when this page's belts had gone back to a static
   grid), are both gone as of this round.
   Packages stays a static chip grid (styles/chips.css), Ryan's call. */

/* =====================================================================
   5. TRACKING: biomarker/wearable section, three animated-SVG cards, the
      tracked-markers list, integrations line and a tall side photo.
   ===================================================================== */

.tracking-layout {
  display: grid;
  grid-template-columns: 1fr 22rem;
  gap: 3rem;
  align-items: stretch;
}
.tracking-side {
  border-radius: var(--radius-lg);
  overflow: hidden;
  min-height: 26rem;
}
.tracking-side img { width: 100%; height: 100%; object-fit: cover; }

.tracking-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-top: 2.25rem;
}
.tracking-card {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  padding: 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
.tracking-card svg { width: 100%; height: 5.5rem; overflow: visible; }
.tracking-card h3 { font-size: 1.02rem; }
.tracking-card p { margin: 0; font-size: 0.88rem; color: var(--ink-soft); }

/* Biological age: a ring that draws on reveal. No number is invented; the
   dial reads "Trend" rather than a placeholder figure. */
.ring-track { fill: none; stroke: var(--line); stroke-width: 6; }
.ring-progress {
  fill: none;
  stroke: var(--accent);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 251;
  stroke-dashoffset: 251;
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(-90deg);
  transition: stroke-dashoffset 1.3s var(--ease-reveal) 0.1s;
}
.reveal.is-in .ring-progress { stroke-dashoffset: 63; }
.ring-label {
  font-family: var(--display);
  font-size: 0.95rem;
  fill: var(--ink);
}

/* Biomarker tracking: a five-point line that draws on reveal and gently
   re-bounces on hover. */
.chart-path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 220;
  stroke-dashoffset: 220;
  transition: stroke-dashoffset 1.1s var(--ease-reveal) 0.1s;
}
.reveal.is-in .chart-path { stroke-dashoffset: 0; }
.chart-dot { fill: var(--accent); }
.tracking-card:hover .chart-path,
.tracking-card:focus-within .chart-path {
  animation: chart-bounce 0.6s var(--ease-reveal);
}
@keyframes chart-bounce {
  0% { transform: translateY(0); }
  40% { transform: translateY(-4px); }
  100% { transform: translateY(0); }
}

/* Wearable integration: a heart-rate style trace that pulses on hover. */
.trace-path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}
.tracking-card:hover .trace-path,
.tracking-card:focus-within .trace-path {
  animation: trace-pulse 1.4s ease-in-out infinite;
}
@keyframes trace-pulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .ring-progress { transition: none; stroke-dashoffset: 63; }
  .chart-path { transition: none; stroke-dashoffset: 0; }
  .tracking-card:hover .chart-path { animation: none; }
  .tracking-card:hover .trace-path,
  .tracking-card:focus-within .trace-path { animation: none; opacity: 0.85; }
}

.tracked-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  margin-top: 2.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
}
.tracked-grid h4 {
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 0.5rem;
}
.tracked-grid p { margin: 0; font-size: 0.92rem; color: var(--ink-soft); }

.tracking-lines { margin-top: 1.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
.tracking-lines p { margin: 0; font-size: 0.95rem; color: var(--ink-soft); }

/* Device and wearable row, round 1B item 5: five brand-neutral line icons
   (watch, ring, phone, scale, CGM patch), same stroke idiom as the hero
   ticker icons (currentColor, 1.6 width). Wraps to two rows at narrow
   widths rather than forcing a scroll or shrinking past legibility. */
.device-row {
  margin-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2rem;
}
.device-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  transition: transform 0.3s var(--ease-reveal);
}
.device-item:hover,
.device-item:focus-within { transform: translateY(-3px); }
.device-icon { width: 2.1rem; height: 2.1rem; color: var(--ink-soft); }
.device-label {
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
@media (prefers-reduced-motion: reduce) {
  .device-item { transition: none; }
}
@media (max-width: 480px) {
  .device-row { gap: 1.25rem 1.5rem; }
}

.tracking-footnote { margin-top: 1.5rem; font-size: 0.82rem; color: var(--ink-faint); }

@media (max-width: 900px) {
  .tracking-layout { grid-template-columns: 1fr; }
  .tracking-side { display: none; }
  .tracking-cards { grid-template-columns: 1fr; }
  .tracked-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
  .tracked-grid { grid-template-columns: 1fr; }
}

/* =====================================================================
   6. PACKAGES: static chip grid, round 1B second pass (2026-09-30).
      Packages are .chip instances inside .chip-rows (styles/chips.css),
      same pattern and same file as Consultations and Therapies now sit
      in, Ryan's call after seeing the belts on the PR 12 preview. No
      photo per package (none exist and the photo rule bars stock faces
      or logos), so .chip-thumb--icon gets a plain tinted surface behind
      an in-file SVG icon instead of a photo. The tagline sits visible
      on the card at rest (.chip-tagline, Ryan wants it always showing);
      the who-for line reuses .chip-more, same hover/focus reveal as
      every other chip on the page. .package-enquire is the only bespoke
      element left, a small text link under the tagline that still
      deep-links to booking.html same as the whole card. The Longevity
      package keeps a feature tint (.chip--feature), the ladder's top
      rung (client ask, "hierarchy laddering to the longevity package").

      Round 1B fifth pass (2026-09-30, "make these cards more attractive
      / animation"): icon tile is a soft sage-to-cream gradient now, not
      a flat tint, with the icon itself in --accent rather than the
      darker --accent-ink (still clears 4.5:1 easily as a body colour,
      see the size/thumb it sits on, and it is a decorative icon rather
      than text regardless). On hover/focus: every icon path/circle
      marked up class="draw" pathLength="1" (index.html; the two
      already-dashed rings on the Mental Health icon carry neither, so
      their own permanent decorative dash stays untouched, not part of
      this effect) draws itself in via stroke-dashoffset, the tile's
      tint deepens, the card lifts 3px with a firmer shadow (its own
      values, not the shared .chip:hover lift, so Packages can sit slightly
      higher than Consultations while still reading as the same
      language: same radius, same shadow colour, just a stronger dose),
      and the Enquire arrow slides 4px right. No new label invented for
      Longevity ("Most complete" or similar does not exist anywhere in
      the source copy); the feature tint alone still carries the
      ladder's top rung, now a little stronger on hover too. Reduced
      motion: no draw, no lift, no arrow slide, the icon simply sits
      fully drawn and static, same as it would after the animation
      finished anyway.

      Round 1B, regression fix (third review): the draw-in above left
      stroke-dashoffset at 1 (fully hidden) at rest, animating to 0 only
      once :hover/:focus-visible/:focus-within actually matched, via a
      transition. Every icon was therefore blank on first paint, only
      the one card under the pointer in a screenshot ever showed
      anything. Icons must read as complete artwork at rest; the draw-in
      is now a decorative flourish that replays on hover, not the only
      thing that ever draws it. Fixed by inverting the resting value
      (dashoffset 0, fully drawn, no transition on it) and swapping the
      hover trigger from a transition to a real @keyframes animation
      (stroke-dashoffset 1 -> 0 over ~500ms): a CSS animation attached
      only by a :hover/:focus rule restarts from its 0% keyframe every
      time that rule starts matching again (leaving hover removes the
      animation entirely, dashoffset falls back to the rest value of 0,
      so nothing is left mid-draw), which is what "restart via a class
      toggle or animation-name swap, not a transition from the resting
      state" needs, no JS retrigger required. */

.chip-thumb--icon {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--accent-tint-soft), var(--bg-card));
  transition: background 260ms var(--ease-reveal);
}
.chip-thumb--icon svg {
  width: 60%;
  height: 60%;
  color: var(--accent);
}
.chip-thumb--icon svg .draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}
@keyframes chip-icon-draw {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}
.chip:hover .chip-thumb--icon svg .draw,
.chip:focus-visible .chip-thumb--icon svg .draw,
.chip:focus-within .chip-thumb--icon svg .draw {
  animation: chip-icon-draw 500ms var(--ease-reveal);
}
.chip:hover .chip-thumb--icon,
.chip:focus-visible .chip-thumb--icon,
.chip:focus-within .chip-thumb--icon {
  background: linear-gradient(135deg, var(--accent-tint), var(--accent-tint-soft));
}

/* .chip.chip--feature, not .chip--feature alone: styles/chips.css
   (loaded after this file, see index.html's <link> order) sets .chip's
   own background/border-color at the same one-class specificity, so
   without the compound selector here the later stylesheet always won
   the tie regardless of source intent, silently, and Longevity never
   actually painted its tint. Found by sampling the rendered pixel
   (matched --bg-card exactly, not the blended colour) rather than
   trusting getComputedStyle, which was also reporting the wrong,
   unresolved value for this color-mix() background. */
.chip.chip--feature {
  background: color-mix(in srgb, var(--accent-tint-soft) 55%, var(--bg-card));
  border-color: var(--accent-tint-deep);
}
.chip--feature .chip-thumb--icon { background: linear-gradient(135deg, var(--accent-tint), var(--accent-tint-soft)); }
.chip--feature:hover .chip-thumb--icon,
.chip--feature:focus-visible .chip-thumb--icon,
.chip--feature:focus-within .chip-thumb--icon {
  background: linear-gradient(135deg, var(--accent-tint-deep), var(--accent-tint));
}

/* .chip-tagline itself moved to styles/chips.css (round 1B fourth
   pass): shared chip-family component, belongs with the rest of the
   chip anatomy rather than duplicated here. */
/* Round 1B, regression fix (second review): Enquire used to just be the
   last thing in normal flow after the tagline and the reveal slot, so
   its own y depended on how tall THOSE happened to be on a given card,
   measured 539px on one card and 561px on another at rest, a card that
   is meant to look like one system reading as visibly inconsistent
   instead. It is position: absolute now, bottom-anchored directly on
   .chip at one fixed inset, same idea as .therapy-card-label
   (styles/round1-home.css, further up this file): left/right are set
   to land exactly under chip-body's own text column (thumb width +
   .chip's gap + .chip's left padding on the left, .chip's own right
   padding on the right, styles/chips.css), so it still reads as part
   of that column, it just no longer moves with whatever is above it. */
/* .chip's shared align-items: center (styles/chips.css) centres the
   thumb + chip-body row within .chip's own content box, height minus
   padding; .package-enquire is pulled out of that flow by position:
   absolute above, so it plays no part in what gets centred. Extra
   bottom padding here, packages only, reserves a footer band below
   that centred content for Enquire to sit in without the two
   overlapping (a plain .chip padding-bottom of 0.75rem, same as every
   other chip, was not enough room for Enquire's own line once the
   surrounding content shrank per item 2's fix). */
.chip-rows--packages .chip {
  padding-bottom: 2.5rem;
}
.package-enquire {
  position: absolute;
  left: 5.6rem;
  right: 1rem;
  bottom: 0.85rem;
  display: inline-block;
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 3px;
  color: var(--ink-soft);
}
.chip:hover .package-enquire,
.chip:focus-visible .package-enquire,
.chip:focus-within .package-enquire { color: var(--ink); }
.package-enquire-arrow {
  display: inline-block;
  transition: transform 220ms var(--ease-reveal);
}
.chip:hover .package-enquire-arrow,
.chip:focus-visible .package-enquire-arrow,
.chip:focus-within .package-enquire-arrow {
  transform: translateX(4px);
}

/* Package cards lift a touch more than the shared .chip hover (2px,
   styles/chips.css) so the row reads as slightly more alive without
   abandoning that shared shadow language: same colour, same radius,
   just one size up. */
.chip-rows--packages .chip:hover,
.chip-rows--packages .chip:focus-visible,
.chip-rows--packages .chip:focus-within {
  transform: translateY(-3px) scale(1.025);
  box-shadow: 0 18px 40px -18px rgba(28, 25, 24, 0.4);
}

@media (prefers-reduced-motion: reduce) {
  .chip-thumb--icon,
  .package-enquire-arrow {
    transition: none;
  }
  .chip:hover .chip-thumb--icon svg .draw,
  .chip:focus-visible .chip-thumb--icon svg .draw,
  .chip:focus-within .chip-thumb--icon svg .draw {
    animation: none;
  }
  /* Higher specificity than chips.css's own .chip:hover { transform:
     none } reduced-motion rule (two classes here vs one there), so it
     needs repeating rather than inherited: without this the package
     row's own, more specific 3px lift would win over that reset. */
  .chip-rows--packages .chip:hover,
  .chip-rows--packages .chip:focus-visible,
  .chip-rows--packages .chip:focus-within {
    transform: none;
  }
}

/* =====================================================================
   7. CLIENT JOURNEY ON THE M: shared by index.html and
      patient-journey.html. The M path is the exact monogram path from
      img/mh-brandmark.svg, stroked and drawn in on reveal.
   ===================================================================== */

.journey-wrap {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: center;
  margin-top: 2.5rem;
}
.journey-m { position: relative; }
.journey-m svg { display: block; width: 100%; height: auto; }
.journey-m-path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 2800;
  stroke-dashoffset: 2800;
  transition: stroke-dashoffset 1.8s var(--ease-reveal) 0.1s;
}
.reveal.is-in .journey-m-path { stroke-dashoffset: 0; }
@media (prefers-reduced-motion: reduce) {
  .journey-m-path { transition: none; stroke-dashoffset: 0; }
}

.journey-node {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1.5px solid var(--accent);
  color: var(--ink);
  font-family: var(--display);
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 16px rgba(24, 24, 24, 0.1);
  cursor: pointer;
  transition: transform 0.3s var(--ease-reveal), background 0.25s ease, color 0.25s ease, box-shadow 0.3s var(--ease-reveal);
}
.journey-node[data-node="1"] { left: 14.8%; top: 86.0%; }
.journey-node[data-node="2"] { left: 37.8%; top: 2.5%; }
.journey-node[data-node="3"] { left: 50.2%; top: 69.4%; }
.journey-node[data-node="4"] { left: 62.8%; top: 1.4%; }
.journey-node[data-node="5"] { left: 85.9%; top: 85.3%; }

/* Filled (passed) state, round 1B scrub. 2026-09-30 fixup: matches the
   fill path's own colour now (var(--accent), the same sage the ghost
   outline's stroke already uses), paper numerals for contrast, rather
   than the ink used before the fill itself became sage: a passed node
   should read as "the same colour as the filled M behind it", not a
   different one. Falls back to the hover/current state below for
   background where both classes land on the same node (source order:
   this rule first, the stronger one after, though both set the same
   background now, that rule's box-shadow/scale/is-active colour is what
   actually differs the current step from a merely-passed one). Only
   ever applied by round1-home.js's scrub script, on index.html;
   patient-journey.html's copy of these nodes never gets it. */
.journey-node.is-filled {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}
.journey-node:hover,
.journey-node:focus-visible,
.journey-node.is-active {
  background: var(--accent);
  color: var(--accent-ink);
  transform: translate(-50%, -50%) scale(1.15);
  box-shadow: 0 10px 22px rgba(24, 24, 24, 0.18);
}
@media (prefers-reduced-motion: reduce) {
  .journey-node { transition: none; }
}

.journey-steps { display: flex; flex-direction: column; gap: 1.1rem; counter-reset: journey-step; }
.journey-step {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 1rem 1.1rem;
  border-radius: var(--radius-md);
  transition: background 0.3s ease;
}
.journey-step:hover, .journey-step.is-active { background: var(--bg-card); }
.journey-step-num {
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--accent-tint);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display);
  font-size: 0.95rem;
}
.journey-step.is-active .journey-step-num { background: var(--accent); }
.journey-step-body h3 { font-size: 1.02rem; margin-bottom: 0.3rem; }
.journey-step-body p { margin: 0; font-size: 0.92rem; color: var(--ink-soft); line-height: 1.5; }

/* 2026-09-30 (round 1B, item 3): the ghost "See the full journey" button
   is gone, Ryan's call, one CTA only under the journey. justify-content
   added so the single remaining button centres in the row instead of
   sitting flush left where the ghost button used to start. The Patient
   journey nav link still reaches patient-journey.html unchanged. */
.journey-cta { margin-top: 2.25rem; display: flex; align-items: center; justify-content: center; gap: 1.25rem; flex-wrap: wrap; }
/* Round 2, 2026-10-06: in scrub mode the CTA is pulled up over the
   sticky block's dead band under the M (--scrub-tail, measured by
   round1-home.js) so it sits 2.25rem below the M at every viewport
   height, and the section's own bottom padding is halved so the map
   section follows at a normal distance instead of a double gap. */
.journey-scrub-section.is-scrub .journey-cta { margin-top: calc(2.25rem - var(--scrub-tail, 0px)); position: relative; z-index: 2; }
.journey-scrub-section.is-scrub { padding-bottom: 3.5rem; }
@media (max-width: 899px) {
  .journey-scrub-section.is-scrub { padding-bottom: 2rem; }
}

@media (max-width: 900px) {
  .journey-wrap { grid-template-columns: 1fr; gap: 2rem; }
  .journey-m { max-width: 22rem; margin: 0 auto; }
}

/* Patient-journey.html's expanded steps reuse the same .journey-step
   classes with longer copy; no extra rule needed beyond the shared ones
   above, since paragraphs wrap naturally regardless of length. */

/* =====================================================================
   7B. CLIENT JOURNEY SCRUB, index.html only, round 1B third pass
   (2026-09-30). New wrapper markup around the shared journey-wrap/-m/
   -node/-steps elements above; every rule here is scoped through
   .journey-scrub-section, the class only index.html's copy of this
   section carries, so patient-journey.html (same journey-wrap/-m/-node/
   -steps classes, no wrapper, no .journey-scrub-section) is completely
   unaffected regardless of what ships here.

   .is-scrub is added by round1-home.js only after feature detection
   (rAF + getBoundingClientRect) and only when the user has not asked
   for reduced motion; without it every selector below is inert and the
   section is the plain static one: no extra height, sticky doing
   nothing, .journey-steps visible, .journey-scrub-card display: none.
   That is also exactly what a no-JS visit gets, nothing here needs JS
   to produce the base layout, only the scrub needs it. ===================================================================== */

.journey-scrub-stage { position: relative; }
.journey-scrub-card { display: none; }

/* Fixup, 2026-09-30: .journey-m carries the shared .reveal/.reveal-2
   classes (opacity 0 -> 1 plus a 12px settle, on a 0.6s transition with
   a 0.12s delay, triggered once by IntersectionObserver or the 2s
   force-reveal fallback in app.js). That one-time fade is fine for
   every other .reveal on the page, but here it raced the fill sweep:
   a fast scroll into the section could land well past the leading edge
   before the *fade* had finished, so the M read as still mostly ghost
   even though the clip was already most of the way across. The scrub
   is its own reveal, so once .is-scrub is live the M skips the fade
   and simply starts fully opaque; higher specificity than the shared
   .reveal.is-in rule, no !important needed.

   Same problem, second source: .journey-m-path (the ghost outline) has
   its OWN separate draw-in, a stroke-dasharray/dashoffset animation
   over 1.8s (plus the .reveal's own 0.12s delay before it can even
   start, since it only fires once .reveal.is-in lands on the ancestor),
   nearly 2s end to end, well past the opacity fade above and easily
   still mid-draw during a normal scroll into the section, not just a
   fast flick. That is what was leaving the far end of the path (the
   loop under node 5) undrawn even after the fill/opacity fix: the
   container was fully opaque and correctly sized, the outline itself
   just was not finished stroking itself in yet. Same treatment: skip
   the draw-in, start fully stroked. */
.journey-scrub-section.is-scrub .journey-m {
  opacity: 1;
  transform: none;
}
.journey-scrub-section.is-scrub .journey-m-path {
  stroke-dashoffset: 0;
  transition: none;
}

.journey-scrub-section.is-scrub .journey-steps {
  /* Standard visually-hidden pattern, not display: none: stays in the
     accessibility tree and in tab order for anyone who still wants the
     plain list, it is just not painted while the card is doing the
     job visually. */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.journey-scrub-section.is-scrub .journey-scrub-card {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 22rem;
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  padding: 2.5rem;
  text-align: center;
}
.journey-scrub-card-inner { max-width: 26rem; }
.journey-scrub-card-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  margin: 0 auto 1.25rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--display);
  font-size: 1.1rem;
}
.journey-scrub-card-heading { font-size: 1.4rem; margin-bottom: 0.6rem; }
.journey-scrub-card-body { margin: 0; color: var(--ink-soft); font-size: 1rem; line-height: 1.6; }

/* Crossfade + small vertical slide, retriggered by round1-home.js
   (remove the class, force a reflow, re-add it) every time the active
   step changes, so it always plays even though the element itself never
   leaves the DOM. */
@keyframes journey-card-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}
.journey-scrub-card-inner.is-changing { animation: journey-card-in 260ms var(--ease-reveal); }

/* Fill M: same path, same transform, as an absolutely-positioned second
   svg on top of the ghost outline. Clipped to a rect whose width
   round1-home.js drives from 0 to the viewBox width (412.26) as the
   user scrolls.

   2026-09-30 fixup, second review: fill is var(--accent) now, the exact
   same token the ghost outline's own stroke uses (styles/round1-home.css,
   .journey-m-path), not a separate ink hex, so the two always match by
   definition rather than by coincidence. The original build used ink
   ("a black vertical line that sweeps") with a thin scanline rect
   marking the leading edge; Ryan's second pass dropped both: same olive
   as the outline, and no scan line at all, just the clean fill growing
   left to right. */
.journey-m .journey-m-fill-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Hidden until .is-scrub actually switches it on: opacity 0 keeps a
     no-JS/reduced-motion visit from ever seeing this second, otherwise
     invisibly-clipped-to-zero-width, copy of the M. Nothing here needs
     a transition, JS only ever flips this class alongside setting real
     progress values on the same frame. */
  opacity: 0;
}
.journey-scrub-section.is-scrub .journey-m-fill-svg { opacity: 1; }
.journey-m-fill-path { fill: var(--accent); }

@media (min-width: 900px) {
  .journey-scrub-section.is-scrub .journey-scrub-stage {
    /* 5 steps, 90vh of scroll each: enough runway per step to feel
       scrubbed rather than skipped, short enough not to feel like a
       stuck page. */
    height: 450vh;
  }
  .journey-scrub-section.is-scrub .journey-scrub-sticky {
    position: sticky;
    top: 6rem;
    height: calc(100vh - 6rem);
    display: flex;
    align-items: center;
  }
  .journey-scrub-section.is-scrub .journey-wrap { width: 100%; }
}

/* Mobile scrub: shipped as the same idea stacked (option A of the two
   offered), not the M-pinned-with-plain-list-scrolling-under fallback,
   because the numbers work: nav clearance (~6rem) + M at 38vh (~321px
   at 844 tall) + the card at its 22rem min-height (~352px) plus a
   1.25rem gap between them comes to about 800px, inside 844 with room
   to spare, so the full scrub fits without the fallback's compromise.
   Checked at 390x844 specifically, the narrowest real target. */
@media (max-width: 899px) {
  .journey-scrub-section.is-scrub .journey-scrub-stage {
    height: 450vh;
  }
  .journey-scrub-section.is-scrub .journey-scrub-sticky {
    position: sticky;
    top: 5rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0.5rem 0;
  }
  .journey-scrub-section.is-scrub .journey-wrap {
    display: block;
  }
  /* 2026-09-30 fixup: .journey-m used to get height: 38vh with its width
     left to fill the column, an arbitrary box that did not match the M's
     own 412.26:276.79 viewBox ratio. The node buttons are positioned by
     left/top percentages of THIS box, calibrated against the M's real
     shape, so any mismatch between the box's ratio and the svg's own
     sent the nodes drifting off the drawn line, and the ghost svg
     (aspect-ratio preserved, so it letterboxed inside the mismatched
     box) stopped lining up with the fill svg (which stretched to fill
     the same box edge to edge, ignoring aspect ratio). Setting
     aspect-ratio here directly, alongside the height, makes the box
     itself exactly the M's own proportions, so both svgs at a plain
     100% x 100% and every node's percentage position all agree again,
     same as they already do on desktop, where .journey-m's height was
     always auto and so always implicitly matched the svg's ratio. */
  .journey-scrub-section.is-scrub .journey-m {
    max-width: none;
    /* Fit within BOTH a height budget (34vh) and the column's own
       width, whichever is tighter, same idea as object-fit: contain:
       34vh converted to the equivalent width via the M's own ratio,
       capped at 100% of the column so it can never overflow sideways
       the way a bare height: 34vh (width free to blow past the column)
       did before. */
    width: min(100%, calc(34vh * 1.48941));
    aspect-ratio: 412.26 / 276.79;
    height: auto;
    margin: 0 auto;
  }
  .journey-scrub-section.is-scrub .journey-m-ghost-svg,
  .journey-scrub-section.is-scrub .journey-m-fill-svg {
    width: 100%;
    height: 100%;
  }
  .journey-scrub-section.is-scrub .journey-right { flex: 1 1 auto; min-width: 0; }
  /* Fixup: the card's 22rem desktop min-height put its bottom edge
     under the fixed WhatsApp pill's band (pill sits at bottom: 5rem
     plus its own ~2.5rem height, roughly the bottom 6.5rem of the
     viewport, verified empirically rather than assumed). Shrinking the
     card on mobile only, plus a little breathing room under it, keeps
     every step's text clear of that band at every scroll progress, not
     just the shortest step's. */
  .journey-scrub-section.is-scrub .journey-scrub-card {
    min-height: 14rem;
    padding: 1.75rem;
  }
  .journey-scrub-card-heading { font-size: 1.2rem; }
  .journey-scrub-section.is-scrub .journey-scrub-sticky { padding-bottom: 5.5rem; }
}
