/* ==========================================================================
   Components — mobile-first. Each block is self-contained and reusable
   across pages; page-specific composition lives in the page CSS.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Button
   .button            solid primary (default)
   .button--ghost     outlined, transparent
   .button--on-dark   outlined white, for dark panels
   ----------------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 13px;
  min-height: var(--button-min-height);
  padding: var(--button-padding);
  /* A hairline, not a 2px outline: the rim is meant to read as light on an
     edge, not as a drawn border. box-sizing is border-box, so dropping from
     2px changes no dimensions. */
  border: 1px solid var(--button-rim);
  border-radius: var(--button-radius);
  /* Flat and even VERTICALLY — a top-to-bottom gradient reads as a moulded
     surface, which is the aqua-button look. The refraction below is a
     different animal: see --button-refraction. */
  background-color: var(--primary);
  background-image: var(--button-refraction);
  color: #fff;
  font-family: inherit;
  font-size: var(--button-font-size);
  font-weight: var(--button-font-weight);
  text-transform: var(--button-text-transform);
  letter-spacing: -0.005em;
  text-align: center;
  text-decoration: none;
  /* A phone number must never break across lines. */
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--button-shadow);
  transition: background-color var(--transition), color var(--transition),
    border-color var(--transition), box-shadow var(--transition),
    transform var(--transition);
}

/* Faux refraction — the fill's own gradient, and the thing that finally read
   as glass rather than plastic. Three properties separate it from the sheen
   that was tried and reverted:

   1. It is a HUE drift, not a luminance one. Nothing here is white. The
      corners are the same blue pushed toward cyan and toward violet, at
      roughly matched lightness. A white highlight is a specular reflection
      and reads as gloss; a hue split across a plane is dispersion, which is
      the one optical signature plastic cannot fake.
   2. It runs on a diagonal, not top-to-bottom. Vertical is what the token
      comment warns about — the eye reads a vertical light ramp as a curved,
      moulded face. An oblique one reads as a flat pane lit from off-axis.
   3. It is low contrast with a wide dead zone in the middle (the 0-alpha
      stop at 48%), so the surface stays matte. Frosted glass scatters; it
      does not have a hot spot.

   Both accent stops are drawn from the existing palette's neighbourhood
   rather than introducing a new colour, so the fill still reads as --primary.

   Per-variant, because the light fills need the opposite treatment: they pick
   up the blue of the panel behind them instead of splitting their own hue. */
.button {
  --button-refraction: linear-gradient(
    118deg,
    rgb(70 180 226 / 0.2) 0%,
    rgb(44 102 177 / 0) 48%,
    rgb(52 82 158 / 0.32) 100%
  );
}

/* No sheen layer here, and this is the second time that has been decided.
   A radial highlight centred above the top edge was tried and reverted: at
   any strength where it was visible on the blue fill it read as moulded
   plastic, and on the near-white variants it was invisible, so it bought
   nothing anywhere. The tokens file predicts exactly this — "any vertical
   gradient here reads as a moulded surface".

   The lit edge this component wants is already present, and it is the 1px
   --button-rim border. Light on the FACE is what makes plastic; light on the
   EDGE is what makes glass. Anything added here should stay in the border. */
.button:hover {
  background-color: var(--primary-hover);
  color: #fff;
}

/* Hover deepens the shadow without moving the button. A translate would put
   the plastic back: glass under a finger does not pop up to meet it. */
@media (hover: hover) {
  .button:hover {
    box-shadow: var(--button-shadow-hover);
  }
}

/* The press is a scale, not a drop. Everything shrinks towards the centre
   together, which is what direct manipulation feels like on a flat surface. */
.button:active {
  transform: scale(0.985);
  box-shadow: var(--button-shadow-pressed);
}

.button svg,
.button img,
.button__icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  fill: currentColor;
  /* Optical centring — see note above; the line box is not the ink centre. */
  transform: translateY(0.05em);
}

/* Masked icon (see the button macro): the shape comes from the SVG, the
   colour from the button, so one file works on every variant. */
.button__icon {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-image: var(--button-icon);
  mask-image: var(--button-icon);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

/* Secondary, and the one place the material is literal: a pane of frosted
   glass over whatever it covers. Translucent fill plus a blur, a rim tinted
   with the brand blue instead of white, and a fraction of the primary's
   shadow — so the pair reads as one button in front and one behind. */
.button--ghost {
  background-color: rgb(255 255 255 / 0.55);
  border-color: rgb(44 102 177 / 0.3);
  color: var(--primary);
  box-shadow: var(--button-shadow-quiet);
  /* Near-white fill: a hue split of its own would go grey. It picks up the
     brand blue instead, which is what a pale pane over a blue ground does. */
  --button-refraction: linear-gradient(
    118deg,
    rgb(44 102 177 / 0.05) 0%,
    rgb(44 102 177 / 0) 50%,
    rgb(44 102 177 / 0.08) 100%
  );
}

/* No backdrop-filter, on purpose — it used to carry var(--glass-blur) and it
   was doing nothing.
   
   The only place this variant renders is .actionbar__go, inside a bar that
   already blurs its own backdrop AND fills at 88% white, and which overrides
   this fill to 90%. Page content reaching the button is 0.10 x 0.12 — about
   1% of the final pixel, already blurred once by the bar. A second, nested
   blur re-filters that 1%.
   
   That is also the worst place to spend it: the bar is fixed, so its backdrop
   re-evaluates on every scrolled frame, and the nested pass rides along.
   
   Re-add it only if this variant is used somewhere with genuinely varied
   content behind it and a fill light enough to see through — over a photo or
   the hero panel, not over another frosted surface. */

.button--ghost:hover {
  background-color: rgb(255 255 255 / 0.75);
  border-color: rgb(44 102 177 / 0.5);
  color: var(--primary);
}

@media (hover: hover) {
  .button--ghost:hover {
    box-shadow: var(--button-shadow-quiet-hover);
  }
}

.button--ghost:active {
  box-shadow: var(--button-shadow-pressed);
}

/* On a dark panel the same material, tuned the other way: nearly opaque
   white so the label keeps its contrast, a brighter rim because there is
   more light to catch, and a near-black shadow — a navy-tinted one would
   vanish into the panel it is cast on. */
.button--on-dark {
  background-color: rgb(255 255 255 / 0.92);
  border-color: rgb(255 255 255 / 0.5);
  color: var(--primary);
  /* Stronger than --ghost's: this one sits directly on the blue CTA panel, so
     there is more colour for it to be picking up. */
  --button-refraction: linear-gradient(
    118deg,
    rgb(44 102 177 / 0.07) 0%,
    rgb(44 102 177 / 0) 46%,
    rgb(44 102 177 / 0.12) 100%
  );
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.1),
    0 12px 28px -14px rgb(0 0 0 / 0.65);
}

/* No backdrop-filter here either, and for a different reason: blurring a flat
   colour returns the same flat colour. Every instance of this variant sits in
   .cta__body, and .cta is a single background-color — there is no detail
   behind it for a blur to move. The 8% translucency still tints the fill with
   the panel underneath; that part is doing work, the filter was not.

   If the CTA panel ever gets artwork or a gradient behind the copy, this
   becomes worth restoring. */

.button--on-dark:hover {
  background-color: #fff;
  color: var(--primary);
}

@media (hover: hover) {
  .button--on-dark:hover {
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.12),
      0 18px 36px -16px rgb(0 0 0 / 0.7);
  }
}

.button--on-dark:active {
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.14);
}

/* The secondary action on a dark panel. --on-dark is a near-solid white pill;
   two of them side by side are two primary buttons, and the panel ends up
   asking for two things at once. This is the same shape drawn in outline, so
   the pair reads as "do this, or if you're not ready, this" — the same
   relationship .button--ghost has to .button on a light background.

   No blur, for the reason given above the --on-dark rule: there is nothing
   behind a flat panel for a filter to move. */
.button--on-dark-quiet {
  background-color: rgb(255 255 255 / 0.08);
  border-color: rgb(255 255 255 / 0.42);
  color: #fff;
  box-shadow: none;
}

.button--on-dark-quiet:hover {
  background-color: rgb(255 255 255 / 0.16);
  border-color: rgb(255 255 255 / 0.7);
  color: #fff;
}

/* The masked icon paints in currentColor, so it follows the label to white
   without a rule of its own. */

/* The press is a response to a touch, not decoration, so the scale stays —
   it simply arrives instantly instead of easing in. */
@media (prefers-reduced-motion: reduce) {
  .button {
    transition: background-color var(--transition), color var(--transition),
      border-color var(--transition), box-shadow var(--transition);
  }
}

/* Denser variant for tight contexts (the sticky action bar). */
.button--compact {
  min-height: var(--button-min-height-compact);
  padding: 0.5rem 1rem;
  font-size: 1.0625rem;
  text-transform: none;
  letter-spacing: 0.01em;
  gap: 0.5rem;
}

.button--compact svg,
.button--compact img,
.button--compact .button__icon {
  width: 1.25rem;
  height: 1.25rem;
}

/* A row of buttons that stacks on narrow screens. */
.button-group {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
}

/* A single action closing a section.

   The margin is deliberately smaller than --section-gap: this button belongs
   to the content above it, and once it sits in a gap the size of the one
   separating whole sections it reads as floating between them. */
.section-actions {
  justify-content: center;
  margin-top: 1.5rem;
}

/* An action closing a feature row's copy. Alignment is left to .button-group
   — centred while the row is stacked, left-aligned once the copy is in its
   own column — which is what the inline style this replaces did. */
.feature__actions {
  margin-top: 2rem;
}

@media only screen and (min-width: 62.5em) {
  .button-group {
    justify-content: flex-start;
  }

  /* Stays centred at every width, unlike .button-group. Declared after it,
     and in the same query, because the two carry equal specificity and the
     later one is the only reason this wins — the inline style it replaces
     did the same job by outranking both. */
  .section-actions {
    justify-content: center;
    margin-top: 2.5rem;
  }
}

/* --------------------------------------------------------------------------
   Section heading — centred topper + title + lede
   ----------------------------------------------------------------------- */

.section-heading {
  text-align: center;
  margin-bottom: 2.5rem;
}

.section-heading__title {
  font-size: var(--h3);
  margin-bottom: 10px;
}

.section-heading__text {
  max-width: var(--container-narrow);
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
   Service card — the whole card is the link, so it carries no button.
   Horizontal on small screens (icon beside the title) to save vertical
   space; upright and centred once there is room for three across.
   ----------------------------------------------------------------------- */

.card-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
  list-style: none;
}

@media only screen and (min-width: 62.5em) {
  .card-grid {
    gap: 25px;
  }

  .card-grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Tablet tier. Between the phone and the desktop the page had no layout of
   its own — one column all the way to 999px — so a 900px window showed
   phone furniture at double width. Two up is the honest middle.

   Bounded at both ends on purpose: an unbounded min-width would sit after
   the three-column rule above and win at every width, since the two carry
   equal specificity. */
@media only screen and (min-width: 43.125em) and (max-width: 62.4375em) {
  .card-grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Three reviews in two columns orphans the third into a half-width row with
     a hole beside it. That was tolerable while the quotes were one-liners and
     the cards were ~120px tall; at real review length the gap is the size of a
     card. The orphan spans the full row instead and reads as a pull quote.

     Scoped to this breakpoint deliberately. The same selector at the desktop
     tier would match the third of three and span it across all three columns,
     which is the bug rather than the fix.

     :last-child:nth-child(odd) generalises — with four reviews the last is
     even and stays in its track, so adding a second row does not need this
     revisited. */
  .review-grid > .review:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

.card {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
  padding: 18px 16px;
  border: 2px solid var(--surface-tint);
  border-radius: var(--radius-panel);
  background-color: var(--surface-tint);
  text-align: left;
  text-decoration: none;
  color: inherit;
  transition: background-color var(--transition), border-color var(--transition),
    transform var(--transition);
}

.card:hover,
.card:focus-visible {
  background-color: var(--surface);
  border-color: var(--primary);
  color: inherit;
}

.card:active {
  transform: scale(0.99);
}

.card__icon {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  color: var(--color-1);
}

.card__icon img,
.card__icon svg {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.card__body {
  flex: 1;
  min-width: 0;
}

.card__title {
  display: block;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--heading);
  margin: 0 0 2px;
}

.card__text {
  display: block;
  font-size: 0.875rem;
  line-height: 1.5;
  margin: 0;
}

/* Shared "this row is tappable" affordance. Used by the service cards and by
   the mobile nav rows, so a row that leads somewhere looks the same wherever
   it appears. */
.chev {
  flex-shrink: 0;
  /* &rsaquo; carries a lot of side bearing and draws small for its point
     size, so it needs to be set larger than the text beside it to read as
     the same weight of mark. */
  font-size: 1.875rem;
  line-height: 1;
  color: var(--primary);
  transition: transform var(--transition);
}

.card:hover .chev,
.menu-row:hover .chev {
  transform: translateX(3px);
}

/* On the smallest screens the description is the first thing to go — the
   title plus the icon already say what the card is. */
@media only screen and (max-width: 22.5em) {
  .card__text {
    display: none;
  }
}

@media only screen and (min-width: 62.5em) {
  .card {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0;
    padding: 45px 35px;
    background-color: transparent;
  }

  .card__icon {
    width: 50px;
    height: 50px;
    margin-bottom: 30px;
  }

  .card__title {
    font-size: var(--h4);
    margin-bottom: 10px;
  }

  .card__text {
    font-size: 1rem;
  }

  .card .chev {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Feature row — image beside copy, alternating sides
   ----------------------------------------------------------------------- */

.feature {
  display: grid;
  gap: 2rem;
  align-items: center;
  grid-template-columns: 1fr;
}

.feature__media img,
.feature__media picture {
  width: 100%;
  border-radius: var(--radius-panel);
}

.feature__title {
  font-size: var(--h2);
}

/* Stacked, the image leads. It is the fastest thing on the row to read and
   it establishes what the section is about before the heading names it —
   below the copy it arrives as an afterthought, and pushes the next section
   further out of reach. The side-by-side layouts below set their own order. */
@media only screen and (max-width: 62.4375em) {
  .feature__media {
    order: -1;
  }

  /* Proximity: at the 2rem the row carries by default, the space above the
     image (--section-gap, 50px) and the space below it were close enough
     that the image read as floating between two sections rather than
     belonging to the copy under it. Tightening its own gap to 20px makes
     the separation from the section above 2.5x the separation from its own
     heading, which is what assigns it to the right one. */
  .feature {
    gap: 1.25rem;
  }
}

@media only screen and (min-width: 62.5em) {
  .feature {
    gap: 3.5rem;
  }

  /* Side by side, the image sets the row's height, so a portrait source
     leaves the copy stranded in the middle of a very tall column — the gym
     photo came in at 685px against 364px of copy. This holds every feature
     image to a band and crops rather than letterboxes, so the shape of the
     source stops deciding the layout. Landscape sources never reach it. */
  .feature__media img {
    max-height: 30rem;
    object-fit: cover;
  }

  /* Copy narrow, media wide. */
  .feature--media-wide {
    grid-template-columns: 1fr 2fr;
  }

  /* Even split. */
  .feature--even {
    grid-template-columns: 1fr 1fr;
  }

  /* Media first in the source order but second visually, or vice versa. */
  .feature--reverse .feature__media {
    order: -1;
  }
}

/* --------------------------------------------------------------------------
   Testimonial — video embed + quote + attribution
   ----------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Reviews — the logos, the rating, the count
   The section leads with its evidence instead of a heading and a lede. The
   logos come first: they are what a reader recognises without reading, and
   they say where the reviews are. The stars and the count caption them.
   ----------------------------------------------------------------------- */

/* A fixed five-star mark. The SVG is masked rather than loaded as an image so
   it takes currentColor — the file's own fill is an off-palette orange left
   over from the starter kit, and this way one file serves any context.
   Sized by height so it scales with the type around it. */
.stars {
  display: block;
  flex-shrink: 0;
  height: 1.125rem;
  aspect-ratio: 91 / 15;
  background-color: currentColor;
  color: var(--accent-warm);
  -webkit-mask-image: url("../assets/svgs/stars.svg");
  mask-image: url("../assets/svgs/stars.svg");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.reviews-intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  /* Deliberately tighter than the gap to the section above: this block is
     the reviews' header, not a thing in its own right. At 2.5rem it sat
     64px clear of the first quote — its own margin plus the card's padding
     — against 50px from the section above, and so read as belonging to the
     section it follows. The card padding does most of the work here. */
  margin-bottom: 0.5rem;
  text-align: center;
}

/* The logos lead. Height-normalised: these are three very differently shaped
   marks — two square glyphs and a wide wordmark — and a common height is
   what makes them read as a row of equals rather than one big logo with two
   small ones beside it. The wordmark ends up much wider, which is correct;
   logo rows are normalised on height, never on width. */
.sources__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem 2rem;
  list-style: none;
}

.sources__item img {
  display: block;
  width: auto;
  height: 1.75rem;
  object-fit: contain;
}

/* Until a wordmark file is dropped in, a platform renders as text. */
.sources__name {
  font-size: 1.0625rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--color-4);
}

.sources__link {
  display: block;
  text-decoration: none;
}

.sources__link:hover .sources__name,
.sources__link:focus-visible .sources__name {
  color: var(--primary);
}

/* Caption to the logos: the rating, then the count. */
.reviews-intro__stars {
  height: 1.5rem;
  color: var(--primary);
}

/* Subtext. A supporting figure, not a claim — at bold it competed with the
   logos it exists to annotate. */
.reviews-intro__count {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--color-3);
}

/* The cards have visible edges at this width, so their padding no longer
   reads as gap and the margin has to supply it. Declared after the base rule
   because the two carry equal specificity and later wins. */
@media only screen and (min-width: 62.5em) {
  .reviews-intro {
    margin-bottom: 1.75rem;
  }
}

/* --------------------------------------------------------------------------
   Review card
   Laid out like the treatment cards: full-bleed stacked rows on a phone,
   three upright cards once there is room. Content is written in home.js.
   ----------------------------------------------------------------------- */

.review {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  padding: 1.5rem;
  border: 2px solid var(--surface-tint);
  border-radius: var(--radius-panel);
  background-color: var(--surface-tint);
}

.review__quote {
  /* The quote is the card, so it gets the size and sets the height. The
     quotation marks are added here rather than typed into the content, so
     the copy stays clean and every card is punctuated the same way. */
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--color-4);
  /* Reviews vary wildly in length. Pushing the author down keeps the
     attribution on one line across a row of uneven cards. */
  margin: 0 0 auto;
}

.review__quote::before {
  content: open-quote;
}

.review__quote::after {
  content: close-quote;
}

.review__author {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding-top: 0.25rem;
}

.review__avatar {
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border-radius: 50%;
  object-fit: cover;
  background-color: var(--color-6);
}

.review__meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.review__name {
  font-weight: 700;
  color: var(--color-4);
  line-height: 1.3;
}

.review__time {
  font-size: 0.8125rem;
  color: var(--color-3);
}

/* --------------------------------------------------------------------------
   CTA panel — dark rounded band with copy and an illustration
   ----------------------------------------------------------------------- */

.cta {
  display: grid;
  gap: 2rem;
  align-items: center;
  grid-template-columns: 1fr;
  padding: 40px 25px;
  border-radius: var(--radius-panel);
  background-color: var(--primary);
  color: #fff;
  text-align: center;
}

.cta__title,
.cta__text {
  color: #fff;
}

.cta__title {
  margin-bottom: 0.75rem;
}

.cta__text {
  /* The panel is the full width of the page here, and a line of text that
     wide is unreadable. This holds it to a comfortable measure under the
     heading rather than letting it run the whole panel. */
  max-width: 34ch;
  margin-inline: auto;
}

/* The actions followed the copy's alignment via `justify-content: inherit`,
   which does not do that — `inherit` takes the grid parent's own value, which
   is `normal`, so the button sat hard left under centred text at every width.
   These are the two values it was meant to have. */
.cta__actions {
  justify-content: center;
  margin-top: 1.75rem;
}

.cta__media img {
  width: 100%;
  max-width: 420px;
  margin-inline: auto;
}

@media only screen and (min-width: 62.5em) {
  .cta {
    grid-template-columns: 1fr 1fr;
    padding: 60px 70px;
    text-align: left;
  }

  .cta__text {
    margin-inline: 0;
  }

  .cta__actions {
    justify-content: flex-start;
  }

  .cta__media img {
    margin-inline: 0 auto;
  }
}

/* --------------------------------------------------------------------------
   Page title — used only where the h1 earns its space by introducing
   something that isn't self-evident. Pages whose content speaks for itself
   (the treatment tiles, the post grid) carry a visually-hidden h1 instead.
   ----------------------------------------------------------------------- */

.page-title {
  font-size: var(--h2);
  color: var(--color-4);
  margin-bottom: 8px;
}

/* --------------------------------------------------------------------------
   Tile — image with a caption, used for the treatments index
   ----------------------------------------------------------------------- */

.tile-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(2, 1fr);
  list-style: none;
}

@media only screen and (min-width: 43.125em) {
  .tile-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 25px;
  }
}

@media only screen and (min-width: 62.5em) {
  .tile-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.tile {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  border-radius: var(--radius-panel);
  background-color: var(--surface-tint);
  text-decoration: none;
  transition: transform var(--transition), box-shadow var(--transition);
}

.tile:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgb(16 33 54 / 0.12);
}

.tile__media {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

.tile__media img,
.tile__media picture {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile__label {
  display: block;
  flex: 1;
  padding: 13px 14px;
  margin: 0;
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--heading);
  text-align: center;
}

/* --------------------------------------------------------------------------
   Form
   ----------------------------------------------------------------------- */

.form {
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}

@media only screen and (min-width: 43.125em) {
  .form {
    grid-template-columns: 1fr 1fr;
  }

  /* Fields that should always take the full row. */
  .field--wide {
    grid-column: 1 / -1;
  }
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--color-4);
}

.field input,
.field textarea {
  width: 100%;
  min-height: var(--field-height);
  padding: 12px 16px;
  border: var(--field-border-width) solid var(--border);
  border-radius: var(--field-radius);
  background-color: var(--surface);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 400;
  color: var(--text);
  transition: border-color var(--transition);
}

.field textarea {
  min-height: 140px;
  resize: vertical;
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--color-1);
}

.field input::placeholder,
.field textarea::placeholder {
  color: #9aa8b8;
}

.form__actions {
  grid-column: 1 / -1;
}

/* A disabled submit still needs to read as a message, not a dead control.
   `white-space: normal` overrides the nowrap that .button carries for phone
   numbers — this label is a sentence and must wrap, or it forces the whole
   form wider than the screen. */
.form__actions .button[disabled] {
  white-space: normal;
  background-color: var(--color-5);
  color: var(--color-3);
  cursor: not-allowed;
  text-transform: none;
  font-weight: 400;
  line-height: 1.4;
  padding: 15px 25px;
  /* Flat on the page: elevation is what says a control can be pressed, and
     this one cannot. */
  border-color: transparent;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   Info list — labelled contact details
   ----------------------------------------------------------------------- */

.info-list {
  display: grid;
  gap: 22px;
  list-style: none;
}

.info-list__label {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-3);
  margin-bottom: 2px;
}

.info-list__value {
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--heading);
}

/* --------------------------------------------------------------------------
   Map embed
   ----------------------------------------------------------------------- */

.map-embed {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-panel);
  overflow: hidden;
  background-color: var(--color-5);
}

.map-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

@media only screen and (min-width: 62.5em) {
  .map-embed {
    aspect-ratio: 21 / 8;
  }
}

/* --------------------------------------------------------------------------
   Post card — blog listing entry
   ----------------------------------------------------------------------- */

.post-grid {
  display: grid;
  gap: 30px;
  grid-template-columns: 1fr;
  list-style: none;
}

@media only screen and (min-width: 43.125em) {
  .post-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.post-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 2px solid var(--border);
  border-radius: var(--radius-panel);
  transition: transform var(--transition), box-shadow var(--transition);
}

.post-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgb(16 33 54 / 0.12);
}

.post-card__media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.post-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.post-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 25px;
}

.post-card__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.8125rem;
  color: var(--color-3);
  margin-bottom: 10px;
}

.post-card__meta img {
  width: 28px;
  height: 28px;
  border-radius: 50%;
}

.post-card__dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0.5;
}

.post-card__title {
  font-size: var(--h4);
  margin-bottom: 10px;
}

.post-card__link {
  margin-top: auto;
  font-weight: 700;
  color: var(--primary);
}

.post-card__link::after {
  content: " →";
}

/* --------------------------------------------------------------------------
   Prose — long-form article body
   ----------------------------------------------------------------------- */

.prose {
  max-width: var(--container-narrow);
}

.prose > * + * {
  margin-top: var(--content-gap);
}

.prose h2 {
  font-size: var(--h3);
  margin-top: 1.8em;
}

.prose h3 {
  font-size: var(--h4);
  margin-top: 1.6em;
}

.prose img {
  border-radius: var(--radius-panel);
}

.prose ul,
.prose ol {
  padding-left: 1.4em;
}

.prose li + li {
  margin-top: 0.5em;
}

.prose blockquote {
  margin: 0;
  padding: 18px 25px;
  border-left: 4px solid var(--primary);
  border-radius: 0 var(--field-radius) var(--field-radius) 0;
  background-color: var(--surface-tint);
  font-style: italic;
}

/* --------------------------------------------------------------------------
   Sidebar — featured posts beside an article or listing
   ----------------------------------------------------------------------- */

.with-sidebar {
  display: grid;
  gap: 50px;
  grid-template-columns: 1fr;
  align-items: start;
}

@media only screen and (min-width: 62.5em) {
  .with-sidebar {
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 60px;
  }
}

.sidebar__heading {
  font-size: var(--h4);
  padding-bottom: 12px;
  margin-bottom: 20px;
  border-bottom: 2px solid var(--border);
}

.sidebar__list {
  display: grid;
  gap: 18px;
  list-style: none;
}

.sidebar__item {
  display: flex;
  gap: 14px;
  align-items: center;
}

.sidebar__item img {
  width: 60px;
  height: 60px;
  border-radius: var(--field-radius);
  object-fit: cover;
  flex-shrink: 0;
}

.sidebar__item-title {
  display: block;
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--heading);
  margin: 0 0 4px;
}

.sidebar__item-date {
  display: block;
  font-size: 0.8125rem;
  color: var(--color-3);
}

/* --------------------------------------------------------------------------
   Full-bleed on small screens
   Side margins that read as generous on desktop are just wasted width on a
   phone. `.bleed-mobile` breaks an element out of its container up to the
   full viewport and drops the rounding that only makes sense inset.
   ----------------------------------------------------------------------- */

/* Phone only — deliberately NOT the same bound as the sticky bar and the
   burger nav, which run to 999px. Breaking content out to the full viewport
   is right at 390px and wrong at 800px: the row lists stretch to a line
   length nobody can read, and the chevron ends up marooned half a screen
   from the label it belongs to. Above this the content returns to the
   container and the cards go back to being cards. */
@media only screen and (max-width: 43.0625em) {
  /* The class is repeated on purpose. This is a utility that has to beat the
     component it is applied to, and both are single classes — so at equal
     specificity the winner is whichever rule appears later in the file. That
     held while every component that used this was declared above it, and
     stopped holding the moment one was appended below: .spotlight and .banner
     both kept their corner radius and rendered as rounded panels butted
     against the edges of the screen, which is the exact thing this exists to
     prevent.

     Doubling the selector takes it to (0,2,0) and makes it independent of
     source order, so a component added at the foot of this file next year
     cannot quietly break it. */
  .bleed-mobile.bleed-mobile {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    border-radius: 0;
  }

  /* Cards go from separate rounded boxes to one full-bleed list — the
     pattern phones already use for tappable rows. Cheapest possible
     vertical cost.

     Separated by their own padding, with no rules. This was a hairlined
     list, but only the two rules closing it top and bottom ever painted:
     the per-row border was cancelled by a `.card:last-child` reset that
     matched every card, since each `.card` is the only child of its `<li>`.
     So the rendered design was always two lines bracketing a plain list —
     the top one landing directly under the section title, where it read as
     a rule underlining the heading. Dropping all of it is what the list
     looked like anyway, minus the brackets. */
  .card-grid.bleed-mobile {
    gap: 0;
  }

  .card-grid.bleed-mobile .card,
  .card-grid.bleed-mobile .review {
    border-radius: 0;
    border: 0;
    background-color: var(--surface);
    padding-inline: 5vw;
  }

  /* Rows of the same list need something between them once the card edges
     are gone. The quote cards are the one place on the page where that is
     true — the treatment rows have an icon and a chevron to separate them. */
  .card-grid.bleed-mobile .review + .review {
    border-top: 1px solid var(--border);
  }
}

/* --------------------------------------------------------------------------
   Sticky action bar — mobile only

   Order is actions -> hours -> address: most actionable nearest the thumb,
   supporting detail below it. Sized in rem and with no fixed heights, so it
   grows correctly when the reader has scaled their font up. Its real height
   is published as --actionbar-h by actionbar.js and used to pad the page.

   Translucent with a blur so page content stays visible behind it — that is
   what signals there is more to scroll rather than the page ending here.
   ----------------------------------------------------------------------- */

.actionbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Above #navigation (10000) so the full-height menu sheet passes behind
     the bar rather than over it. They never overlap the header visually. */
  z-index: 10001;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.7rem 4vw calc(0.7rem + env(safe-area-inset-bottom, 0px));
  background-color: rgb(255 255 255 / 0.88);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 20px rgb(16 33 54 / 0.10);
  transition: gap 0.25s ease, padding 0.25s ease;
}

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .actionbar {
    -webkit-backdrop-filter: blur(12px) saturate(1.6);
    backdrop-filter: blur(12px) saturate(1.6);
  }
}

/* A short scrim above the bar so content fades under it instead of being
   sliced off — the second half of the "there is more below" cue.

   The stops are eased rather than linear. A straight ramp puts its steepest
   change at the very top of the band, which the eye picks up as an edge —
   that faint horizontal line was most of what made the effect obvious. This
   curve keeps the opacity concentrated near the bar and lets the last third
   trail off almost flat, so there is no point where the scrim visibly
   begins. It ends at the bar's own 0.88 so the two read as one surface.

   The one exception is the navy footer, where the bar is already the
   footer's own edge and there is nothing left to fade. */
.actionbar::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 2.5rem;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgb(255 255 255 / 0.88) 0%,
    rgb(255 255 255 / 0.82) 14%,
    rgb(255 255 255 / 0.68) 30%,
    rgb(255 255 255 / 0.5) 46%,
    rgb(255 255 255 / 0.32) 62%,
    rgb(255 255 255 / 0.17) 76%,
    rgb(255 255 255 / 0.06) 89%,
    rgb(255 255 255 / 0) 100%
  );
}

.actionbar__actions {
  display: flex;
  align-items: stretch;
  gap: 0.625rem;
  /* Safety valve: at very large user font sizes the two buttons stop fitting
     side by side. Wrapping stacks them full-width rather than clipping. */
  flex-wrap: wrap;
}

/* Shared button shape. min-height in rem keeps the 44px touch target when
   the base font grows, instead of pinning it to a px value. */
/* Layout only — appearance comes from .button / .button--ghost, so the bar
   uses the same component as the rest of the site rather than a lookalike. */
.actionbar__go {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 0.875rem;
  border-color: var(--border);
  background-color: rgb(255 255 255 / 0.9);
}

.actionbar__go:hover,
.actionbar__go:focus-visible {
  border-color: var(--primary);
  background-color: var(--surface-tint);
}

.actionbar__call {
  flex: 1 1 10rem;
  min-width: 0;
}

.actionbar__info {
  text-align: center;
  overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease;
  max-height: 4rem;
  opacity: 1;
}

.actionbar__hours {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--color-4);
}

.actionbar__address {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--color-3);
}

/* Narrow phones (<=360px): there is not room for both labels beside the
   number, so Directions falls back to its arrow. The aria-label still names
   it, and the label returns as soon as there is room. */
@media only screen and (max-width: 22.5em) {
  .actionbar__go span {
    display: none;
  }

  .actionbar__go {
    padding-inline: 0.9rem;
  }
}

/* Collapsed while scrolling down: the detail folds away, both buttons stay. */
.actionbar.is-compact {
  gap: 0;
}

.actionbar.is-compact .actionbar__info {
  max-height: 0;
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .actionbar,
  .actionbar__info {
    transition: none;
  }
}

@media only screen and (min-width: 62.5em) {
  .actionbar {
    display: none;
  }
}

/* Reserve exactly the bar's height, on the footer rather than on the body.
   The fallback covers the first paint before the script has measured, and
   any no-JS case.

   Why the footer owns this: the strip is whatever the bar sits on top of, so
   it has to be the same colour as the bar's blended state or any mismatch
   between the two heights flashes. On the body it was page-white, and the
   mismatch is not hypothetical — the bar animates between its compact and
   full heights precisely when you reach the bottom, so arriving mid-flight
   means the reserved strip and the painted bar disagree for a frame or two.
   White for two frames is a visible glitch; navy for two frames is nothing.
   The footer is the last element on every page, so the scroll room it buys
   is identical. */
@media only screen and (max-width: 62.4375em) {
  #footer {
    padding-bottom: var(--actionbar-h, 7.5rem);
  }
}

/* --------------------------------------------------------------------------
   Small-screen space savers
   ----------------------------------------------------------------------- */

@media only screen and (max-width: 62.4375em) {
  /* Bled grids still need a hair of side padding so tiles aren't cut off. */
  .tile-grid.bleed-mobile {
    gap: 10px;
    padding-inline: 10px;
  }

  /* Decorative art earns its space on desktop, not on a phone. */
  .hide-mobile {
    display: none;
  }

  .sources__list {
    gap: 0.75rem 1.25rem;
  }

  .section-heading {
    margin-bottom: 1.5rem;
  }
}

/* Phone only, and tied to .bleed-mobile above rather than to the sticky bar:
   both of these only make sense while the panel is running edge to edge. */
@media only screen and (max-width: 43.0625em) {
  /* The CTA runs edge-to-edge, so give the copy its own inset. */
  .cta.bleed-mobile {
    padding: 40px 5vw 44px;
  }

  /* Full width, the panel is a blue block and the footer a navy one, with
     --section-gap of page background stranded between them — a white band
     with nothing in it, which reads as a mistake rather than as breathing
     room. Running them together makes the bottom of the page one continuous
     coloured mass and gives the CTA a floor to sit on. The negative margin
     cancels #footer's own margin-top by adjoining with it; both are
     --section-gap, so they sum to zero. Once the panel is an inset card
     again — tablet up — it wants air on all sides instead. */
  .section--to-footer {
    margin-bottom: calc(-1 * var(--section-gap));
  }

  /* The same problem, generalised.

     Full-bleed blocks separated by --section-gap leave a band of page white
     running the whole width of the screen with nothing in it. Between two
     inset cards that gap is breathing room and reads as deliberate; between
     two edge-to-edge coloured blocks it reads as a seam that failed to close
     — which is exactly the argument the .section--to-footer rule above makes
     about the CTA and the footer.

     Two halves, because a gap is made by both neighbours: --join-next gives
     up its bottom margin, --join-prev its top. Adjacent margins collapse, so
     the negative here and the next section's positive margin sum to nothing.
     Above the phone breakpoint the blocks are inset cards again and want air
     on all sides, so neither applies. */
  .section--join-next {
    margin-bottom: calc(-1 * var(--section-gap));
  }

  .section--join-prev {
    margin-top: 0;
  }
}

/* --------------------------------------------------------------------------
   Site header — the fixed stack
   #navigation used to pin itself to the viewport. It does not any more: this
   wrapper does, and the bar plus anything attached under it (the notice) are
   rows in normal flow inside it. That is what lets a notice be "attached to
   the header" without a second set of fixed coordinates to keep in sync, and
   it gives nav.js one box to measure for --header-h.
   ----------------------------------------------------------------------- */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 10000;
  /* Tight, not deep. The old shadow was 0 8px 24px, which reaches roughly
     32px past the bar's own bottom edge — far enough to fall across the first
     line of type under it. The masthead used to be pushed down 10px to escape
     it, and on mobile, where the hero bleeds full width, that 10px read as a
     white seam between the header and the hero rather than as breathing room.
     This one only draws the edge, so the hero sits flush again. */
  box-shadow: 0 1px 3px rgb(16 33 54 / 0.09);
  transition: box-shadow 0.2s ease;
}

/* With the sheet open the header is not floating above page content any
   more — it is the top edge of one continuous surface, so the shadow just
   smears a grey band across the menu. */
html.nav-open .site-header {
  box-shadow: none;
}

@media only screen and (min-width: 62.5em) {
  .site-header {
    z-index: 200000;
    /* Desktop content is inset and starts well clear of the header, so the
       deeper shadow has nothing to land on and can stay. */
    box-shadow: 0 2px 12px rgb(0 0 0 / 0.08);
  }
}

/* --------------------------------------------------------------------------
   Notice — announcement strip attached under the header
   A saturated band between the white bar above it and the pale hero below,
   so it separates from both without a border. The whole strip is the link;
   the close button is the one thing inside it that is not.
   ----------------------------------------------------------------------- */

/* The band itself. Both containers take it: the strip pinned under the header
   and the block dropped into a page. One rule, so they cannot drift into two
   things that nearly match — which is what happened when the in-page one was
   given its own gradient, its own icon size and a pill-shaped button. */
.notice,
.banner {
  position: relative;
  /* Both jobs: clips the sheen sweep, and makes the height collapse on close
     hide its own contents instead of spilling them over the page. */
  overflow: hidden;
  /* Runs from the brand blue into the navy, left to right — dark where the
     close button sits, so the one control that should not compete is the one
     with the least contrast behind it. */
  background-image: linear-gradient(
    100deg,
    var(--primary) 0%,
    var(--color-1) 45%,
    var(--color-4) 130%
  );
  color: #fff;
}

/* The block variant: everything above, plus the shape and padding a thing
   standing on its own in a page needs. Laid out directly rather than through
   an inner wrapper, since it has no close button to sit beside. */
.banner {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.9375rem 1.25rem;
  border-radius: 18px;
  text-decoration: none;
}

/* base.css paints every a:hover in the brand blue. On this band the only
   child that inherits colour is the masked icon, so the pointer crossing it
   turned the icon brand-blue on a blue band — it read as the icon flickering
   out. Same problem the strip has, same fix. */
.banner:hover,
.banner:focus-visible {
  color: #fff;
}

@media only screen and (max-width: 43.0625em) {
  /* Edge to edge, so the inset comes from the viewport rather than the box. */
  .banner {
    padding: 0.875rem 5vw;
  }
}

.notice {
  /* Only height and opacity, and only for the close: the entrance is done
     with keyframes because there is no state to transition from. */
  transition: height 0.32s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.22s ease;
}

/* The strip is in the layout from the first paint — animating its height in
   would shove the page down after load, which is the one thing an
   announcement must not do. So the band fades and its contents rise into
   place instead, and the height never moves.

   `backwards`, not `both`, for the same class of reason as .notice__inner
   below: a filled animation keeps applying its end frame forever, and an
   animation outranks a normal declaration — so `both` left opacity: 1 pinned
   on the element and the close below could never fade it out. The end frame
   is the element's normal state anyway, so dropping the forwards fill costs
   nothing. */
.notice {
  animation: notice-in 0.4s ease backwards;
}

.notice__inner {
  /* .container is carried for its width, but root.css makes every .container
     position: relative — which would capture the link overlay below and clip
     the target to the container instead of the full-bleed strip. The width is
     all that is wanted here. */
  position: static;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* Chrome, not content. An announcement strip is subordinate to the bar it
     hangs off, and it has to read that way: at 48px against a 56px nav this
     was 96% of its height, which is near-parity, not subordination. Half the
     nav is the shape being aimed at — the floor here only catches the case
     where the message is one short line. */
  min-height: 2.5rem;
  padding-block: 0.375rem;
  /* `backwards`, not `both`. A transform makes an element the containing block
     for its absolutely positioned descendants even at translateY(0), which
     would pin the link overlay below to this container and undo the whole
     point of it. `backwards` still holds the opening frame through the delay,
     but leaves no transform behind once the animation is done — and the end
     frame is the element's normal state anyway, so nothing is lost. */
  animation: notice-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.08s backwards;
}

@keyframes notice-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes notice-rise {
  from {
    opacity: 0;
    transform: translateY(-0.4rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* The attention-getter, and the reason the entrance itself can stay quiet: a
   slow highlight crossing the band three times. It runs after the page has
   settled, catches the eye in peripheral vision the way movement does, and
   then stops — a permanent loop in a fixed header is a distraction for
   everyone who already read it. `both` parks it off the right edge for good.

   The pause is inside the keyframes rather than between iterations, so the
   sweep is brisk and the gap between sweeps is long. */
.notice::after,
.banner::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(
    100deg,
    transparent 38%,
    rgb(255 255 255 / 0.24) 50%,
    transparent 62%
  );
  transform: translateX(-100%);
  animation: notice-sheen 3.2s ease-out 0.9s 3 both;
}

/* The in-page band starts its sweep held, and notice.js releases it when the
   band reaches the viewport. On the plain timer the animation ran while the
   band was still several screens below the fold and had finished long before
   anyone scrolled to it, so the one thing it exists to do — catch the eye —
   never happened. The strip under the header needs no such trick: it is on
   screen at first paint.

   Held with a paused animation rather than by adding the animation later, so
   a reader with JavaScript off still gets the sweep on load. Worse timing
   beats no animation and, more to the point, beats a band that never paints
   its highlight at all. */
.banner[data-banner-sheen]::after {
  animation-play-state: paused;
}

.banner[data-banner-sheen].is-seen::after {
  animation-play-state: running;
}

/* A held animation is still an animation, and the reduced-motion rule in
   base.css only shortens durations. Drop the layer outright instead. */
@media (prefers-reduced-motion: reduce) {
  .notice::after,
  .banner::after {
    display: none;
  }
}

@keyframes notice-sheen {
  0% {
    transform: translateX(-100%);
  }
  42%,
  100% {
    transform: translateX(100%);
  }
}

.notice__link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex: 1 1 auto;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

/* The band sets the colour for everything on it, and hovering must not take
   that away: base.css paints every a:hover in the brand blue, and `a:hover`
   outranks the `color: inherit` above, so the pointer landing anywhere on the
   strip swapped white for #2c66b1. Every text child names its own colour, so
   the only thing that ever showed it was the icon — which paints in
   currentColor and turned brand-blue on a blue band, reading as the icon
   flickering out as the pointer crossed. */
.notice__link:hover,
.notice__link:focus-visible {
  color: inherit;
}

/* The strip is one link, and the anchor's own box only reaches as far as its
   text — leaving the gutters, the padding and the gap beside the close button
   dead to a tap, which is not what a full-width banner promises. This stretches
   the target over the whole band.

   Deliberately anchored to .notice rather than to .notice__inner: the strip
   bleeds the full width and the container does not, so anchoring to the
   container would leave the two side gutters out. That is also why .notice__link
   itself must stay unpositioned — it would become the containing block. */
.notice__link::before {
  content: '';
  position: absolute;
  inset: 0;
}

/* Masked, not an <img> — see the macro. The source file is the car from the
   services grid and carries a blue fill, which would vanish on this band.

   Everything from here to the close button is shared by both containers, and
   is named for the banner rather than the notice because the band is the
   component and the strip is one of its two placements. */
.banner__icon {
  flex-shrink: 0;
  /* Sized off the text it sits beside, not off the bar. At 24px against 13px
     type it was 1.7x the font size and read as the loudest thing in the
     strip; this is the ~1.35x that keeps it a marker rather than a logo. */
  width: 1.125rem;
  height: 1.125rem;
  background-color: currentColor;
  -webkit-mask-image: var(--banner-icon);
  mask-image: var(--banner-icon);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.banner__body {
  min-width: 0;
  /* A step below body copy, which is what separates chrome from content — at
     14px on a 16px base the difference was too small to register and the
     strip set like a paragraph. Tight leading for the same reason: this is a
     label of one or two lines, not prose. */
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* base.css paints every <strong> in navy for body copy. On this band that is
   near-invisible, so the lead takes the band's own colour back. */
.banner__lead {
  font-weight: 800;
  color: #fff;
}

.banner__text {
  color: rgb(255 255 255 / 0.88);
}

/* Inline, and styled as the hero's "Learn more" is — the chevron is part of
   the label rather than a mark parked at the end of a row. nowrap keeps the
   label and its chevron on one line when the sentence wraps around them. */
.banner__cta {
  display: inline-block;
  font-weight: 700;
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}

.notice__link:hover .banner__cta,
.notice__link:focus-visible .banner__cta,
.banner:hover .banner__cta,
.banner:focus-visible .banner__cta {
  text-decoration-thickness: 2px;
}

.notice__close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  /* Deliberately quieter than the call to action beside it. Dismissing is the
     secondary act here; it should be findable, not competing. */
  color: rgb(255 255 255 / 0.7);
  cursor: pointer;
  /* Above the link overlay — the one part of the strip that is not the link. */
  position: relative;
  z-index: 1;
  transition: background-color var(--transition), color var(--transition);
}

.notice__close svg {
  width: 1rem;
  height: 1rem;
}

/* The disc is 32px because a bigger one would shout, but the target it stands
   for has to clear 44px. Grown invisibly rather than by padding the button,
   which would grow the disc with it. */
.notice__close::before {
  content: '';
  position: absolute;
  inset: -0.375rem;
}

.notice__close:hover,
.notice__close:focus-visible {
  background-color: rgb(255 255 255 / 0.16);
  color: #fff;
}

/* Closing: nav.js is watching .site-header with a ResizeObserver, so as this
   height runs to zero --header-h follows it frame by frame and the page slides
   up with the strip instead of jumping when it is removed.

   animation: none covers the one window the fill mode above does not — a
   reader who dismisses the strip inside the first 0.4s, while the entrance is
   still running and still winning over any opacity set here. */
.notice.is-closing {
  animation: none;
  height: 0 !important;
  opacity: 0;
}

/* Aligned to the nav row above, not to page content. The two are one block of
   chrome sitting one on top of the other, and the eye reads them as a unit —
   so the icon belongs under the logo and the close under the burger. The page
   gutter (.container, 88vw) set them 6px further in than either, which is
   what made the strip look inset on a header that is not.

   The 9px is the same optical inset the logo takes, and for the same reason —
   see root.css. Both marks are smaller than the box that positions them, so
   matching the boxes leaves the INK misaligned; these numbers match the ink.
   Padding the link rather than the strip keeps the tap target full-bleed,
   since the overlay is anchored to .notice. */
@media only screen and (max-width: 62.4375em) {
  .notice__inner {
    width: 96%;
  }
}

@media only screen and (max-width: 43.0625em) {
  .notice__link {
    padding-left: 9px;
  }
}

@media only screen and (min-width: 43.125em) {
  .notice__inner {
    gap: 0.75rem;
  }

  /* One centred line. The message is short enough to sit in the middle of the
     bar at this width, which is where a site-wide announcement belongs;
     stretched edge to edge it reads as a toolbar.

     Centred inside the link, not inside the row: the link still spans
     everything the close button does not, so the tap target keeps the full
     width. The padding is the close button (2rem) plus the gap (0.75rem),
     which is exactly what it takes to put the text back on the bar's own
     centre line rather than the centre of what is left beside the button. */
  .notice__link {
    justify-content: center;
    gap: 0.75rem;
    padding-left: 2.75rem;
  }

  .banner__body {
    font-size: 0.875rem;
  }

  .banner__icon {
    width: 1.25rem;
    height: 1.25rem;
  }
}

/* Line the strip's content up with the nav row above it, which runs to a
   narrower container than the page does. */
@media only screen and (min-width: 62.5em) {
  .notice__inner {
    max-width: 75rem;
  }
}

/* --------------------------------------------------------------------------
   Header language toggle — mobile only
   Switching language was buried two taps deep inside the burger. On a
   bilingual clinic site that is the wrong depth for it, so it sits in the
   header bar beside the menu button. On desktop the nav row already carries
   the same link, so this is hidden and the in-menu item shows instead.
   ----------------------------------------------------------------------- */

@media only screen and (max-width: 62.4375em) {
  #navigation .lang-toggle {
    position: absolute;
    /* Clear of the 3rem burger sitting at right: 0. */
    right: 3.25rem;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    min-height: 2.25rem;
    padding: 0 0.7rem;
    border: 1px solid var(--border);
    /* Same shape language as every other control on this platform. */
    border-radius: var(--button-radius);
    background-color: var(--surface);
    font-size: 0.8125rem;
    font-weight: 700;
    line-height: 1;
    color: var(--primary);
    text-decoration: none;
    white-space: nowrap;
    z-index: 100;
  }

  #navigation .lang-toggle img {
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
  }

  #navigation .lang-toggle:active {
    background-color: var(--surface-tint);
  }

  /* The header carries it on mobile — don't repeat it inside the menu. */
  #navigation #navbar-menu .cs-lang-item {
    display: none;
  }
}

/* Very narrow phones: the flag alone, so it never crowds the logo. */
@media only screen and (max-width: 22.5em) {
  #navigation .lang-toggle span {
    display: none;
  }

  #navigation .lang-toggle {
    padding: 0 0.55rem;
  }
}

@media only screen and (min-width: 62.5em) {
  #navigation .lang-toggle {
    display: none;
  }
}

@media only screen and (max-width: 62.4375em) {
  /* --------------------------------------------------------------------------
     Menu row — mobile nav entry
     Same row language as the service cards: left-aligned label, chevron on the
     right, the whole row is the target. Separators rather than gaps, so the
     list reads as one panel.
     ----------------------------------------------------------------------- */

  .menu-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    width: 100%;
    padding: 0.95rem 5vw;
    color: var(--color-4);
    text-decoration: none;
    transition: background-color var(--transition), color var(--transition);
  }

  .menu-row:hover,
  .menu-row:focus-visible {
    background-color: var(--surface-tint);
    color: var(--color-4);
  }

  .menu-row:active {
    background-color: var(--color-5);
  }

  .menu-row__label {
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: 0.01em;
  }

  /* Current page: tinted row plus a left accent, which is how a selected row in
     a list is conventionally marked — the pill suited a centred list, not this. */
  .menu-row.is-current {
    background-color: var(--surface-tint);
    color: var(--primary);
    box-shadow: inset 3px 0 0 var(--primary);
  }

  .menu-row.is-current .menu-row__label {
    color: var(--primary);
  }
}

/* The chevron belongs to the mobile row layout. The desktop nav is a
   horizontal bar of plain links, so it must not inherit the affordance. */
@media only screen and (min-width: 62.5em) {
  #navigation #navbar-menu .chev {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Footer
   Rebuilt: the old one was near-black (off-palette), spaced its sections with
   <br> tags, and ran to an enormous single column on mobile. This is a grid
   that goes two-up on small screens and four-up on desktop.
   ----------------------------------------------------------------------- */

#footer {
  background-color: var(--color-4);
  color: rgb(255 255 255 / 0.72);
  margin-top: var(--section-gap);
}

.footer__inner {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem 1.5rem;
  padding: 2.5rem 0 2rem;
}

/* The brand block spans the full width above the columns on small screens. */
.footer__brand {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}

.footer__logo img {
  width: auto;
  height: 2.5rem;
}

.footer__affiliation img {
  width: auto;
  height: 2.25rem;
  opacity: 0.85;
  transition: opacity var(--transition);
}

.footer__affiliation:hover img {
  opacity: 1;
}

/* The address is too long for a half-width track on a phone — it wrapped
   mid-street. It takes the full row until there is room for four columns. */
.footer__col--wide {
  grid-column: 1 / -1;
}

.footer__heading {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fff;
  margin: 0 0 0.9rem;
}

.footer__list {
  list-style: none;
  display: grid;
  gap: 0.7rem;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.footer__list a {
  color: rgb(255 255 255 / 0.72);
  text-decoration: none;
  transition: color var(--transition);
}

.footer__list a:hover,
.footer__list a:focus-visible {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.footer__muted {
  color: rgb(255 255 255 / 0.55);
}

.footer__credit {
  border-top: 1px solid rgb(255 255 255 / 0.12);
}

.footer__credit-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.35rem 1.5rem;
  padding: 1.1rem 0;
  font-size: 0.8125rem;
  color: rgb(255 255 255 / 0.55);
}

.footer__credit-inner a {
  color: rgb(255 255 255 / 0.75);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer__credit-inner a:hover {
  color: #fff;
}

@media only screen and (min-width: 62.5em) {
  .footer__inner {
    /* Brand takes the widest track, the three lists share the rest. */
    grid-template-columns: 1.4fr repeat(3, minmax(0, 1fr));
    gap: 3rem;
    padding: 3.5rem 0 2.5rem;
  }

  .footer__brand,
  .footer__col--wide {
    grid-column: auto;
  }

  .footer__logo img {
    height: 3rem;
  }
}

/* --------------------------------------------------------------------------
   Action bar: blended state over the static footer
   The bar is an overlay on light page content for most of the scroll. Once
   the dark footer arrives it becomes the footer's bottom edge, so it takes
   those colours instead of sitting on top as a pale slab.

   Gated on html:not(.nav-open) — nav.js sets .nav-open on <html> while the
   burger sheet is out. The sheet covers the footer, so the bar is no longer
   the footer's edge; it is the sheet's. Left dark it turned into a navy slab
   under a white panel, which is exactly the mismatch the blend exists to
   avoid, and it only appeared when the reader happened to open the menu at
   the bottom of the page.
   ----------------------------------------------------------------------- */

.actionbar {
  transition: gap 0.25s ease, padding 0.25s ease,
    background-color 0.35s ease, border-color 0.35s ease,
    box-shadow 0.35s ease;
}

/* Same reasoning as the header dropping its shadow: with the sheet out the
   bar is that panel's bottom edge, not an overlay above page content. Both
   the shadow and the hairline drew a line across what should read as one
   continuous white surface. The border keeps its width so nothing shifts. */
html.nav-open .actionbar {
  box-shadow: none;
  border-top-color: transparent;
}

html:not(.nav-open) .actionbar.is-on-footer {
  /* Fully opaque: translucency plus the backdrop's saturate() lifted the bar
     a shade above the footer, so the two never quite matched once the footer
     filled the screen behind it.

     The blur deliberately stays declared. It used to be switched off here as
     well, which was redundant — nothing behind an opaque background is
     visible — but not free: backdrop-filter cannot interpolate to `none`, so
     it snapped off on frame one while the background took 350ms to cross
     from white to navy. For that third of a second the bar was a
     half-transparent grey pane with sharp footer text showing through it.
     That is the choppiness; leaving the blur alone removes it. */
  background-color: var(--color-4);
  border-top-color: rgb(255 255 255 / 0.12);
  box-shadow: none;
}

/* Scrolled to the end, the footer has nowhere left to travel and the bar is
   simply where the page stops. The hairline that marks the bar's top edge
   while footer content is still sliding under it becomes a thin light line
   between two identical navy blocks — read as a rendering seam, not a
   border. It goes; the border keeps its width so nothing shifts. */
html:not(.nav-open) .actionbar.is-on-footer.is-at-bottom {
  border-top-color: transparent;
}

html:not(.nav-open) .actionbar.is-on-footer::before {
  /* The one place the scrim is wrong: over the flat navy footer it has
     nothing to fade and only lays a pale stripe across it. Everywhere else,
     including the blue sections, the eased ramp carries it. */
  opacity: 0;
}

/* This list replaces .button's wholesale, so anything .button animates has to
   be repeated here or it snaps. box-shadow was the omission: the Directions
   button dropped its shadow instantly while its colours faded. transform
   keeps the button's own short duration — a press is a response to a finger,
   not a state change, and 0.35s on it feels broken. */
.actionbar__hours,
.actionbar__address,
.actionbar__go,
.actionbar__call {
  transition: color 0.35s ease, background-color 0.35s ease,
    border-color 0.35s ease, box-shadow 0.35s ease,
    transform var(--transition);
}

html:not(.nav-open) .actionbar.is-on-footer .actionbar__hours {
  color: #fff;
}

html:not(.nav-open) .actionbar.is-on-footer .actionbar__address {
  color: rgb(255 255 255 / 0.7);
}

html:not(.nav-open) .actionbar.is-on-footer .actionbar__go {
  background-color: transparent;
  border-color: rgb(255 255 255 / 0.35);
  color: #fff;
  /* A shadow cast by a transparent button onto navy is only grime. */
  box-shadow: none;
}

html:not(.nav-open) .actionbar.is-on-footer .actionbar__go:hover,
html:not(.nav-open) .actionbar.is-on-footer .actionbar__go:focus-visible {
  background-color: rgb(255 255 255 / 0.08);
  border-color: rgb(255 255 255 / 0.6);
  color: #fff;
}

/* The solid button keeps its colour — lightening it to separate from the navy
   would cost label contrast. A hairline defines its edge instead. */
html:not(.nav-open) .actionbar.is-on-footer .actionbar__call {
  border-color: rgb(255 255 255 / 0.25);
  /* A navy-tinted shadow is invisible on navy. This one is near-black, so
     the button still separates from the footer it is sitting on. */
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 10px 24px -14px rgb(0 0 0 / 0.9);
}

.actionbar::before {
  transition: opacity 0.35s ease;
}

/* --------------------------------------------------------------------------
   "Open now" dot
   Shown only while the clinic is actually open in Eastern Time — see
   actionbar.js. Hidden entirely otherwise, so its presence always means
   something.
   ----------------------------------------------------------------------- */

.status-dot[hidden] {
  display: none;
}

.status-dot {
  /* Changes colour with the bar, so it has to travel with it. */
  transition: background-color 0.35s ease;
  position: relative;
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.45rem;
  border-radius: 50%;
  /* Readable on the light bar (3.30:1); becomes the brand mint on the dark
     footer, where a deep green would disappear. */
  background-color: #16a34a;
  vertical-align: baseline;
}

.status-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: inherit;
  animation: status-pulse 2s ease-out infinite;
}

html:not(.nav-open) .actionbar.is-on-footer .status-dot {
  background-color: var(--accent-mint);
}

@keyframes status-pulse {
  0% { transform: scale(1); opacity: 0.7; }
  70% { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .status-dot::after {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   Perk chips — the practical answers to "should I call," as labels
   ----------------------------------------------------------------------- */

.perks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  list-style: none;
}

.perk {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Pill radius rather than --radius-panel: these are labels, not cards, and
     40px on a 40px-tall box would render as a lozenge either way. Naming it
     keeps them tracking the pill token if it moves. */
  padding: 0.5rem 1.0625rem;
  border: 2px solid var(--border);
  border-radius: var(--radius-pill);
  background-color: var(--surface-tint);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--color-4);
}

.perk__check {
  flex-shrink: 0;
  color: var(--primary);
}

@media only screen and (max-width: 43.0625em) {
  /* Chips wrap to three or four rows on a phone. Tightening the row gap keeps
     the strip reading as one block instead of a stack of loose pills. */
  .perks {
    gap: 8px;
  }

  .perk {
    padding: 0.4375rem 0.875rem;
    font-size: 0.875rem;
  }
}

/* ==========================================================================
   TREATMENT / CONVERSION COMPONENTS
   Styles for the macros at the foot of components.html. Mobile-first like
   everything above; page composition stays in pages.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Anchor targets

   Every in-page link on the site now has to clear a fixed header whose height
   is only known at runtime. --header-h is what nav.js measures and publishes;
   the token is the first-paint fallback, exactly as in base.css. The extra
   1rem is breathing room so a heading does not land flush against the bar.
   ----------------------------------------------------------------------- */

/* Scoped to <main> rather than left as a bare [id]. The header, the notice
   strip and the sticky action bar all carry ids and none of them is ever an
   anchor target; a global rule would be quietly setting a property on them
   for no reason, and on anything fixed it would be actively confusing to
   read later. */
main [id] {
  scroll-margin-top: calc(var(--header-h, var(--header-height)) + 1rem);
}

/* --------------------------------------------------------------------------
   Section heading — left-aligned variant

   The centred default is right for a section that is one idea introduced to
   the whole page. It is wrong for a run of four category blocks: centred
   headings over left-aligned lists give every block two different axes, and
   the eye has to re-find the left edge at each one.
   ----------------------------------------------------------------------- */

.section-heading--start {
  text-align: left;
  margin-bottom: 1.75rem;
}

.section-heading--start .section-heading__text {
  margin-inline: 0;
}

/* --------------------------------------------------------------------------
   Tick — shared by the perk chips and the spotlight point lists
   ----------------------------------------------------------------------- */

.tick {
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   Stat bar — proof figures, before any argument has been made

   Hairline-separated columns rather than cards. Four boxes would read as four
   things to act on; this reads as one strip of facts, which is what it is.
   The rules are vertical from the first breakpoint up and absent on the
   narrowest screens, where the row wraps to two-by-two and a vertical rule
   would land in the middle of nothing.
   ----------------------------------------------------------------------- */

.stat-bar {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem 1.5rem;
  list-style: none;
  padding: 1.25rem 0;
  border-block: 1px solid var(--border);
}

/* No inside padding while the strip is two-up: the dividers only exist from
   the next breakpoint, so the padding has nothing to separate — and with it
   the second row started 1rem to the right of the first, which is the one
   misalignment nobody misses. The column gap does the spacing instead. */
.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stat__value {
  font-size: 1.5rem;
  font-weight: 800;
  line-height: 1.1;
  color: var(--color-4);
}

.stat__label {
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--text);
}

@media only screen and (min-width: 43.125em) {
  .stat-bar {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0;
  }

  /* One row now, so the padding is back and it is holding the figures off
     the dividers rather than off each other. */
  .stat {
    padding-inline: 1rem;
  }

  .stat:first-child {
    padding-inline-start: 0;
  }

  .stat + .stat {
    border-inline-start: 1px solid var(--border);
  }

  .stat__value {
    font-size: 1.75rem;
  }
}

/* --------------------------------------------------------------------------
   Jump nav — in-page navigation on the treatments index

   Replaces a strip of pale outlined pills that was shaped after .perk, the
   home page's inert label chips. Copying an inert component was the whole
   problem: the only navigation on the page borrowed the one shape the reader
   had been taught means "small passive badge", and it did it directly under
   the cycle strip, which is inert prose styled like a breadcrumb. Both
   signals pointed the wrong way.

   Equal cells in a grid, under a named rule. The rule marks the end of the
   lede, which a 28px margin was failing to do; the name says what the cells
   below it are; and the equal widths make them read as one control rather
   than four things that happen to be near each other.

   Every cell is visible at every width. The old strip scrolled horizontally
   on a phone with two and a half chips showing and no fade at the edge, so
   half the navigation was behind a gesture nothing advertised.

   The whole block costs 109px on a 375px screen, against the 113px the pale
   strip and the cycle above it were spending between them.
   ----------------------------------------------------------------------- */

/* Same type as .section-topper. This page already uses that shape for "a
   small label naming the block below it", and this is one.

   The label carries the rule rather than sitting under one. A separate
   border-top plus its padding plus the label was three stacked things doing
   one job — 36px to say "the lede ended here" — and the whole point of this
   pass was that the strip under the header was spending height it had not
   earned. Naming the divider costs the same row the name was already using. */
.jump-nav__label {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  margin-bottom: 0.4375rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--primary);
}

.jump-nav__label::after {
  content: '';
  flex: 1;
  height: 1px;
  background-color: var(--border);
}

/* Two up on a phone, four across from 690px.

   Four across on a phone was tried first and is worse in both directions at
   once: a quarter of 375px leaves about nine characters a line, so every
   label broke to two or three ragged lines and the row ended up TALLER than
   two clean rows of two — and looked like an accident besides. Two columns
   give every label one line and every cell the same height. */
.jump-nav__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  list-style: none;
}

@media only screen and (min-width: 43.125em) {
  .jump-nav__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
  }
}

.jump {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  /* Fills its grid cell so the four read as one segmented row even when one
     label wraps to two lines and its neighbours do not. */
  height: 100%;
  /* Floor rather than padding alone. The type here is small and the padding
     that suits it would leave a 33px target — fine against WCAG's 24px
     minimum and not fine for the patients this practice actually sees, half
     of whom are reading it one-handed with something that hurts. */
  min-height: 2.375rem;
  padding: 0.375rem 0.6875rem 0.4375rem;
  /* A primary tint rather than --border. These are controls, and --border is
     the hairline this site draws between things that are not. */
  border: 2px solid rgba(44, 102, 177, 0.22);
  border-radius: 12px;
  background-color: var(--surface-tint);
  transition: background-color var(--transition), border-color var(--transition),
    color var(--transition);
}

.jump__label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--color-4);
}

/* Down, not right: the destination is further along this page, and a chevron
   would promise another one. Generated rather than typed into the markup so
   it is never read out and never selected with the label. */
.jump__arrow::before {
  content: '\2193';
  margin-inline-start: 0.3125rem;
  font-weight: 400;
  color: var(--primary);
  transition: color var(--transition);
}

/* The reader's own vocabulary, for the reader who arrived about one thing.
   Hidden where there is no room for it — the label has to carry the cell on
   its own at phone widths. */
.jump__hint {
  display: none;
}

.jump:hover,
.jump:focus-visible {
  border-color: var(--primary);
  background-color: var(--primary);
}

.jump:hover .jump__label,
.jump:focus-visible .jump__label,
.jump:hover .jump__arrow::before,
.jump:focus-visible .jump__arrow::before {
  color: #fff;
}

.jump:hover .jump__hint,
.jump:focus-visible .jump__hint {
  /* 0.9, not the 0.82 that looked right: against the primary fill that lands
     at 4.45:1 and the hint is 13px, so it has to clear 4.5. This is 4.99. */
  color: rgba(255, 255, 255, 0.9);
}

@media only screen and (min-width: 43.125em) {
  .jump {
    padding: 0.5rem 0.75rem 0.5625rem;
    border-radius: 14px;
  }

  .jump__label {
    font-size: 0.875rem;
  }
}

/* The hint only appears where a quarter of the container is wide enough to
   set it in one or two lines. Below that the label carries the cell alone —
   a hint broken over four lines would make the nav taller than the section
   it points at is worth. */
@media only screen and (min-width: 62.5em) {
  .jump {
    padding: 0.625rem 1rem 0.75rem;
  }

  .jump__label {
    font-size: 0.9375rem;
  }

  .jump__hint {
    display: block;
    margin-top: 1px;
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.35;
    color: var(--text);
    transition: color var(--transition);
  }
}

/* --------------------------------------------------------------------------
   Spotlight — one section given over to one thing

   Two variants that are visual opposites, so two of them stacked never read
   as the same block repeated:

     --urgent  solid navy, a masked icon, no photograph
     --offer   tinted panel with a photograph

   The panel is the outer box and `__main` is the two-column grid inside it,
   so that `__extra` — anything a caller passes in, the numbered steps being
   the case that exists today — can run the full width underneath rather than
   being trapped in one of the columns.
   ----------------------------------------------------------------------- */

.spotlight {
  padding: 2rem 1.5rem;
  border-radius: var(--radius-panel);
}

.spotlight__main {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.75rem;
  align-items: center;
}

.spotlight__eyebrow {
  margin-bottom: 0.375rem;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.spotlight__title {
  font-size: var(--h2);
  margin-bottom: 0.625rem;
}

.spotlight__text {
  margin-bottom: 1.25rem;
}

.spotlight__points {
  display: grid;
  gap: 0.625rem;
  list-style: none;
  margin-bottom: 1.5rem;
}

.spotlight__points li {
  display: flex;
  align-items: start;
  gap: 0.625rem;
  font-size: 0.9375rem;
  line-height: 1.45;
}

/* Optically centred on the first line of the label rather than on its cap
   height — a tick sitting on the baseline of a two-line item reads as lower
   than the text it belongs to. */
.spotlight__tick {
  margin-top: 0.3125rem;
}

/* The money sentence. A bar rather than a paragraph, because a reader
   skimming for "what does this cost me" has to be able to find it without
   reading the copy above it — and because it must not be mistaken for more
   body text. The left rule is the whole device; it is the same idiom as a
   pull quote and it costs three declarations. */
.spotlight__note {
  padding: 0.875rem 0 0.875rem 1rem;
  border-inline-start: 3px solid;
  margin-bottom: 1.5rem;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.spotlight__actions {
  margin-top: 0;
}

/* Anything the caller hangs under both columns — the numbered steps, today.
   The rule is the panel's, not the passenger's, so whatever gets put here is
   separated the same way. */
.spotlight__extra {
  padding-top: 1.75rem;
  margin-top: 1.75rem;
  border-top: 1px solid var(--border);
}

/* --- urgent: solid navy, icon, no photograph ---------------------------- */

.spotlight--urgent {
  background-color: var(--color-4);
  color: #fff;
}

.spotlight--urgent .spotlight__title,
.spotlight--urgent .spotlight__text,
.spotlight--urgent .spotlight__points li {
  color: #fff;
}

/* Mint rather than white. The eyebrow, the ticks and the note rule are the
   three accents on this panel and they have to be one colour that is neither
   the body white nor the button blue — otherwise the panel is monochrome and
   nothing on it has a hierarchy. --accent-mint is a fill tint and cannot
   carry type; this is the deep form of the same hue, which clears AA on
   navy. */
.spotlight--urgent .spotlight__eyebrow {
  color: var(--accent-mint);
}

.spotlight--urgent .spotlight__tick {
  color: var(--accent-mint);
}

.spotlight--urgent .spotlight__extra {
  border-top-color: rgb(255 255 255 / 0.15);
}

.spotlight--urgent .spotlight__note {
  border-color: var(--accent-mint);
  background-color: rgb(255 255 255 / 0.06);
  border-start-end-radius: 8px;
  border-end-end-radius: 8px;
  padding-inline-end: 1rem;
  color: rgb(255 255 255 / 0.88);
}

/* The mark. Masked rather than loaded as an <img> because the source SVGs
   carry a hardcoded fill — same technique as the button and notice icons.
   Set at a size no photograph on this page reaches, which is what buys the
   panel its visual weight without loading a single byte of artwork. */
.spotlight__mark {
  display: block;
  width: 100%;
  max-width: 8.75rem;
  aspect-ratio: 1;
  background-color: var(--accent-mint);
  opacity: 0.9;
  -webkit-mask-image: var(--spotlight-mark);
  mask-image: var(--spotlight-mark);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

/* --- offer: tinted panel with a photograph ------------------------------ */

.spotlight--offer {
  background-color: var(--surface-tint);
}

.spotlight--offer .spotlight__eyebrow {
  color: var(--primary);
}

.spotlight--offer .spotlight__tick {
  color: var(--accent-mint-deep);
}

/* Quieter than the urgent panel's version. On navy the note has to be a lit
   box or it disappears; on the tint it does not, and the white card plus navy
   type made a self-pay caveat outweigh the offer it qualifies. The warm rule
   is kept — that is what a skimmer looking for the price finds — and the
   type steps back to the muted slate the other caveats use (.hours-note in
   korean.css). Held at 82% rather than that rule's 78% because this one sits
   on the panel tint and not on white: 4.7:1 there, still AA, against 7.5:1
   for the body copy beside it. */
.spotlight--offer .spotlight__note {
  border-color: var(--accent-warm);
  padding-inline-end: 1rem;
  color: color-mix(in srgb, var(--color-3) 82%, transparent);
}

.spotlight--offer .spotlight__media img,
.spotlight--offer .spotlight__media picture {
  width: 100%;
  border-radius: calc(var(--radius-panel) - 0.75rem);
}

/* Media is first in the source order, which is what a phone should get for
   --urgent: the car mark says what the panel is about faster than the eyebrow
   under it does. Everything below is stated against that baseline. */
@media only screen and (max-width: 62.4375em) {
  /* The photograph on --offer is the opposite case. It is evidence for an
     argument that has not been made yet, so it drops below the copy. */
  .spotlight--offer .spotlight__media {
    order: 2;
    /* Cropped to a band. At its own 3:2 across the full width of a phone the
       frame ran to most of a screen, and this particular shot is mainly white
       sweep — so the reader scrolled through a lot of nothing to reach the
       next section. The subject sits dead centre, so the crop costs nothing. */
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: calc(var(--radius-panel) - 0.75rem);
  }

  .spotlight--offer .spotlight__media picture,
  .spotlight--offer .spotlight__media img {
    height: 100%;
    object-fit: cover;
  }

  .spotlight__mark {
    max-width: 4.5rem;
  }
}

@media only screen and (min-width: 62.5em) {
  .spotlight {
    padding: 3rem 3.5rem;
  }

  .spotlight__main {
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
  }

  /* The mark column is narrow — an icon does not need half a panel, and the
     copy is the thing that converts. The photograph does earn its half.
     Pushing the media to the end sends it to that narrow second column and
     leaves the copy the wide one; without it the panel comes out backwards,
     with a 500px car beside a column of text three words wide. */
  .spotlight--urgent .spotlight__main {
    grid-template-columns: minmax(0, 1fr) 13.75rem;
  }

  .spotlight--urgent .spotlight__media {
    order: 2;
  }

  .spotlight--urgent .spotlight__mark {
    max-width: 100%;
    margin-inline: auto;
  }

  /* Back to source order: the photograph leads the row, mirroring the panel
     above it so the two alternate rather than stacking the same way twice. */
  .spotlight--offer .spotlight__media {
    order: 0;
  }

  /* The photograph takes the height of the copy beside it instead of sitting
     centred in a column twice its size. Capped, so a short panel cannot
     stretch it into a strip, and cropped from the centre — the vials are
     dead centre in the frame, so a tighter crop only improves it. */
  .spotlight--offer .spotlight__media {
    align-self: stretch;
  }

  .spotlight--offer .spotlight__media picture {
    display: block;
    height: 100%;
    max-height: 26rem;
  }

  .spotlight--offer .spotlight__media img {
    height: 100%;
    max-height: 26rem;
    object-fit: cover;
  }
}

/* --------------------------------------------------------------------------
   Steps — a numbered sequence

   An <ol>, so the order is the document's. The visible numerals are drawn in
   the markup rather than by a counter because they are a design element with
   their own colour and shape, and aria-hidden because the list already
   announces its own numbering.
   ----------------------------------------------------------------------- */

/* No separator and no spacing of its own. Both used to live here as a
   padding-top and a translucent white rule, which meant the component only
   worked inside a dark panel and carried a 1.75rem gap it could not be asked
   to drop. They belong to the thing it is separated FROM, so they moved to
   .spotlight__extra — see below. */
.steps__title {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 1.25rem;
}

.steps__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.125rem;
  list-style: none;
}

.step {
  display: flex;
  align-items: start;
  gap: 0.875rem;
}

.step__num {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  font-size: 0.875rem;
  font-weight: 800;
  line-height: 1;
}

.step__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.step__title {
  font-weight: 700;
  line-height: 1.3;
}

.step__text {
  font-size: 0.9375rem;
  line-height: 1.45;
}

/* On the navy panel. The steps component carries no colours of its own —
   it inherits from whatever it is placed in, and this is the one placement
   that exists today. */
.spotlight--urgent .steps__title {
  color: var(--accent-mint);
}

.spotlight--urgent .step__num {
  background-color: var(--accent-mint);
  color: var(--color-4);
}

.spotlight--urgent .step__title {
  color: #fff;
}

.spotlight--urgent .step__text {
  color: rgb(255 255 255 / 0.78);
}

@media only screen and (min-width: 43.125em) {
  .steps__list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem 2rem;
  }
}

@media only screen and (min-width: 62.5em) {
  .steps__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1.5rem;
  }

  /* Four across, the numeral goes above its label rather than beside it: a
     28px circle plus a gap eats a quarter of a narrow column and forces the
     two-word titles to wrap. */
  .step {
    flex-direction: column;
    gap: 0.625rem;
  }
}

/* --------------------------------------------------------------------------
   Area card — photograph, heading, and the conditions it covers

   Horizontal on a phone, where a stack of four full-width images would be
   most of a screen each, and upright from tablet up. The 16:9 crop is
   deliberate: the old tiles ran 4:3, and across four cards that difference
   is most of a phone screen.
   ----------------------------------------------------------------------- */

.area-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  list-style: none;
}

.area {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
  overflow: hidden;
  /* Not stretch, which is what `normal` resolves to and what a grid does by
     default. Each card is stretched to the tallest card in the row, and a
     stretching grid hands that slack to its own auto-sized rows — so a card
     with a shorter condition list pushed its picture row 10px taller than
     its neighbours' and every heading in the row landed on a different line.
     Packing the rows to the top keeps the four pictures and the four
     headings on two shared baselines; the slack collects under the last
     list item, where nobody can see it. */
  align-content: start;
}

.area__media {
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 14px;
}

.area__media img,
.area__media picture {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.area__title {
  font-size: 1.0625rem;
  color: var(--color-4);
  margin-bottom: 0.375rem;
}

/* Not bullets. A dot before each line implies an unordered set of equivalent
   things and adds an indent this card cannot spare; a hairline between them
   says "these are separate entries" using space that is already there. */
.area__list {
  list-style: none;
  font-size: 0.875rem;
  line-height: 1.4;
}

.area__list li {
  padding: 0.3125rem 0;
  border-top: 1px solid var(--border);
}

.area__list li:first-child {
  border-top: 0;
  padding-top: 0;
}

@media only screen and (min-width: 43.125em) {
  .area-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.75rem;
  }

  .area {
    grid-template-columns: 1fr;
    gap: 0.875rem;
  }

  .area__media {
    aspect-ratio: 16 / 9;
    border-radius: 18px;
  }
}

@media only screen and (min-width: 62.5em) {
  .area-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1.5rem;
  }
}

/* --------------------------------------------------------------------------
   Treatment list — a category's entries as rows

   No artwork, and that is where the height reclaimed from the old tile grid
   went. A row costs roughly a fifth of what an image tile costs and carries
   a sentence the tile could not.
   ----------------------------------------------------------------------- */

.treatment-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  list-style: none;
}

.treatment {
  border-top: 1px solid var(--border);
}

.treatment:last-child {
  border-bottom: 1px solid var(--border);
}

/* The padding lives on the row rather than on the <li>, so that when the row
   is a link the padding is inside the target instead of dead space around it.
   That is the whole difference between "the heading is clickable" and "the
   row is clickable". */
.treatment__row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.125rem 0;
}

.treatment__main {
  min-width: 0;
  flex: 1 1 auto;
}

/* Pulled out to the sides and given the space back as padding, so the hover
   state reads as a band the row sits in rather than a rectangle floating
   inside it. The row's own width is unchanged — and it has to stay that way
   at every width, because a link row and a plain row are neighbours in the
   same list and their text has to start on the same vertical line.

   The two-column desktop layout used to drop the negative margin and keep
   the padding, on the theory that an overhang would let two adjacent hover
   bands touch across the column gap. It cannot: the gap is 3rem and each
   band reaches 0.75rem into it, which leaves 1.5rem clear. What dropping it
   did do was indent every clickable row 12px further than every plain one —
   most visibly in "Injury and accident care", where the link is the first
   row under a heading it no longer lined up with. */
.treatment__row--link {
  color: inherit;
  padding-inline: 0.75rem;
  margin-inline: -0.75rem;
  border-radius: 12px;
  transition: background-color var(--transition);
}

.treatment__row--link:hover,
.treatment__row--link:focus-visible {
  color: inherit;
  background-color: var(--surface-tint);
}

.treatment__row--link:hover .treatment__title {
  color: var(--primary);
}

/* Not a flex row, deliberately. The tag is part of the sentence the heading
   makes, so it is set as inline content after it: it then flows with the last
   word when the title wraps, instead of being thrown onto a flex line of its
   own, and it takes a line break at the space before it if there is no room
   for it. The alignment is handled on the tag itself. */
.treatment__title {
  font-size: 1.0625rem;
  color: var(--color-4);
  margin-bottom: 0.25rem;
  transition: color var(--transition);
}

/* Marks a row that is unlike its neighbours. Amber rather than the site blue,
   because blue here would read as a second link beside the one in the title.
   The default is the caveat colour; `--partner` below is the other direction,
   and the two match the callout tones exactly — see the note on that macro. */
.treatment__tag {
  /* Centred on the title's capitals, which takes two corrections and is the
     whole reason this is not a baseline-aligned flex item. Sitting the pill
     on the title's baseline hangs its border and lower padding below the
     text and reads as a sag; `middle` centres it on the title's x-height
     instead, and the nudge lifts it the rest of the way, x-height being
     about a fifth of an em short of cap height. */
  display: inline-block;
  vertical-align: middle;
  position: relative;
  top: -0.18em;
  /* The 8px the flex `gap` used to give it, less the width of the word space
     before it — which is now a real space in the markup, and is what lets
     the pill wrap onto the next line rather than overflow the row. */
  margin-inline-start: 0.1875rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-pill);
  background-color: var(--surface-tint);
  border: 1px solid var(--accent-warm);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-warm);
  white-space: nowrap;
}

/* Green, for a row the practice is making a claim FOR rather than a caveat
   about — the therapy delivered by the clinics inside this building. Same
   --accent-mint-deep as .callout--partner directly under the list, so the
   pill and the callout read as one statement instead of two. */
.treatment__tag--partner {
  border-color: var(--accent-mint-deep);
  color: var(--accent-mint-deep);
}

.treatment__text {
  max-width: 68ch;
  font-size: 0.9375rem;
  line-height: 1.5;
  margin-bottom: 0;
}

@media only screen and (min-width: 62.5em) {
  /* Two columns of rows. A single column of fifteen rows at this width runs
     a very long way down for content that is mostly two lines an entry. */
  .treatment-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 3rem;
  }

  /* Both first-row borders have to survive the split, and the last-child
     rule only ever matched one of the two columns. Every row draws its top
     border and the list closes itself underneath instead. */
  .treatment:last-child {
    border-bottom: 0;
  }

  .treatment-list::after {
    content: "";
    grid-column: 1 / -1;
    border-top: 1px solid var(--border);
  }
}

/* --------------------------------------------------------------------------
   Callout — an aside attached to a list of rows

   The thing that is true of a whole group rather than of any entry in it: the
   money sentence under the self-pay list, the partnership under the therapy
   list. Shaped after .spotlight__note — a rule down the leading edge rather
   than a box — because both are the same kind of object: a sentence that has
   to be visibly not body copy.

   The tone colour is the only difference between the two variants, and it is
   set once as a custom property so the icon and the rule cannot drift apart.
   ----------------------------------------------------------------------- */

.callout {
  --callout-accent: var(--primary);
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-top: 1.5rem;
  padding: 0.9375rem 1.0625rem;
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--callout-accent);
  border-radius: 12px;
  background-color: var(--surface-tint);
}

/* Amber, the same amber as .treatment__tag — a row wearing the pill and a
   caveat under the same list read as one statement. Nothing uses it at the
   moment: the wellness billing line went back to the default navy rule when
   that section stopped leading with its price. Kept for the next real caveat,
   which should be a caveat and not a price. */
.callout--caution {
  --callout-accent: var(--accent-warm);
}

/* Green, for a claim the practice is making FOR itself. The pair is the whole
   design: amber warns you about us, green tells you something about us. */
.callout--partner {
  --callout-accent: var(--accent-mint-deep);
}

.callout__icon {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  /* Optically aligned to the title's cap height rather than its line box. */
  margin-top: 0.125rem;
  background-color: var(--callout-accent);
  -webkit-mask-image: var(--callout-icon);
  mask-image: var(--callout-icon);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.callout__title {
  margin-bottom: 0.125rem;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--color-4);
}

.callout__text {
  margin-bottom: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
}

@media only screen and (min-width: 62.5em) {
  .callout {
    padding: 1.0625rem 1.25rem;
  }
}

/* --------------------------------------------------------------------------
   FAQ — native <details>, no JavaScript

   Every answer is in the served HTML whether or not it is open, which is the
   reason for using <details> rather than building this out of buttons: the
   sentence about insurance coverage gets indexed without a reader ever
   clicking it.
   ----------------------------------------------------------------------- */

.faq {
  display: grid;
  gap: 0;
  max-width: var(--container-narrow);
  margin-inline: auto;
}

.faq__item {
  border-top: 1px solid var(--border);
}

.faq__item:last-child {
  border-bottom: 1px solid var(--border);
}

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.0625rem 0;
  font-weight: 700;
  line-height: 1.4;
  color: var(--color-4);
  cursor: pointer;
  list-style: none;
}

/* Safari draws its own disclosure triangle from a pseudo-element that
   `list-style: none` does not reach. */
.faq__q::-webkit-details-marker {
  display: none;
}

.faq__q:hover {
  color: var(--primary);
}

/* A plus that becomes a minus — two bars, one of which is rotated away when
   the item opens. Drawn in CSS rather than shipped as two SVGs because it is
   the only way the two states can animate into each other. */
.faq__marker {
  position: relative;
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
}

.faq__marker::before,
.faq__marker::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: calc(50% - 1px);
  height: 2px;
  border-radius: 2px;
  background-color: var(--primary);
  transition: transform var(--transition);
}

.faq__marker::after {
  transform: rotate(90deg);
}

.faq__item[open] .faq__marker::after {
  transform: rotate(0deg);
}

.faq__a {
  padding-bottom: 1.125rem;
  /* Clear of the marker, so the answer lines up with the question rather
     than running under the control. */
  padding-inline-end: 2rem;
}

.faq__a p {
  font-size: 0.9375rem;
  line-height: 1.55;
  margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   Cycle — the stages of care, chevron-separated, on one line

   The cheapest possible way to say "all of this happens here". A section
   making the same point would cost twenty times the height for something the
   reader takes in at a glance.

   The chevrons are generated between items rather than being characters in
   the content, so they are never read out and never selected with the text.
   ----------------------------------------------------------------------- */

/* Caption weight, which is what this is. At 700/navy it was the boldest thing
   between the headline and the banner, and a bold chevron-separated row of
   words sitting on top of the page's navigation is a breadcrumb as far as any
   reader is concerned — so the inert strip was out-signalling the real one.
   Quieting it is most of what makes the jump nav below legible as navigation. */
.cycle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
  list-style: none;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text);
}

.cycle__step {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.cycle__step:not(:last-child)::after {
  content: '\203A';
  font-size: 1rem;
  font-weight: 400;
  line-height: 1;
  /* Border grey, not primary. A primary-coloured chevron is the one detail
     that made this read as a link trail. */
  color: var(--color-5);
}

@media only screen and (min-width: 62.5em) {
  .cycle {
    font-size: 0.875rem;
    gap: 0.25rem 0.6875rem;
  }

  .cycle__step {
    gap: 0.6875rem;
  }
}

/* --------------------------------------------------------------------------
   Link grid — the page library's one component

   Every list of library pages on the site: the related block at the foot of a
   treatment or condition page, the conditions index, the generated block on
   the treatments index. One look, because they are one thing — a set of
   pages, named and summarised. See `linkGrid` in components.html.

   Cards rather than the bare rows `.treatment-list` uses, and the difference
   is doing a job. A row in that list is an entry in a catalogue, most of
   which goes nowhere; every one of these is a page. Boxing them says which
   of the two a reader is looking at without a word of copy spent on it.
   ----------------------------------------------------------------------- */

.link-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  list-style: none;
}

.link-card {
  display: flex;
}

/* The whole card is the target, not the title inside it. Same reasoning as
   the treatment rows and the menu rows: a two-word heading is a thin thing to
   aim at, and on a phone the card is the band a thumb actually lands on. */
.link-card__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  padding: 1.125rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background-color: var(--surface);
  color: inherit;
  transition: background-color var(--transition), border-color var(--transition);
}

.link-card__inner:hover,
.link-card__inner:focus-visible {
  color: inherit;
  background-color: var(--surface-tint);
  border-color: var(--primary);
}

.link-card__inner:hover .link-card__title {
  color: var(--primary);
}

.link-card__inner:hover .chev {
  transform: translateX(3px);
}

/* A stub — listed, but there is no page to go to. Quieter than a link card
   and without the affordances of one: no border highlight, no hover state, no
   chevron, and the cursor never changes. It reads as an item in a list rather
   than as a link that is broken. */
.link-card--stub .link-card__inner {
  background-color: transparent;
  border-style: dashed;
}

.link-card--stub .link-card__title {
  color: var(--text);
}

.link-card--stub .link-card__text {
  color: color-mix(in srgb, var(--color-3) 80%, transparent);
}

.link-card__body {
  flex: 1 1 auto;
  min-width: 0;
}

.link-card__title {
  font-size: 1.0625rem;
  color: var(--color-4);
  margin-bottom: 0.25rem;
  transition: color var(--transition);
}

.link-card__text {
  font-size: 0.9375rem;
  line-height: 1.5;
  margin-bottom: 0;
}

@media only screen and (min-width: 43.125em) {
  .link-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media only screen and (min-width: 62.5em) {
  .link-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
  }
}

/* Condition names on the body-area cards, where a page exists to link to.
   Underlined rather than tinted: the list is a column of short phrases at
   0.875rem where two or three are links and the rest are not, and colour
   alone at that size is a difference a reader has to hunt for. */
.area__link {
  color: var(--color-1);
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
}

.area__link:hover,
.area__link:focus-visible {
  color: var(--primary);
  text-decoration-color: currentColor;
}

/* --------------------------------------------------------------------------
   Tick list — a checklist outside a spotlight panel

   The same mark and the same rhythm as `.spotlight__points`, on a light
   background. It exists because the points that make a hero panel worth
   reading are just as useful beside a photograph halfway down the page, and
   copying the panel's rules to get them was how the panel's colours started
   leaking into sections that are not panels.
   ----------------------------------------------------------------------- */

.tick-list {
  display: grid;
  gap: 0.625rem;
  list-style: none;
  margin: 1.25rem 0 0;
}

.tick-list li {
  display: flex;
  align-items: start;
  gap: 0.625rem;
  font-size: 0.9375rem;
  line-height: 1.45;
}

.tick-list__tick {
  margin-top: 0.3125rem;
  color: var(--accent-mint-deep);
}

/* --------------------------------------------------------------------------
   Figure — one wide photograph with a caption

   The quietest of the three image blocks. Capped in height and cropped from
   the centre rather than letterboxed, for the same reason the feature rows
   are: otherwise the shape of whatever file somebody drops in decides how
   much of the page it takes, and a portrait source becomes a screen and a
   half of photograph between two paragraphs.
   ----------------------------------------------------------------------- */

.figure-block {
  margin: 0;
}

.figure-block picture,
.figure-block img {
  display: block;
  width: 100%;
  border-radius: var(--radius-panel);
}

.figure-block img {
  max-height: 26rem;
  object-fit: cover;
}

/* Caption, not body copy: a step down in size and colour, and held to a
   readable measure rather than running the full width of the photograph
   above it. */
.figure-block__caption {
  max-width: 68ch;
  margin-top: 0.875rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: color-mix(in srgb, var(--color-3) 82%, transparent);
}

@media only screen and (max-width: 43.0625em) {
  /* Full-bleed on a phone, so the caption needs the container's inset back or
     it sits against the edge of the screen. */
  .figure-block__caption {
    width: var(--container-edge);
    margin-inline: auto;
  }
}

/* --------------------------------------------------------------------------
   Gallery — a row of captioned photographs

   Side-scrolling on a phone and a grid from tablet up. Stacking three
   full-width images on a phone would cost about three screens and bury
   whatever follows; a row that visibly runs off the edge invites the swipe
   instead, and costs one.
   ----------------------------------------------------------------------- */

.gallery {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 72%;
  gap: 1rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Runs to the screen edge, with the container's inset restored as padding
     so the first card still lines up with the heading above it and the last
     one does not end flush against the edge. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-inline: calc((100vw - var(--container-edge)) / 2);
  /* The snapport has to be inset by the same amount or the first card cannot
     rest where it is laid out: snapping aligns the card's edge to the
     snapport's, so with the default snapport the browser scrolls the padding
     away on load and the row starts flush against the screen edge — 22px out
     of register with the heading above it, and only on the first card. */
  scroll-padding-inline: calc((100vw - var(--container-edge)) / 2);
  /* The scrollbar is the browser's; nothing here draws a fake one. */
  scrollbar-width: thin;
}

.gallery__item {
  scroll-snap-align: start;
}

.gallery__media {
  display: block;
  overflow: hidden;
  border-radius: 14px;
  /* One shape for every cell, so a mixed set of sources still reads as a
     row rather than as a shelf of different-sized frames. */
  aspect-ratio: 4 / 3;
}

.gallery__media img,
.gallery__media picture {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery__caption {
  margin: 0.75rem 0 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: color-mix(in srgb, var(--color-3) 82%, transparent);
}

@media only screen and (min-width: 43.125em) {
  .gallery {
    grid-auto-flow: row;
    /* A definite minimum, not minmax(0, 1fr): auto-fit needs one, and with a
       zero floor the track count is undefined and the declaration is dropped.
       12rem is about the narrowest a captioned photograph stays legible. */
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    grid-auto-columns: auto;
    overflow-x: visible;
    width: auto;
    margin-inline: 0;
    padding-inline: 0;
  }
}

@media only screen and (min-width: 62.5em) {
  .gallery {
    gap: 1.5rem;
  }
}
