/* ==========================================================================
   ASMC — LAYOUT & COMPONENTS
   No raw colour values here. Everything comes from theme.css tokens.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* Cross-document transitions: a card's photo and title morph into the ones
   on the page it opens. Declared inside a no-preference query so it simply
   never activates for anyone who has asked for less motion — the navigation
   still happens, it just cuts. Browsers without support ignore it and
   navigate normally.

   The names themselves are set inline per record, keyed by slug: a
   view-transition-name has to be unique in the document, so a single shared
   name on every card would break the transition rather than share it. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
/* Belt and braces, in case a browser honours @view-transition but not its
   nesting inside a media query. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Keep anchor targets clear of the sticky header — without this,
     jumping to #book puts the heading underneath it. */
  scroll-padding-top: calc(var(--header-offset) + var(--s-4));
}

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-ink-soft);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  /* room for the mobile action bar so it never sits on top of content */
  padding-bottom: calc(var(--actionbar-h) + env(safe-area-inset-bottom));
}
@media (min-width: 900px) { body { padding-bottom: 0; } }

h1, h2, h3 {
  font-family: var(--font-display);
  color: var(--c-ink);
  line-height: var(--lh-head);
  font-weight: 600;
  margin: 0 0 var(--s-4);
}
p { margin: 0 0 var(--s-4); }
img { max-width: 100%; height: auto; display: block; }
a { color: var(--c-brand); text-decoration-thickness: 1px; text-underline-offset: 3px; }

:focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Read but not seen. Clipped rather than display:none, which would take it
   out of the accessibility tree along with the layout. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--c-ink); color: var(--c-on-dark); padding: var(--s-3) var(--s-4); z-index: 200;
}
.skip:focus { left: var(--s-4); top: var(--s-4); }

.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }

.section { padding-block: var(--section-y); }
.section--tint { background: var(--c-surface); }

.eyebrow {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase; color: var(--c-brand); margin-bottom: var(--s-3);
  display: block;
}
.lede { font-size: 1.125rem; max-width: var(--maxw-text); color: var(--c-ink-soft); }


/* ---------- Buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  font-family: var(--font-body); font-weight: 600; font-size: var(--t-small);
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-pill); border: 1.5px solid transparent;
  cursor: pointer; text-decoration: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
  min-height: 46px;                     /* comfortable touch target */
}
/* A small lift, same idea as the card hover — a button is a tappable
   surface too. Colour changes are handled per-variant below. */
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
/* Presses register on touch, where there is no hover to lean on. After
   :hover, so a tap that leaves a stuck hover state still flattens back. */
.btn:active { transform: scale(.99); box-shadow: none; }
.btn--solid { background: var(--c-brand); color: var(--c-on-brand); }
.btn--solid:hover { background: var(--c-brand-deep); }
.btn--ghost { background: transparent; color: var(--c-ink); border-color: var(--c-line); }
.btn--ghost:hover { border-color: var(--c-brand); color: var(--c-brand); }
.btn--wa { background: var(--c-whatsapp); color: var(--c-whatsapp-ink); }
.btn--wa:hover { filter: brightness(.94); }
.btn--sm { padding: var(--s-2) var(--s-4); min-height: 44px; }
.btn--block { width: 100%; }
.btn__ico { width: 19px; height: 19px; fill: currentColor; }


/* ---------- Header ---------- */

.hdr {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--c-surface) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--c-line);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Out of the way while reading down the page; back on the way up. */
.hdr.is-hidden { transform: translateY(-100%); }

/* A resting shadow once the page has moved off the top — set by .is-elevated
   on any header. A header riding transparently over a photo (.hdr--over)
   only earns it once it has already gone solid (.is-scrolled): a shadow
   under see-through chrome would float over nothing. */
.hdr.is-elevated:not(.hdr--over),
.hdr--over.is-scrolled { box-shadow: var(--shadow-sm); }
/* Sliding chrome in and out is motion like any other — leave it put. */
@media (prefers-reduced-motion: reduce) {
  .hdr.is-hidden { transform: none; }
}
.hdr__in {
  display: flex; align-items: center; gap: var(--s-4);
  min-height: var(--header-h);
}
.brand { display: flex; align-items: center; text-decoration: none; margin-right: auto; }
/* 300x80 lockup. 120px wide sets 32px tall, 150px sets 40px — both clear
   the 68px header, so the logo never drives --header-h. */
.brand__logo { width: 120px; height: auto; }
@media (min-width: 900px) { .brand__logo { width: 150px; } }

.nav__list { display: flex; gap: var(--s-5); list-style: none; margin: 0; padding: 0; }
.nav__link {
  color: var(--c-ink-soft); text-decoration: none; font-size: var(--t-small); font-weight: 500;
  padding-block: var(--s-2); position: relative;
}
.nav__link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--c-accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.nav__link:hover::after, .nav__link.is-current::after { transform: scaleX(1); }
.nav__link.is-current { color: var(--c-ink); font-weight: 600; }

.hdr__cta { display: flex; gap: var(--s-2); }

.burger {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; background: none; border: 0; cursor: pointer; padding: 0;
}
.burger span {
  display: block; height: 2px; width: 22px; margin-inline: auto;
  background: var(--c-ink); border-radius: 2px;
  transition: transform var(--dur) var(--ease), opacity var(--dur-fast) var(--ease);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Desktop keeps its CTA in the header bar, so the in-menu copy stays off.
   Declared before the mobile block so the media query can turn it on. */
.nav__cta { display: none; }


/* ---------- Header riding over the hero ----------
   Home page only, and only with a photo behind it. Gated on the script
   being alive: a header that turned transparent and never turned back
   would put white nav text over white content. .no-reveal is the same
   "main.js never ran" signal the scroll reveals use, so both failure
   modes — no JS at all, and a script that failed to load — end up solid.

   No extra scrim is needed behind the nav. The veil already covers the
   header band, and at its lightest point there (alpha .82, over a
   blown-out white photo) white text measures 9.37:1. */

/* Repeats transform and box-shadow, because this shorthand replaces the
   base .hdr one and would otherwise leave the hide-on-scroll slide and the
   is-scrolled shadow with nothing to animate. */
.hdr--over {
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.hdr--over .nav__link,
.hdr--over .btn--ghost,
.hdr--over .btn--solid,
.hdr--over .burger span,
.hdr--over .brand__logo {
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), filter var(--dur) var(--ease);
}

.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) {
  background: transparent;
  backdrop-filter: none;
  border-bottom-color: transparent;
}
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .nav__link,
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .nav__link.is-current,
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .btn--ghost { color: var(--c-on-dark); }
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .btn--ghost { border-color: var(--c-on-dark-soft); }
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .burger span { background: var(--c-on-dark); }

/* Book inverts rather than only whitening its label. Brand navy on the
   navy veil measures 1.06:1 — the button has no edge at all against it.
   White on the same veil is 9.37:1, with the navy label at 11.69:1. */
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .btn--solid {
  background: var(--c-on-dark); color: var(--c-brand);
}
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .btn--solid:hover {
  background: var(--c-brand-wash);
}

/* TODO: ship a reversed logo file and delete this. brightness(0) invert(1)
   flattens the navy wordmark to solid white, which throws away any colour
   in it — fine for a one-colour mark, wrong the moment the logo gains a
   second colour. */
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) .brand__logo {
  filter: brightness(0) invert(1);
}

/* The accent focus ring is 2:1 on the veil; white reads against the photo. */
.js:not(.no-reveal) .hdr--over:not(.is-scrolled):not(.is-menu) :focus-visible {
  outline-color: var(--c-on-dark);
}

.nav__scrim {
  position: fixed; inset: 0; background: var(--c-scrim); z-index: 98;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.nav__scrim.is-open { opacity: 1; }

@media (max-width: 899px) {
  .burger { display: flex; }
  .hdr__cta { display: none; }
  .nav {
    /* Absolute against .hdr, not fixed against the viewport: the panel has
       to hang off the header's real bottom edge wherever that is. Fixed
       only lands there because backdrop-filter on .hdr makes it the
       containing block and --header-h matches the header height — lose
       either and the offer bar pushes the header down behind the panel. */
    position: absolute; top: 100%; left: 0; right: 0;
    background: var(--c-surface); border-bottom: 1px solid var(--c-line);
    z-index: 99; box-shadow: var(--shadow-md);

    /* Collapse to nothing rather than sliding up behind the header.
       .hdr is a stacking context (z-index + backdrop-filter) and .nav is a
       positioned child with a positive z-index, so it paints ON TOP of the
       header's own background — a panel parked above the fold still shows
       across it. Clipping to zero height keeps it below the header always.
       visibility also takes the links out of the tab order while closed. */
    overflow: hidden;
    max-height: 0;
    visibility: hidden;
    transition: max-height var(--dur) var(--ease),
                visibility 0s linear var(--dur);
  }
  .nav.is-open {
    /* Bounded so a long menu stays usable on a landscape phone. */
    max-height: calc(100dvh - var(--header-offset));
    visibility: visible;
    overflow-y: auto;
    overscroll-behavior: contain;
    transition: max-height var(--dur) var(--ease), visibility 0s;
  }
  .nav__list { flex-direction: column; gap: 0; padding: var(--s-2) var(--gutter) var(--s-4); }
  .nav__link { display: block; padding: var(--s-4) 0; border-bottom: 1px solid var(--c-line); font-size: 1.0625rem; }
  .nav__link::after { display: none; }
  .nav__cta {
    display: grid; gap: var(--s-3);
    padding: 0 var(--gutter) var(--s-5);
  }
}


/* ---------- Hero ---------- */

.hero { position: relative; overflow: hidden; background: var(--c-surface); }
.hero__in {
  --hero-pad: clamp(2.5rem, 1.5rem + 5vw, 5.5rem);
  display: grid; gap: var(--s-7);
  padding-block: var(--hero-pad);
}

/* Watched by the header observer. 80% down the hero, no size, no paint. */
.hero__end {
  position: absolute; top: 80%; left: 0;
  width: 1px; height: 1px; pointer-events: none;
}
@media (min-width: 900px) {
  .hero__in { grid-template-columns: 1.05fr .95fr; align-items: center; gap: var(--s-8); }
}
/* The page's one h1, always visible — the rotating showcase title beneath
   it stays an h2, so this needs its own size rather than reusing
   .hero__title, which is tuned for that much bigger, dynamic headline. */
.hero__welcome { font-size: var(--t-h2); margin-bottom: var(--s-4); }

.hero__title { font-size: var(--t-hero); line-height: var(--lh-tight); letter-spacing: -.02em; margin-bottom: var(--s-4); }
.hero__title em { font-style: normal; color: var(--c-brand); }
.hero__sub { font-size: 1.125rem; max-width: 46ch; margin-bottom: var(--s-6); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-6); }

/* Signature: the falaj rule — a channel line with a node, echoing Al Ain's
   irrigation channels. Used once per page as the hero's closing mark. */
.falaj { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-5); }
.falaj__line { height: 1px; flex: 1; background: linear-gradient(90deg, var(--c-accent), transparent); }
.falaj__node { width: 7px; height: 7px; border-radius: 50%; background: var(--c-accent); flex: none; }
.falaj__txt { font-size: var(--t-micro); letter-spacing: var(--ls-eyebrow); text-transform: uppercase; color: var(--c-ink-muted); font-weight: 600; }

.trust { display: flex; flex-wrap: wrap; gap: var(--s-5); list-style: none; margin: 0; padding: 0; }
.trust__item { display: flex; flex-direction: column; }
.trust__n { font-family: var(--font-display); font-size: 1.6rem; font-weight: 700; color: var(--c-ink); line-height: 1; }
.trust__l { font-size: var(--t-micro); color: var(--c-ink-muted); margin-top: 4px; }

/* ---------- Hero showcase ----------
   The treatment photo fills the hero and the copy sits over it. Panels and
   background slides stack into one grid cell each, so the hero holds the
   height of the tallest and nothing jumps as it advances. */

.hero__copy { min-width: 0; }

/* The hero's second column: a real white card over the photo, not empty
   space the background shows through. Already above the veil — .hero__in
   gets the z-index lift for both its children when .hero--photo is set.
   Sized to its own content — the form is short (name, mobile, consent,
   submit) since treatment/dentist were dropped from it. */
.herobook { min-width: 0; width: 100%; max-width: 480px; }
/* Doubled class beats .panel's own box-shadow on specificity alone, so
   this holds regardless of where either rule sits in the file. The main
   conversion point on the page earns more presence against the photo
   behind it than a plain .panel gets elsewhere. */
.herobook.panel { box-shadow: var(--shadow-lift); }
@media (max-width: 899px) {
  /* Full width once stacked below the copy — the 480px cap is only there
     to stop the card sprawling in the second grid column. */
  .herobook { max-width: none; }
}

.showcase { display: grid; }
.showcase > * { grid-area: 1 / 1; }

.showcase__panel {
  opacity: 0; filter: blur(6px); visibility: hidden;
  transition: opacity .4s var(--ease), filter .4s var(--ease),
              visibility 0s linear .4s;
}
.showcase__panel.is-on {
  opacity: 1; filter: blur(0); visibility: visible;
  transition: opacity .4s var(--ease), filter .4s var(--ease), visibility 0s;
}

.showcase { margin-bottom: var(--s-5); }
.showcase .hero__sub { margin-bottom: 0; }

/* A long description entered in the admin would otherwise set the height of
   the whole hero, for every treatment. Two lines, not four — the full text
   already has its own home on the service's own page. */
.showcase__txt {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Dots only. Pulled left so the first sits on the text edge rather than the
   edge of its 44px tap target. */
.showcase__ctrl {
  display: flex; align-items: center; gap: var(--s-1);
  margin: 0 0 var(--s-5) -14px;
}
.showcase__dot {
  display: grid; place-items: center;
  width: 44px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.showcase__dot::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--c-line);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.showcase__dot:hover::before { background: var(--c-ink-muted); }
.showcase__dot.is-on::before { background: var(--c-accent); transform: scale(1.25); }


/* ---------- Photographic hero ----------
   Applied whenever at least one slide resolved to an image. The veil is
   --c-hero-veil, defined with its contrast reasoning in theme.css; it sits
   above the photos and below the copy, so the guarantee holds for every
   slide whatever the photo behind it. A slide that resolved to no image at
   all shows the ink base under the same veil — darker still, never broken. */

.hero--photo { background: var(--c-ink); }

.hero__bg { position: absolute; inset: 0; display: grid; }
.hero__bg > * { grid-area: 1 / 1; }
.hero__slide {
  background-image: var(--slide-img);
  background-size: cover;
  background-position: center;
  opacity: 0;
  /* Opacity only: blurring a full-bleed image every few seconds is real
     work on a phone, and the copy already carries the blur. */
  transition: opacity .4s var(--ease);
}
.hero__slide.is-on { opacity: 1; }

.hero--photo::after {
  content: ""; position: absolute; inset: 0;
  background: var(--c-hero-veil);
}
.hero--photo .hero__in { position: relative; z-index: 1; }

.hero--photo .hero__welcome,
.hero--photo .hero__title,
.hero--photo .hero__title em,
.hero--photo .eyebrow,
.hero--photo .trust__n { color: var(--c-on-dark); }

.hero--photo .hero__sub,
.hero--photo .trust__l,
.hero--photo .falaj__txt { color: var(--c-on-dark-soft); }

/* The ghost button is ink-on-light by default, which vanishes here. */
.hero--photo .btn--ghost { color: var(--c-on-dark); border-color: var(--c-on-dark-soft); }
.hero--photo .btn--ghost:hover { color: var(--c-on-dark); border-color: var(--c-on-dark); background: var(--c-on-dark-line); }

/* Dots and focus rings have to read against the photo, not the page. */
.hero--photo .showcase__dot::before { background: var(--c-on-dark-line); }
.hero--photo .showcase__dot:hover::before { background: var(--c-on-dark-soft); }
.hero--photo .showcase__dot.is-on::before { background: var(--c-on-dark); }
.hero--photo :focus-visible { outline-color: var(--c-on-dark); }


/* ---------- Cards grid ---------- */

.grid { display: grid; gap: var(--s-5); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

.card {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); overflow: hidden; text-decoration: none; color: inherit;
  display: flex; flex-direction: column;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: var(--c-brand); }
/* After :hover, so a tap that leaves a stuck hover state still reads. */
.card:active { transform: scale(.99); }
.card__media { aspect-ratio: 4 / 3; background: var(--c-brand-wash); overflow: hidden; }
.card__media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--dur) var(--ease);
}
/* The card lifts as a whole; the photo inside also drifts in slightly,
   clipped by .card__media's own overflow: hidden. */
.card:hover .card__media img { transform: scale(1.06); }
/* Column so the foot link can be pushed to the bottom of whatever height
   the row settles on. Positioned so the icon badge can hang off its top. */
.card__body { padding: var(--s-4) var(--s-5) var(--s-5); flex: 1; display: flex; flex-direction: column; position: relative; }
.card__title { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.card__txt { font-size: var(--t-small); color: var(--c-ink-muted); margin: 0; }

/* Reception pastes whole paragraphs into the short description, which used
   to stretch one card and drag the whole row with it. Scoped to text inside
   a card: the same class dresses the standalone caption under the insurance
   strip, which must not be truncated. */
.card .card__txt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
/* Three lines held open so a one-line card and a three-line card keep the
   same shape across a row. Doctor cards put a single line of experience in
   here and would only gain dead space. */
.card:not(.card--doc) .card__txt {
  min-height: calc(var(--t-small) * var(--lh-body) * 3);
}

.doc__media { aspect-ratio: 1 / 1; }
/* Doctor headshots are usually portrait, so the square crop above trims
   evenly top and bottom by default — bias it upward so the head isn't cut. */
.doc__media img { object-position: top; }
.doc__spec { font-size: var(--t-micro); letter-spacing: .06em; text-transform: uppercase; color: var(--c-brand); font-weight: 700; margin-bottom: var(--s-1); display: block; }

/* On a phone the square photo makes each dentist a full screen of scroll,
   so the card turns into a row: portrait left, details right. Forced to a
   single column first — a row card inside a narrow grid track has no space
   for the text. */
@media (max-width: 599px) {
  .grid--doc { grid-template-columns: 1fr; gap: var(--s-3); }
  .card--doc { flex-direction: row; align-items: stretch; }
  .card--doc .card__media { flex: none; width: 96px; }
  .card--doc .card__body {
    padding: var(--s-3) var(--s-4);
    display: flex; flex-direction: column; justify-content: center;
    min-width: 0;   /* let long names wrap instead of overflowing the row */
  }
  .card--doc .card__title { margin-bottom: var(--s-1); }
}


/* ---------- Service icon badge ----------
   .card__media clips its photo, so the badge lives in the body and hangs
   upward off its top edge instead — which puts it half over the photo
   either way, and keeps it out of the overflow. */

.svcbadge {
  width: 56px; height: 56px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-md);
  background: var(--c-brand); color: var(--c-on-brand);
}
.svcbadge svg { width: 28px; height: 28px; }

.card .svcbadge {
  position: absolute; top: 0; left: var(--s-5);
  transform: translateY(-50%);
  box-shadow: var(--shadow-md);
}
/* Room for the half that hangs down, so it never lands on the title. */
.card--icon .card__body { padding-top: calc(28px + var(--s-4)); }

/* In the banner it simply leads the block. Brand navy on the veil measures
   about 1.1:1 — no edge at all — so it inverts there, the same way the
   header's Book button does. */
.phero .svcbadge { margin-bottom: var(--s-4); }
.phero--photo .svcbadge { background: var(--c-on-dark); color: var(--c-brand); }


/* ---------- Insurance marquee ----------
   Same seamless-loop technique as the treatment marquee below: the track
   is rendered twice and shifts by exactly half its own width, so the loop
   repeats with no visible seam. Spacing lives on the item as padding, not
   a gap on the track — a gap would leave the shift half a gap short and
   the seam would show.

   Unlike the treatment marquee, this is the only place these names
   appear, so the whole thing can't be hidden from assistive tech. Only
   the second (visual-only) copy carries aria-hidden, so a screen reader
   hears each provider once. */

.insmarquee { overflow: hidden; }
.insmarquee__track { display: flex; width: max-content; animation: insMarqueeRun 32s linear infinite; }
.insmarquee:hover .insmarquee__track { animation-play-state: paused; }
.insmarquee__set { display: flex; }

.insmarquee__i {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 190px; height: 92px; padding-inline: var(--s-6);
}
.insmarquee__i img {
  max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain;
  opacity: .62; filter: grayscale(1);
  transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.insmarquee__i:hover img { opacity: 1; filter: none; }
/* No logo: the provider's name stands in, in the same box, rather than
   leaving an empty gap in the row. */
.insmarquee__txt {
  font-family: var(--font-display); font-weight: 700; font-size: var(--t-small);
  color: var(--c-ink-muted); text-align: center; line-height: 1.25;
  transition: color var(--dur) var(--ease);
}
.insmarquee__i:hover .insmarquee__txt { color: var(--c-ink); }

@keyframes insMarqueeRun {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* No motion: the duplicate set (already out of the accessibility tree) is
   dropped, and the real one wraps into a static row instead of scrolling —
   the strip this replaced, in effect. */
@media (prefers-reduced-motion: reduce) {
  .insmarquee { overflow: visible; }
  .insmarquee__track { animation: none; width: auto; flex-wrap: wrap; justify-content: center; }
  .insmarquee__set[aria-hidden] { display: none; }
}


/* ---------- Call panel ----------
   Replaced the booking form when the clinic moved to phone-only bookings.
   The number is shown as text as well as being the link: people read a
   phone number even when they mean to tap it. */

.callpanel__lede { font-size: var(--t-small); color: var(--c-ink-soft); margin: 0 0 var(--s-4); }

.callpanel__call {
  display: flex; align-items: center; justify-content: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); min-height: 64px;
  background: var(--c-brand); color: var(--c-on-brand);
  border-radius: var(--r-md); text-decoration: none;
  transition: background var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}
.callpanel__call:hover  { background: var(--c-brand-deep); }
.callpanel__call:active { transform: translateY(1px); }
.callpanel__ico { width: 26px; height: 26px; fill: currentColor; flex: none; }
.callpanel__num {
  font-family: var(--font-display); font-weight: 700; letter-spacing: .01em;
  /* Stays on one line down to a 320px screen. */
  font-size: clamp(1.25rem, 1rem + 1.4vw, 1.5rem);
  white-space: nowrap;
}

.callpanel__h {
  font-family: var(--font-body); font-size: var(--t-micro); font-weight: 700;
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase; color: var(--c-brand);
  margin: var(--s-5) 0 var(--s-2);
}

.callpanel__nums { list-style: none; margin: 0; padding: 0; }
.callpanel__nums a {
  display: flex; align-items: center; min-height: 44px;
  font-size: 1.0625rem; font-weight: 600;
  color: var(--c-ink); text-decoration: none;
}
.callpanel__nums a:hover { color: var(--c-brand); }

.callpanel__hours { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.callpanel__hours li {
  display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap;
  font-size: var(--t-small);
}
.callpanel__hours span   { color: var(--c-ink); font-weight: 600; }
.callpanel__hours strong { color: var(--c-ink-soft); font-weight: 500; }


/* ---------- Appointment page ----------
   Four blocks in reading order: pitch, call card, form card, details. On a
   phone that is exactly the order wanted, so nothing is reordered. Above
   900px the grid lifts the first and last into a left column — placement,
   not `order`, so the DOM and the screen still agree.

   Both left blocks are sticky. Each is confined to its own grid row, so the
   pitch releases as its row ends and the details take over for the length of
   the form: something is always pinned, and they never overlap. That needs
   align-items: start — stretched items fill their row and have nowhere to
   travel. */

.apt { display: grid; gap: var(--s-5); }

/* Static by default, so the phone stack and any browser without the script
   below simply scroll it. */
.apt__side { position: static; }

@media (min-width: 900px) {
  .apt {
    grid-template-columns: 1fr 1.35fr;
    column-gap: var(--s-8);
    /* start, never stretch: a stretched item fills its row and sticky has
       nowhere left to travel. It also must not override align-self below. */
    align-items: start;
  }
  .apt__side { grid-column: 1; grid-row: 1 / 3; }
  .apt__call { grid-column: 2; grid-row: 1; }
  .apt__form { grid-column: 2; grid-row: 2; }

  /* The one sticky element on this page. Everything in the left column
     travels inside it, so no two parts can pin at the same moment and draw
     over each other — which is exactly what two sticky siblings did. */
  .apt__side {
    position: sticky;
    top: calc(var(--header-offset) + var(--s-4));
    align-self: start;
  }

  /* Set by the script when the column is taller than the viewport: pinning
     it then would cut the bottom off with no way to reach it. */
  .apt--nostick .apt__side { position: static; }
}

.apt__intro { margin-bottom: var(--s-6); }
.apt__intro h2 { font-size: var(--t-h2); }
.apt__intro .lede { margin-bottom: 0; }

.apt__info .aside__h:first-child { margin-top: 0; }
.apt__info .callpanel__hours { margin-bottom: var(--s-3); }
.apt__info .callpanel__nums a { font-size: var(--t-small); }


/* ---------- Booking form ----------
   Back alongside the call panel, not instead of it. Only rendered when SMTP
   is configured, so these styles idle on a site with email switched off. */

.bookform__t { font-size: var(--t-h3); margin-bottom: var(--s-1); }
.bookform__lede { font-size: var(--t-small); color: var(--c-ink-muted); margin-bottom: var(--s-5); }

/* Fieldsets carry the grouping to assistive tech as well as the eye. The
   browser default border and padding go; a hairline between groups does the
   separating. */
.bookform__grp {
  border: 0; padding: 0; margin: 0 0 var(--s-5);
  border-top: 1px solid var(--c-line); padding-top: var(--s-5);
}
.bookform__grp:first-of-type { border-top: 0; padding-top: 0; }
.bookform__leg {
  padding: 0; margin-bottom: var(--s-3);
  font-size: var(--t-micro); font-weight: 700;
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--c-brand);
}
.bookform__grp .bookform__row:last-child,
.bookform__grp .field:last-child { margin-bottom: 0; }

.bookform__row { display: grid; gap: var(--s-4); margin-bottom: var(--s-4); }
@media (min-width: 620px) { .bookform__row { grid-template-columns: 1fr 1fr; } }

.field { display: block; margin-bottom: var(--s-4); }
.field--wide { grid-column: 1 / -1; }
.field__lbl { display: block; font-size: var(--t-small); font-weight: 600; color: var(--c-ink); margin-bottom: var(--s-2); }
.field__opt { font-weight: 400; color: var(--c-ink-muted); font-size: var(--t-micro); }
.field__in {
  width: 100%; font-family: inherit; font-size: 1rem;   /* 16px stops iOS zoom-on-focus */
  padding: var(--s-3) var(--s-4);
  border: 1.5px solid var(--c-line); border-radius: var(--r-sm);
  background: var(--c-surface); color: var(--c-ink);
  min-height: 48px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.field__in:focus {
  outline: none; border-color: var(--c-brand);
  box-shadow: 0 0 0 3px var(--c-brand-wash);
}
textarea.field__in { min-height: 92px; resize: vertical; }

/* Consent to be contacted. The whole row is the tap target. */
.field--check {
  display: flex; align-items: flex-start; gap: var(--s-3);
  margin-bottom: var(--s-2); padding-block: var(--s-2);
  min-height: 44px; cursor: pointer;
}
.field__check {
  flex: none; width: 22px; height: 22px; margin-top: 2px;
  accent-color: var(--c-brand); cursor: pointer;
}
.field__consent { font-size: var(--t-small); color: var(--c-ink-soft); }

/* The whole label is the target, and it shows its own focus ring because the
   checkbox inside it is only 22px of it. */
.field--check { border-radius: var(--r-sm); }
.field--check:has(.field__check:focus-visible) {
  outline: 3px solid var(--c-accent); outline-offset: 3px;
}

/* Shown on blur by the script; empty and out of the flow until then. */
.field__msg { display: block; font-size: var(--t-micro); color: var(--c-error); margin-top: var(--s-2); }
.field__msg:empty { display: none; }
.field__in.is-bad { border-color: var(--c-error); }
.field__in.is-bad:focus { box-shadow: 0 0 0 3px var(--c-error-wash); }

/* Held at the size it will be, so the row does not jump when the label
   changes to "Sending...". */
.bookform button[type="submit"][aria-disabled="true"] { opacity: .72; cursor: default; }
.bookform__privacy { font-size: var(--t-micro); color: var(--c-ink-muted); margin: 0 0 var(--s-4); }
.bookform__note { font-size: var(--t-micro); color: var(--c-ink-muted); margin: var(--s-3) 0 0; text-align: center; }

/* $booking_minimal only: a way to ring instead of waiting for a callback,
   without a second full call panel taking up room in the card. */
.bookform__call {
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 44px; margin-top: var(--s-4);
  font-size: var(--t-small); font-weight: 600; color: var(--c-brand); text-decoration: none;
}
.bookform__call:hover { color: var(--c-brand-deep); }
.bookform__callico { width: 17px; height: 17px; fill: currentColor; flex: none; }

/* .bookform--tight (set together with $booking_minimal): the hero has one
   screen's worth of room, so the gaps between groups, rows and fields come
   in tighter than the spacious appointment-page form. Group headings are
   dropped in the markup rather than hidden here — removing the element,
   not just its text, is what actually gives the space back. */
.bookform--tight .bookform__grp { margin-bottom: var(--s-3); }
.bookform--tight .bookform__grp:not(:first-of-type) { padding-top: var(--s-3); }
.bookform--tight .bookform__row { gap: var(--s-3); }
.bookform--tight .field { margin-bottom: var(--s-2); }
.bookform--tight .field__lbl { margin-bottom: var(--s-1); }
.bookform--tight .field--check { margin-bottom: 0; padding-block: var(--s-1); }

/* Lives outside the form: an error must still speak when the form itself
   is not rendered. */
.bookform__err {
  background: var(--c-accent-wash); border-left: 3px solid var(--c-accent);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  padding: var(--s-3) var(--s-4); margin-bottom: var(--s-5);
  font-size: var(--t-small); color: var(--c-ink-soft);
}
.bookform__err p { margin: 0 0 var(--s-2); }
.bookform__err ul { margin: 0; padding-left: var(--s-5); }

/* Off-screen rather than display:none, which some bots know to skip. */
.bookform__hp {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
}

/* Confirmation shown in place of the form after a successful send. */
.bookdone { text-align: center; padding-block: var(--s-5); }
.bookdone__ico {
  width: 44px; height: 44px; margin: 0 auto var(--s-4);
  padding: 9px; border-radius: 50%;
  background: var(--c-brand-wash); color: var(--c-brand);
}
.bookdone__t { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.bookdone__p { font-size: var(--t-small); color: var(--c-ink-soft); margin-bottom: var(--s-4); }


/* ---------- Footer ---------- */

.ftr { background: var(--c-ink); color: var(--c-on-dark-soft); padding-block: var(--s-8) var(--s-5); margin-top: var(--section-y); }
.ftr__grid { display: grid; gap: var(--s-6); }
@media (min-width: 700px) { .ftr__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .ftr__grid { grid-template-columns: 1.3fr 1fr 1fr 1fr; } }
.ftr__logo { width: 52px; height: 52px; object-fit: contain; margin-bottom: var(--s-3); background: var(--c-surface); border-radius: var(--r-sm); padding: 1px; }
.ftr__name { font-family: var(--font-display); color: var(--c-on-dark); font-size: 1.05rem; margin-bottom: var(--s-1); }
.ftr__tag { font-size: var(--t-small); margin: 0; }
.ftr__h { font-size: var(--t-micro); letter-spacing: var(--ls-eyebrow); text-transform: uppercase; color: var(--c-accent); font-family: var(--font-body); font-weight: 700; margin-bottom: var(--s-3); }
.ftr__addr { font-size: var(--t-small); margin: 0; }
.ftr__hours { font-size: var(--t-small); margin-bottom: var(--s-3); display: flex; flex-direction: column; }
.ftr__days { color: var(--c-on-dark); font-weight: 600; }
.ftr__link { display: block; color: var(--c-on-dark-soft); text-decoration: none; font-size: var(--t-small); padding-block: var(--s-2); }
.ftr__link:hover { color: var(--c-on-dark); }
.ftr__link--wa { color: var(--c-whatsapp); font-weight: 600; }
.ftr__legal { border-top: 1px solid var(--c-on-dark-line); margin-top: var(--s-7); padding-top: var(--s-5); display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: space-between; }
.ftr__lic { display: flex; flex-wrap: wrap; gap: var(--s-4); font-size: var(--t-micro); margin: 0; }
.ftr__copy { font-size: var(--t-micro); margin: 0; }


/* ---------- Mobile action bar (signature) ---------- */

.actionbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
  display: grid; grid-template-columns: repeat(3, 1fr);
  background: color-mix(in srgb, var(--c-surface) 94%, transparent);
  backdrop-filter: blur(14px) saturate(1.5);
  border-top: 1px solid var(--c-line);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: var(--shadow-bar);
}
/* Call and Book only, when WhatsApp is switched off in config. */
.actionbar--2 { grid-template-columns: repeat(2, 1fr); }
.actionbar__btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: var(--actionbar-h); text-decoration: none;
  font-size: var(--t-micro); font-weight: 600; color: var(--c-ink-soft);
  position: relative;
}
.actionbar__btn + .actionbar__btn::before {
  content: ""; position: absolute; left: 0; top: 22%; bottom: 22%; width: 1px; background: var(--c-line);
}
.actionbar__btn svg { width: 21px; height: 21px; fill: currentColor; }
.actionbar__btn--wa { color: var(--c-whatsapp-deep); }
.actionbar__btn--book { color: var(--c-brand); }
.actionbar__btn:active { background: var(--c-brand-wash); }
@media (min-width: 900px) { .actionbar { display: none; } }


/* ---------- Desktop WhatsApp float ---------- */

.wafloat {
  position: fixed; right: 24px; bottom: 24px; z-index: 94;
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--c-whatsapp); color: var(--c-whatsapp-icon);
  display: none; align-items: center; justify-content: center;
  box-shadow: var(--shadow-lift);
  transition: transform var(--dur) var(--ease);
}
.wafloat svg { width: 30px; height: 30px; fill: currentColor; }
.wafloat:hover { transform: scale(1.07); }
@media (min-width: 900px) { .wafloat { display: flex; } }


/* Colour and shadow still respond; only the movement stops. */
@media (prefers-reduced-motion: reduce) {
  .card:hover, .card:active, .btn:active, .btn:hover,
  .shot:hover, .gal__i:hover img, .shot:hover img { transform: none; }
  .card:hover .card__arrow { transform: none; }
  .card:hover .card__media img { transform: none; }
}


/* ---------- Treatment marquee ----------
   The list is rendered twice and the track shifts by exactly half its own
   width, which lands the copy where the original started. That only holds
   if every item is the same shape, so the spacing lives inside the item as
   padding rather than as a gap on the track — a gap would leave the shift
   half a gap out and the seam would show. */

.marquee {
  overflow: hidden;
  background: var(--c-brand); color: var(--c-on-brand);
  padding-block: var(--s-4);
}
.marquee__track {
  display: flex; width: max-content;
  animation: marqueeRun 45s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__i {
  display: flex; align-items: center; gap: var(--s-6);
  padding-right: var(--s-6); white-space: nowrap;
  font-family: var(--font-display); font-weight: 600; font-size: var(--t-h3);
}
.marquee__i::after {
  content: ""; flex: none;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--c-accent);
}
@keyframes marqueeRun {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Decorative and permanently in motion, and every name it carries is in the
   grid above — so it goes rather than sits frozen mid-word. */
@media (prefers-reduced-motion: reduce) { .marquee { display: none; } }


/* ---------- Banner drift ----------
   6% over twenty seconds, once, easing out: slow enough to read as depth
   rather than movement. Both surfaces already clip, so nothing escapes.
   transform only, so it stays on the compositor. */

@keyframes bannerDrift {
  from { transform: scale(1); }
  to   { transform: scale(1.06); }
}
.hero__slide,
.phero--photo::before { animation: bannerDrift 20s ease-out forwards; }

@media (prefers-reduced-motion: reduce) {
  .hero__slide,
  .phero--photo::before { animation: none; }
}


/* ---------- Scroll reveal ---------- */

.reveal { transition: opacity .6s var(--ease), transform .6s var(--ease); }
/* Items in a row arrive in sequence rather than all at once. The index is
   set in PHP and capped at six, so a long grid never crawls. */
.js .reveal { transition-delay: calc(var(--i, 0) * 60ms); }
/* Hidden only once JS is confirmed running — see the guard in head.php. */
.js .reveal { opacity: 0; transform: translateY(24px); }
.js .reveal.is-in { opacity: 1; transform: none; }
/* Set by the guard timer when main.js never arrived: show everything. */
.no-reveal .reveal { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .js .reveal { opacity: 1; transform: none; } }


/* ---------- Offer banner (set from the admin page) ---------- */

.offerbar {
  background: var(--c-ink);
  color: var(--c-on-dark);
  font-size: var(--t-small);
  font-weight: 600;
  text-align: center;
}
.offerbar p { margin: 0; padding-block: var(--s-3); }


/* ---------- Inner page hero ---------- */

.phero {
  --phero-pad: var(--s-7);
  position: relative;
  display: flex; align-items: center;
  min-height: clamp(280px, 34vw, 420px);
  /* Safe now the breadcrumb chip is a sibling: this keeps the slow zoom
     from spilling past the banner's edges. */
  overflow: hidden;
  background: var(--c-brand-wash);
  border-bottom: 1px solid var(--c-line);
  padding-block: var(--phero-pad);
}
.phero__in { width: 100%; }
.phero__t { font-size: var(--t-h1); letter-spacing: -.015em; margin-bottom: var(--s-3); }
.phero__s { font-size: 1.0625rem; color: var(--c-ink-muted); max-width: 56ch; margin: 0; }

/* With a photo it takes the same veil as the home hero, so the contrast
   guarantee is the same one: at the veil's lightest point, over a
   blown-out white photo, white text holds well clear of 4.5:1. */
.phero--photo {
  background: var(--c-ink);
  border-bottom: 0;
  /* Slides up under the header, which floats over it on these pages, and
     puts the height back so the title still clears it. */
  margin-top: calc(var(--header-h) * -1);
  padding-top: calc(var(--phero-pad) + var(--header-h));
}
.phero--photo::before,
.phero--photo::after { content: ""; position: absolute; inset: 0; }
.phero--photo::before {
  background-image: var(--phero-img);
  background-size: cover;
  background-position: center;
}
.phero--photo::after { background: var(--c-hero-veil); }
.phero--photo .phero__in { position: relative; z-index: 1; }
.phero--photo .phero__t { color: var(--c-on-dark); }
.phero--photo .phero__s { color: var(--c-on-dark-soft); }
.phero--photo .eyebrow { color: var(--c-on-dark); }

/* service.php passes a treatment's short description straight through, and
   reception pastes paragraphs into that field. Two lines is all a banner
   can carry; the full text is on the page below. */
.phero__s {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Breadcrumb chip, anchored to the banner's bottom edge and hanging just
   below it. The banner must not clip, so no overflow here. */
.crumbs {
  position: relative; z-index: 2;
  margin-top: calc(var(--s-5) * -1);   /* lifts it back onto the banner edge */
}
.crumbs__list {
  display: inline-flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-2); list-style: none; margin: 0;
  padding: var(--s-2) var(--s-5); min-height: 44px;
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-pill); box-shadow: var(--shadow-sm);
  font-size: var(--t-small);
}
.crumbs__i { display: flex; align-items: center; gap: var(--s-2); }
.crumbs__i + .crumbs__i::before { content: "/"; color: var(--c-ink-muted); opacity: .6; }
.crumbs__ico { width: 15px; height: 15px; fill: var(--c-ink-muted); flex: none; }
.crumbs__i a { color: var(--c-ink-soft); text-decoration: none; padding-block: var(--s-2); }
.crumbs__i a:hover { color: var(--c-brand); }
.crumbs__i [aria-current] { color: var(--c-ink); font-weight: 600; }


/* ---------- Two-column page layout ---------- */

.booklayout { display: grid; gap: var(--s-6); }
@media (min-width: 900px) {
  .booklayout { grid-template-columns: 1.5fr 1fr; align-items: start; }
  /* Contact leads with the details, so the wide column is the second one —
     the map lands on the right without reordering anything. */
  .booklayout--contact { grid-template-columns: 1fr 1.5fr; }

  /* Neither column here is sticky, so nothing needs align-items: start to
     keep travelling room free. Stretched instead, the info card's border
     grows to match the photo/map column instead of stopping short and
     leaving a gap of bare page background beside it — the clinic photo
     above the map often makes that column the taller of the two. */
  .booklayout--contact { align-items: stretch; }
}


.panel {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-lg); padding: var(--s-5); box-shadow: var(--shadow-sm);
}
.aside {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-lg); padding: var(--s-5); box-shadow: var(--shadow-sm);
}
.aside__h {
  font-family: var(--font-body); font-size: var(--t-micro); font-weight: 700;
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase; color: var(--c-brand);
  margin: var(--s-5) 0 var(--s-2);
}
.aside__h:first-child { margin-top: 0; }
.aside__phone { display: block; font-size: 1.0625rem; font-weight: 600; color: var(--c-ink);
  text-decoration: none; padding-block: var(--s-2); }
.aside__phone:hover { color: var(--c-brand); }
.aside__hours { font-size: var(--t-small); margin-bottom: var(--s-3); }
.aside__addr { font-size: var(--t-small); margin-bottom: var(--s-4); }

.mapbox { border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--c-line); }


/* ---------- Doctor detail ---------- */

.back-link { display: inline-block; font-size: var(--t-small); margin-bottom: var(--s-5); text-decoration: none; }
.docpage { display: grid; gap: var(--s-6); }
@media (min-width: 800px) { .docpage { grid-template-columns: 360px 1fr; align-items: start; gap: var(--s-8); } }
.docpage__img {
  width: 100%; aspect-ratio: 1/1; object-fit: cover; object-position: top;
  border-radius: var(--r-lg); background: var(--c-brand-wash); box-shadow: var(--shadow-md);
  border: 1px solid var(--c-line);
}
.meta { list-style: none; margin: 0 0 var(--s-5); padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-6); }
.meta li { display: flex; flex-direction: column; }
.meta span { font-size: var(--t-micro); color: var(--c-ink-muted); text-transform: uppercase; letter-spacing: .08em; }
.meta strong { color: var(--c-ink); font-size: 1.05rem; }

.certs { margin-top: var(--s-8); padding-top: var(--s-6); border-top: 1px solid var(--c-accent); }
.certs__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
@media (min-width: 760px) { .certs__list { grid-template-columns: 1fr 1fr; gap: var(--s-3) var(--s-6); } }
.certs__list li {
  position: relative; padding-left: var(--s-5); font-size: var(--t-small); color: var(--c-ink-soft);
}
.certs__list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 7px; height: 7px; border-radius: 50%; background: var(--c-accent);
}


/* ---------- About page ---------- */

/* Optional page photo. Fixed ratio so the space is reserved before it
   loads, whatever shape was uploaded. */
.pagephoto {
  width: 100%; aspect-ratio: 16 / 7; object-fit: cover;
  border-radius: var(--r-lg); margin-bottom: var(--s-6);
  background: var(--c-brand-wash); box-shadow: var(--shadow-sm);
  border: 1px solid var(--c-line);
}

.prose { max-width: var(--maxw-text); }
.stats { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin-top: var(--s-8); }
.stat {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); padding: var(--s-5); text-align: center;
}
.stat__n { display: block; font-family: var(--font-display); font-size: 2.1rem; font-weight: 700; color: var(--c-brand); line-height: 1; }
.stat__l { display: block; font-size: var(--t-micro); color: var(--c-ink-muted); margin-top: var(--s-2); }

.licard {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); padding: var(--s-4) var(--s-5);
}
.licard__k { display: block; font-size: var(--t-micro); letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase; color: var(--c-brand); font-weight: 700; }
.licard__v { display: block; font-family: ui-monospace, monospace; color: var(--c-ink); margin-top: var(--s-1); }

/* A short point in a card — a value ("what matters to us") or a
   capability ("how we plan treatment"), same shape either way. */
.value {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); padding: var(--s-5);
}
.value__t { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.value__p { font-size: var(--t-small); color: var(--c-ink-soft); margin: 0; }


/* ---------- Band CTA ---------- */

.band { display: flex; gap: var(--s-5); align-items: center; flex-wrap: wrap; }
.band > div { flex: 1; min-width: 260px; }
.band h2 { margin-bottom: var(--s-2); }
.band .lede { margin: 0; }


/* ---------- Gallery ---------- */

.filters { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-6); }
.chip {
  font: inherit; font-size: var(--t-small); font-weight: 600;
  padding: var(--s-2) var(--s-4); min-height: 44px;
  border: 1.5px solid var(--c-line); border-radius: var(--r-pill);
  background: var(--c-surface); color: var(--c-ink-soft); cursor: pointer;
  transition: all var(--dur-fast) var(--ease);
}
.chip:hover { border-color: var(--c-brand); color: var(--c-brand); }
.chip.is-on { background: var(--c-brand); border-color: var(--c-brand); color: var(--c-on-brand); }

.gal { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.gal__i {
  margin: 0; border-radius: var(--r-md); overflow: hidden;
  background: var(--c-surface); border: 1px solid var(--c-line);
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.gal__i:hover { box-shadow: var(--shadow-md); border-color: var(--c-brand); }
.gal__i img {
  width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block;
  transition: transform var(--dur) var(--ease);
}
.gal__i:hover img { transform: scale(1.06); }
.gal__i figcaption { font-size: var(--t-micro); color: var(--c-ink-muted); padding: var(--s-3); }
.gal__i[hidden] { display: none; }

.empty-state {
  background: var(--c-surface); border: 1px dashed var(--c-line);
  border-radius: var(--r-lg); padding: var(--s-8) var(--s-5); text-align: center;
}
.empty-state p { max-width: 48ch; margin: 0 auto var(--s-5); color: var(--c-ink-muted); }


/* ---------- Lightbox ----------
   Full-viewport dialog. The ground is painted on the element itself rather
   than on ::backdrop, which does not reliably inherit custom properties. */

.lb {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; padding: 0; border: 0;
  background: var(--c-lb-ground); color: var(--c-on-dark);
  overflow: hidden;
}
.lb[open] { display: block; }
.lb::backdrop { background: var(--c-lb-ground); }

.lb__fig {
  margin: 0; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-4);
  padding: calc(var(--s-8) + env(safe-area-inset-top)) var(--s-4)
           calc(var(--s-7) + env(safe-area-inset-bottom));
}
.lb__img {
  max-width: 100%; min-height: 0;
  width: auto; height: auto; object-fit: contain;
  border-radius: var(--r-md);
}
.lb__cap {
  font-size: var(--t-small); color: var(--c-on-dark-soft);
  text-align: center; max-width: 60ch; flex: none;
}
.lb__count { opacity: .75; margin-left: var(--s-2); }

.lb__btn {
  position: absolute; z-index: 1;
  display: grid; place-items: center;
  width: 48px; height: 48px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--c-on-dark-line); color: var(--c-on-dark);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.lb__btn:hover { background: var(--c-on-dark-soft); color: var(--c-ink); }
.lb__btn svg { width: 24px; height: 24px; }
.lb__x   { top: calc(var(--s-4) + env(safe-area-inset-top)); right: var(--s-4); }
.lb__nav { top: 50%; transform: translateY(-50%); }
.lb__nav--prev { left: var(--s-3); }
.lb__nav--next { right: var(--s-3); }
.lb--single .lb__nav { display: none; }


/* ---------- Health-authority advisory ----------
   Guidance requires this to be legible at roughly the same size as
   surrounding body text, so it is deliberately NOT small print. */

.hnotice {
  border-left: 3px solid var(--c-accent);
  background: var(--c-accent-wash);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: var(--s-4) var(--s-5);
  margin-block: var(--s-6);
}
.hnotice p { font-size: var(--t-body); color: var(--c-ink-soft); margin: 0 0 var(--s-2); }
.hnotice p:last-child { margin-bottom: 0; }


/* ---------- Doctor profile extras ---------- */

/* Static by default — sticky only from 900px, matching the rest of the
   site. The two-column grid itself starts at 800px (a photo and two
   buttons need less room than that to look right beside body text), so
   between 800 and 899px this is a plain two-column layout with a column
   that scrolls normally, not stuck. */
.docpage__side { position: static; }
@media (min-width: 900px) {
  .docpage__side {
    position: sticky;
    top: calc(var(--header-offset) + var(--s-4));
    align-self: start;
  }
}

.docsec {
  font-size: var(--t-h3); margin: var(--s-7) 0 var(--s-4);
  padding-top: var(--s-5); border-top: 1px solid var(--c-accent);
}

.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.chiplink {
  display: inline-block; font-size: var(--t-small); font-weight: 500;
  padding: var(--s-2) var(--s-4); border-radius: var(--r-pill);
  background: var(--c-brand-wash); color: var(--c-brand-deep); text-decoration: none;
  transition: background var(--dur-fast) var(--ease);
}
.chiplink:hover { background: var(--c-brand); color: var(--c-on-brand); }

.avail { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.avail li {
  display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap;
  padding: var(--s-3) var(--s-4); background: var(--c-surface);
  border: 1px solid var(--c-line); border-radius: var(--r-sm);
}
.avail span { color: var(--c-ink); font-weight: 600; font-size: var(--t-small); }
.avail strong { color: var(--c-ink-soft); font-weight: 500; font-size: var(--t-small); }
.fine { font-size: var(--t-micro); color: var(--c-ink-muted); margin-top: var(--s-3); }

.shots { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.shots--work { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.shot {
  display: block; border-radius: var(--r-md); overflow: hidden;
  border: 1px solid var(--c-line); background: var(--c-surface);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.shot:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.shot img {
  width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block;
  transition: transform var(--dur) var(--ease);
}
.shot:hover img { transform: scale(1.06); }
.shots--work .shot img { aspect-ratio: 1/1; }


/* ---------- Service rows (services.php) ----------
   Full-width alternating rows, not a grid — this is a page visitors scroll
   through treatment by treatment, so the rhythm between rows matches a
   section boundary rather than a card gap.

   Media first in the markup, text second: that is the even-row order
   (image left) and the default. Odd rows reverse with flex-direction, so
   nothing in the DOM has to change — row order and reading order still
   agree for a screen reader either way. Stacked below 900px regardless of
   position; alternating on a narrow column just reads as inconsistent. */

.svcrows { display: flex; flex-direction: column; gap: var(--section-y); }

.svcrow { display: flex; flex-direction: column; gap: var(--s-5); }
@media (min-width: 900px) {
  .svcrow { flex-direction: row; align-items: center; gap: var(--s-8); }
  .svcrow:nth-child(odd) { flex-direction: row-reverse; }
}

.svcrow__media, .svcrow__body { flex: 1 1 50%; min-width: 0; width: 100%; }

.svcrow__media img, .svcrow__ph {
  width: 100%; aspect-ratio: 4/3; object-fit: cover;
  border-radius: var(--r-md); box-shadow: var(--shadow-sm);
  border: 1px solid var(--c-line);
}
/* No photo: the same box, in the brand wash, rather than collapsing the
   row down to the text side alone. */
.svcrow__ph { background: var(--c-brand-wash); }

/* Name first, then the optional listing subtitle set in the admin — no
   full description here, that already has its own destination (the
   "Full details" link below). */
.svcrow__t { font-size: var(--t-h2); margin-bottom: var(--s-5); }
/* Tucked in against the subtitle when there is one, rather than leaving
   the full gap meant for going straight to the buttons. */
.svcrow__t:has(+ .svcrow__sub) { margin-bottom: var(--s-2); }
.svcrow__sub { font-size: 1.0625rem; color: var(--c-ink-soft); margin-bottom: var(--s-5); }

.svcrow__acts { display: flex; align-items: center; gap: var(--s-5); flex-wrap: wrap; }
.svcrow__more {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-small); font-weight: 600; color: var(--c-brand); text-decoration: none;
}
.svcrow__more:hover { color: var(--c-brand-deep); }
.svcrow__arrow { transition: transform var(--dur) var(--ease); }
.svcrow__more:hover .svcrow__arrow { transform: translateX(4px); }


/* ---------- Treatment detail page ---------- */

.svclayout { display: grid; gap: var(--s-6); }
@media (min-width: 900px) {
  .svclayout { grid-template-columns: 1.6fr 1fr; align-items: start; gap: var(--s-8); }
  /* Treatment pages run long — steps, results, FAQs — and the Book button
     lives at the bottom of this panel. Keep it on screen. Scoped to this
     layout: the Contact and Appointment asides are page content, not a
     rail, and should scroll away normally. */
  .svclayout .aside {
    position: sticky;
    top: calc(var(--header-offset) + var(--s-4));
    /* If the panel is taller than the viewport, scroll it internally
       rather than letting the button fall off the bottom. */
    max-height: calc(100dvh - var(--header-offset) - var(--s-7));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

.svcimg {
  width: 100%; aspect-ratio: 3/2; object-fit: cover;
  border-radius: var(--r-lg); margin-bottom: var(--s-6);
  background: var(--c-brand-wash); box-shadow: var(--shadow-sm);
  border: 1px solid var(--c-line);
}

.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); counter-reset: s; }
.steps li {
  display: flex; gap: var(--s-4); align-items: flex-start;
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); padding: var(--s-4);
}
.steps__n {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  background: var(--c-brand-wash); color: var(--c-accent);
  display: grid; place-items: center;
  font-weight: 700; font-size: var(--t-small);
}

.faqs { display: grid; gap: var(--s-2); }
.faq {
  background: var(--c-surface); border: 1px solid var(--c-line);
  border-radius: var(--r-md); padding: var(--s-4) var(--s-5);
}
.faq summary {
  cursor: pointer; font-weight: 600; color: var(--c-ink);
  font-size: var(--t-body); list-style: none; position: relative; padding-right: var(--s-6);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; position: absolute; right: 0; top: -2px;
  font-size: 1.3rem; color: var(--c-brand); line-height: 1;
  transition: transform var(--dur) var(--ease);
}
.faq[open] summary::after { content: "\2212"; }
.faq p { margin: var(--s-3) 0 0; font-size: var(--t-small); color: var(--c-ink-soft); }

.provider {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0; text-decoration: none; color: inherit;
}
.provider img { width: 46px; height: 46px; border-radius: 50%; object-fit: cover; background: var(--c-brand-wash); flex: none; }
.provider strong { display: block; color: var(--c-ink); font-size: var(--t-small); }
.provider em { display: block; font-style: normal; color: var(--c-ink-muted); font-size: var(--t-micro); }
.provider:hover strong { color: var(--c-brand); }

/* Card foot. auto top margin drops it to the bottom of the body, so the
   link sits on one line across a row whatever the descriptions do above it.
   Not an <a> — the whole card is already the link, and links do not nest. */
.card__more {
  margin: auto 0 0; padding-top: var(--s-4);
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-small); font-weight: 600; color: var(--c-brand);
}
.card__arrow { transition: transform var(--dur) var(--ease); }
.card:hover .card__arrow { transform: translateX(4px); }
