/* ============================================================================
   L'Ateleir · Site-level base styles
   ----------------------------------------------------------------------------
   Page-level rules shared by every page (previously duplicated inline in each
   page's <style> block by the Claude Design export). Load AFTER
   design-system.css so the Playfair display override below wins.
   ============================================================================ */

html { scroll-behavior: smooth; }

/* Site uses the "Bold Luxe" display face (Playfair) site-wide. This overrides
   the design system's default (Cormorant Garamond). */
:root { --font-display: 'Playfair Display', Georgia, 'Times New Roman', serif; }

/* Default (light) page background · warm ivory with soft gold/blush glows. */
body {
  background:
    radial-gradient(120% 78% at 100% -5%, rgba(var(--glow-lite-rgb),0.13), rgba(var(--glow-lite-rgb),0) 44%),
    radial-gradient(95% 70% at -5% 102%, rgba(var(--glow-warm-rgb),0.22), rgba(var(--glow-warm-rgb),0) 48%),
    var(--porcelain);
  background-attachment: fixed;
}

/* Dark page variant (e.g. the ICOONE landing page). Add class="page-dark" to
   <body> to opt in. */
body.page-dark { background: var(--espresso); }

a { color: var(--gold-deep); }
a:hover { color: var(--gold-antique); }

/* Custom scrollbar (WebKit) */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-thumb { background: var(--gold-antique); border-radius: 999px; }
body.page-dark::-webkit-scrollbar-thumb { background: var(--espresso); border-radius: 0; }

/* Shared keyframes used by inline animations in the markup. */
@keyframes lxSpin { to { transform: rotate(360deg); } }
@keyframes lxFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }

/* Respect reduced-motion: disable all CSS animations. site.js also checks
   this and skips scroll/hover motion. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
}

/* FAQ accordions built with native <details>. */
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }
details[open] .faq-plus { transform: rotate(45deg); }

/* ---------------------------------------------------------------------------
   Image placeholder · a labelled box marking where real photography goes.
   These replace the Claude Design drag-and-drop <image-slot> elements, which
   only worked inside Claude Design's editor.

   To drop in a real photo, replace the whole
     <div class="image-placeholder" ...><span>...</span></div>
   with an <img>, e.g.:
     <img src="assets/img/your-photo.jpg" alt="descriptive alt text"
          style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover;">
   (keep the parent element's positioning/overflow as-is).
   --------------------------------------------------------------------------- */
.image-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 12px;
  box-sizing: border-box;
  background: repeating-linear-gradient(45deg,
    rgba(var(--glow-deep-rgb),0.05) 0 10px,
    rgba(var(--glow-deep-rgb),0.10) 10px 20px);
  border: 1px dashed rgba(var(--glow-deep-rgb),0.5);
  color: var(--gold-deep);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.45;
}
.image-placeholder > span { max-width: 92%; }

/* ---------------------------------------------------------------------------
   Full-bleed photo band · a wide edge-to-edge image strip between sections
   (home page uses one for the studio entryway).

   Everything else on the site handles its own responsiveness with inline
   `aspect-ratio` + `object-fit:cover`, which needs no media query. This one
   does: a 21:9 letterbox is cinematic on a laptop but collapses to a ~170px
   sliver on a phone, so narrow screens get a taller, more upright crop.

   Markup:
     <div class="photo-band"><img src="..." alt="..."></div>
   --------------------------------------------------------------------------- */
.photo-band {
  position: relative;
  overflow: hidden;
  aspect-ratio: 21 / 9;
  background: var(--champagne); /* holds the space while the image loads */
}
.photo-band > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@media (max-width: 760px) { .photo-band { aspect-ratio: 4 / 3; } }

/* ---------------------------------------------------------------------------
   Team bios · long bio inside a person card, with an inline "Read more"
   disclosure at the end of the opening snippet.

   Built on native <details>/<summary>, so it works with no JavaScript and is
   keyboard-operable for free (Tab to the summary, Enter/Space to toggle).
   Both elements are block-level by default, so they're forced inline here to
   sit at the end of the snippet's last sentence.

   Markup:
     <div class="bio">
       Opening snippet sentence.
       <details class="bio-more">
         <summary>
           <span class="bio-more-open">Read more</span>
           <span class="bio-more-close">Read less</span>
           <span class="bio-caret" aria-hidden="true">&#9662;</span>
         </summary>
         <div class="bio-rest"><p>…</p><p>…</p></div>
       </details>
     </div>

   NOTE · the card simply grows taller when opened (no max-height animation, no
   scrolling inner box). That's intentional for now.
   --------------------------------------------------------------------------- */
.bio {
  margin-top: 18px;
  text-align: left;
  font-size: var(--fs-body);
  line-height: 1.75;
  color: var(--taupe);
}

.bio-more { display: inline; }

.bio-more > summary {
  display: inline;
  list-style: none;
  cursor: pointer;
  white-space: nowrap;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: #A2988A; /* deliberately lighter than the body copy around it */
  transition: color 0.16s ease;
}
.bio-more > summary::-webkit-details-marker { display: none; }
.bio-more > summary:hover,
.bio-more > summary:focus-visible { color: var(--gold-deep); }

.bio-caret {
  display: inline-block;
  margin-left: 5px;
  font-size: 9px;
  line-height: 1;
  transition: transform 0.2s var(--ease-out);
}
.bio-more[open] .bio-caret { transform: rotate(180deg); }

/* Swap the label text with the open state. */
.bio-more-close { display: none; }
.bio-more[open] .bio-more-open { display: none; }
.bio-more[open] .bio-more-close { display: inline; }

/* The revealed remainder of the bio · a step smaller than the snippet. */
.bio-rest {
  display: block;
  margin-top: 14px;
  font-size: 13.5px;
  line-height: 1.72;
}
.bio-rest p { margin: 0 0 12px; }
.bio-rest p:last-child { margin-bottom: 0; }

/* Collapse control at the FOOT of an expanded bio. Once a long bio is open the
   summary toggle is far back up the page, so site.js appends one of these to
   each .bio-rest. It's injected by script (not written into the markup) so it
   can never sit there dead if JavaScript doesn't run · the summary alone still
   opens and closes the disclosure. */
.bio-collapse {
  display: block;
  margin: 16px 0 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: #A2988A;
  transition: color 0.16s ease;
}
.bio-collapse:hover,
.bio-collapse:focus-visible { color: var(--gold-deep); }

@media (prefers-reduced-motion: reduce) {
  .bio-caret { transition: none; }
}

/* ---------------------------------------------------------------------------
   Header navigation · Technology dropdown (added in the 2026 content update).
   The header markup is inline-styled per page; these classes hook ONLY the new
   dropdown, so the rest of the nav is untouched. The menu opens on hover and on
   keyboard focus (:focus-within), so it is reachable without JavaScript.
   --------------------------------------------------------------------------- */
.nav-dd { position: relative; display: inline-flex; align-items: center; }

/* The drawer's expand control. Desktop opens these menus on hover, so the
   button has no job here and is not rendered at all. It comes back inside the
   1024px media query below. */
.nav-dd-expand { display: none; }

.nav-dd-caret {
  margin-left: 6px;
  font-size: 9px;
  line-height: 1;
  color: var(--gold-deep);
  transition: transform 0.2s var(--ease-out);
}

.nav-dd-menu {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 210px;
  margin-top: 12px;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--marble);
  border: 1px solid var(--champagne);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.18s var(--ease-out), transform 0.18s var(--ease-out), visibility 0.18s;
  z-index: 70;
}
/* Invisible hover bridge so the menu doesn't close in the gap below the toggle. */
.nav-dd-menu::before { content: ""; position: absolute; top: -12px; left: 0; right: 0; height: 12px; }

.nav-dd:hover .nav-dd-menu,
.nav-dd:focus-within .nav-dd-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nav-dd:hover .nav-dd-caret,
.nav-dd:focus-within .nav-dd-caret { transform: rotate(180deg); }

.nav-dd-menu a {
  display: block;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--espresso);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.16s ease, color 0.16s ease;
}
.nav-dd-menu a:hover,
.nav-dd-menu a:focus-visible { background: var(--cream); color: var(--gold-deep); text-decoration: none; }

/* Thin divider inside a dropdown menu. Used in the Treatments menu to separate
   the four areas of care from the two entry-point paths (Bridal, Medical). */
.nav-dd-sep {
  display: block;
  height: 1px;
  margin: 6px 8px;
  background: var(--line);
}

/* ---------------------------------------------------------------------------
   Header navigation · layout + mobile drawer ("hamburger")
   ----------------------------------------------------------------------------
   Above --nav-breakpoint the header is one row: logo · links · Book button.
   Below it the links collapse into a drawer behind the hamburger, while the
   Book button STAYS VISIBLE in the header bar (booking is the whole point of
   the site, so it never hides behind a menu).

   Structure the markup gives us, in every page's <header>:

     <div class="nav-right">              · right-hand group
       <nav id="site-nav" class="site-nav"> …links… </nav>
       <a class="nav-book" …>Book a Consultation</a>
       <button class="nav-toggle" …>     · hamburger, mobile only
     </div>

   The drawer is positioned against the sticky <header> (a sticky element is a
   positioned one, so `position:absolute` resolves to it), which is why the
   panel spans the full viewport width rather than the 1280px inner container.
   --------------------------------------------------------------------------- */
/* THE BREAKPOINT IS 1024px. It can't live in a custom property — media queries
   can't read them — so it is written literally in the @media rules below. If
   you change it, change it in all of them AND in NAV_BREAKPOINT in
   assets/js/site.js, which uses it to close the drawer on resize.
   (The narrower 640/430/340px queries further down are a separate concern:
   they only slim the logo and Book button, they don't move the breakpoint.) */

/* Brand logo. The supplied artwork already contains "L'Ateleir / AESTHETICS &
   WELLNESS", so it is the whole lockup — there is no separate text beside it.
   Height is set here (not inline) so the responsive steps below don't need
   !important; the img's width/height attributes carry the aspect ratio. */
.nav-logo { display: flex; align-items: center; text-decoration: none; line-height: 1; }
.nav-logo-img { display: block; height: 72px; width: auto; }

/* Footer lockup. Uses the white-wordmark artwork, not the logo-mark.png the
   header carries: that one's wordmark is BLACK, so on the dark footer only the
   gold monogram and wreath survived and the name vanished. This file already
   contains the name, which is why the footer no longer sets it in type beside
   it. Sized a step above the 72px header mark. */
.footer-logo { display: block; height: 88px; width: auto; }
@media (max-width: 720px) {
  .footer-logo { height: 76px; }
}

/* Home page only · logo hand-off from the hero to the header.
   index.html opens with a large brand lockup inside the espresso hero, so the
   small nav mark alongside it just prints the logo twice. site.js adds
   .home-logo-swap to <html> when that hero lockup exists, and .nav-logo-lit
   once the lockup has scrolled up behind the header.

   Two deliberate choices:
   - visibility, not display · the <a> keeps its box, so the header height (and
     therefore every scroll offset measured from it) never changes, and a hidden
     logo is skipped by both the keyboard and screen readers.
   - hidden state lives behind .home-logo-swap · no JS means the class is never
     added and the logo simply stays visible, on every page including this one.

   The fade is a third class rather than part of the rule above so the very
   first state lands instantly. Otherwise setting up the page would itself be a
   transition, and a slow first paint could show the logo fading out on load. */
.home-logo-swap .nav-logo-img {
  visibility: hidden;
  opacity: 0;
}
.home-logo-swap.nav-logo-lit .nav-logo-img {
  visibility: visible;
  opacity: 1;
}
.home-logo-swap.nav-logo-fade .nav-logo-img {
  transition: opacity 0.5s ease, visibility 0.5s;
}
@media (prefers-reduced-motion: reduce) {
  .home-logo-swap.nav-logo-fade .nav-logo-img { transition: none; }
}

/* ---------------------------------------------------------------------------
   Home hero · brand lockup beside the headline (index.html only)
   ---------------------------------------------------------------------------
   The lockup is near-square (723x778, so ~0.93), and the headline needs 407px
   to keep its "You, beautifully / considered." two-line break. The hero
   container caps at 1280px, so at that width and above the column is a stable
   586px: 139px logo + 22px gap + 415px headline fits, with slack.

   Size is capped deliberately. 150px against 706px of real artwork is ~4.7x
   oversampled, i.e. sharp even on a 3x phone. The artwork stops being safe
   somewhere past ~250px, so don't raise these numbers without a bigger file
   (ideally an SVG — see the asset request).

   Below ~1260px the column is too narrow for all three and the headline would
   silently break to THREE lines. Rather than pin that to a magic breakpoint,
   the row is `flex-wrap: wrap` with a min-width on the headline — when the
   headline can no longer get its 415px the row wraps on its own and the logo
   moves above it. Self-healing at every width.

   Costs no vertical height while side-by-side: the treatment photo already
   sets the height of the espresso band and the logo fits inside it.
   --------------------------------------------------------------------------- */
.hero-headline-row { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.hero-logo { display: block; width: auto; height: 150px; flex: none; }
/* 415px is the measured 407px the two-line break needs, plus a little slack for
   font-loading differences. Don't lower it — that reintroduces the third line. */
.hero-headline-row h1 { flex: 1 1 415px; min-width: 415px; }

/* Once the hero itself is one column, drop the headline's min-width or it would
   force horizontal overflow on a phone, and slim the logo to suit. */
@media (max-width: 720px) {
  .hero-headline-row { gap: 18px; }
  .hero-headline-row h1 { flex-basis: 100%; min-width: 0; }
  .hero-logo { height: 128px; }
}

/* Right-hand group: links, Book button, hamburger. */
.nav-right {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 22px);
}

/* The link row itself. (Styles live here rather than inline on <nav> so the
   mobile rules below can restyle it without fighting an inline style.) */
.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 22px);
}

/* Hamburger · hidden on desktop, shown below the breakpoint. */
.nav-toggle {
  display: none;             /* flipped to inline-flex in the media query */
  align-items: center;
  justify-content: center;
  flex: none;
  width: 44px;               /* 44px = comfortable touch target */
  height: 44px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--champagne);
  border-radius: var(--radius-sm);
  color: var(--espresso);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-toggle:hover { border-color: var(--gold-antique); }

/* Three stacked bars, drawn as spans so they can morph into an X. */
.nav-toggle-bars {
  position: relative;
  display: block;
  width: 20px;
  height: 14px;
}
.nav-toggle-bars span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 0.24s var(--ease-out), opacity 0.16s linear;
}
.nav-toggle-bars span:nth-child(1) { top: 0; }
.nav-toggle-bars span:nth-child(2) { top: 6.25px; }
.nav-toggle-bars span:nth-child(3) { top: 12.5px; }

/* Open state · top and bottom bars cross, middle bar fades out. */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(1) {
  transform: translateY(6.25px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(3) {
  transform: translateY(-6.25px) rotate(-45deg);
}

/* ---------------------------------------------------------------------------
   Below the breakpoint · links become a drawer, Book button goes compact.
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
  .nav-toggle { display: inline-flex; }

  /* The drawer. Closed by default — `visibility:hidden` also takes it out of
     the accessibility tree and tab order, so a collapsed menu can't be tabbed
     into. site.js adds .is-open. */
  .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    max-height: 78vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 8px 28px 22px;
    background: var(--porcelain);
    border-bottom: 1px solid var(--champagne);
    box-shadow: 0 18px 40px rgba(var(--ink-rgb), 0.14);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    pointer-events: none;
    /* visibility is stepped, not eased. Easing it would leave the menu
       computing as `hidden` for the first instant of the open transition, and
       a link that is still hidden can't take focus — so the drawer would open
       but keyboard focus would refuse to move into it. Here it flips to
       `visible` immediately on open, and back to `hidden` only after the fade
       out has finished (0s duration, delayed by the fade). */
    transition: opacity 0.22s var(--ease-out), transform 0.22s var(--ease-out),
                visibility 0s linear 0.22s;
  }
  .site-nav.is-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    pointer-events: auto;
    transition: opacity 0.22s var(--ease-out), transform 0.22s var(--ease-out),
                visibility 0s;
  }

  /* Top-level links become full-width rows with a comfortable tap target. */
  .site-nav > a,
  .site-nav .nav-dd-toggle {
    display: flex !important;      /* beats the inline display on each link */
    align-items: center;
    justify-content: space-between;
    padding: 15px 2px;
    border-bottom: 1px solid var(--line);
  }
  /* The active-page link is underlined inline with a 2px gold border-bottom;
     in the drawer that clashes with the row divider, so mark it with colour
     and weight instead (both already come from the inline style). */
  .site-nav > a { border-bottom-width: 1px !important; }

  /* Submenus collapse behind their own control.
     ------------------------------------------------------------------------
     These used to render expanded inline, on the reasoning that a nested
     accordion is one more thing to tap through and every page should stay one
     tap away. That held at ten services. It stopped holding at twenty-one plus
     a Technology group and fourteen focus areas: the open drawer measured
     2000px against a 633px window — 3.2 screens of scrolling, forty links —
     and one-tap access is worth nothing on links nobody scrolls far enough to
     see. Collapsed, the drawer opens at about a third of one screen.

     Only one level collapses, deliberately. Expanding Services still shows all
     twenty-one links at once, grouped under their headings. Nesting the six
     groups too would put three taps between a visitor and Facial
     Rejuvenation, which trades one kind of friction for a worse one.

     Nothing auto-expands, including the group matching the current page. The
     value here is a short, predictable drawer; opening the largest group by
     default on every service page would hand most of it straight back. */
  .nav-dd {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    width: 100%;
  }
  .nav-dd > .nav-dd-toggle { grid-column: 1; }
  .nav-dd-menu { grid-column: 1 / -1; }

  /* Both the collapsing and the button that undoes it are gated on
     .has-accordions, which site.js adds once it has wired the handler up.
     Without that class nothing here applies: the menus render open, the button
     never appears, and the drawer behaves exactly as it did before. A collapsed
     menu with no working control to open it would put twenty-one pages behind
     a dead chevron. */
  .site-nav.has-accordions .nav-dd:not(.is-expanded) > .nav-dd-menu { display: none; }

  /* Sits in the second column, beside the label rather than over it, so the
     label stays a working link to services.html. 44px wide and stretched to
     the full row height for a comfortable tap target. */
  .site-nav.has-accordions .nav-dd-expand {
    display: flex;
    grid-column: 2;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    width: 44px;
    padding: 0;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line);
    color: var(--gold-deep);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
  }
  .nav-dd-expand:focus-visible {
    outline: 2px solid var(--focus-ring, #A8842C);
    outline-offset: -2px;
  }
  .nav-dd-expand-icon {
    display: block;
    transition: transform 0.2s var(--ease-out);
  }
  .nav-dd.is-expanded .nav-dd-expand-icon { transform: rotate(180deg); }

  .nav-dd-menu {
    position: static;
    opacity: 1;
    /* `inherit`, NOT `visible`: the drawer hides itself with visibility:hidden,
       and a descendant that forces `visible` overrides that — which would leave
       these submenu links invisible but still focusable, so tabbing through a
       closed menu would strand focus on nothing. Inheriting keeps them hidden
       while the drawer is closed and shown once it opens. */
    visibility: inherit;
    transform: none;
    box-shadow: none;
    border: 0;
    background: transparent;
    min-width: 0;
    margin-top: 0;
    padding: 4px 0 10px 16px;
  }
  .nav-dd-menu::before { display: none; }
  .nav-dd-menu a { padding: 10px 12px; font-size: 11px; }
  /* Caret is decorative here — the menu is already open. */
  .nav-dd-caret { display: none; }

  /* Book button · compact so it still fits beside the logo and hamburger.
     `!important` is needed because the button carries a long inline style
     (kept because site.js keys its hover + metal-sheen effects off it). */
  .nav-book {
    padding: 10px 14px !important;
    letter-spacing: 0.06em !important;
    font-size: 11px !important;
    gap: 0 !important;
    white-space: nowrap;
  }
  /* Drops the "a" so the label reads "Book Consultation" — one text node, so
     screen readers get the shortened wording too rather than a hidden dupe. */
  .nav-book-article { display: none; }

  /* Scrim behind the open drawer. Drawn on <body> so no page needs extra
     markup; it sits under the sticky header (z-60) and over everything else,
     and a tap on it lands on <body>, which the outside-click handler closes on. */
  body.nav-open::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(var(--ink-rgb), 0.42);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
  }
}

/* ---------------------------------------------------------------------------
   Phones · step the logo down so logo + Book + hamburger stay on ONE row.
   The nav-bar padding is inline-styled, so those overrides need !important.
   --------------------------------------------------------------------------- */
@media (max-width: 640px) {
  .nav-bar { padding-left: 16px !important; padding-right: 16px !important; gap: 12px !important; }
  .nav-right { gap: 10px; }
  .nav-logo-img { height: 54px; }
  .site-nav { padding-left: 16px; padding-right: 16px; }
}

/* Common phone widths (360–430). Below ~430px the logo + full-size Book button
   + hamburger stop fitting on one line, so both step down. */
@media (max-width: 430px) {
  .nav-bar { padding-left: 14px !important; padding-right: 14px !important; gap: 10px !important; }
  .nav-logo-img { height: 48px; }
  .nav-book { padding: 9px 11px !important; font-size: 10px !important; }
  .site-nav { padding-left: 14px; padding-right: 14px; }
}

/* Very small legacy phones (≤340px, e.g. iPhone SE 1st gen). */
@media (max-width: 340px) {
  .nav-logo-img { height: 42px; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-dd-menu { transition: none; transform: none; }
  .nav-dd-caret { transition: none; }
  .site-nav { transition: none; transform: none; }
  .nav-toggle-bars span { transition: none; }
}

/* ---------------------------------------------------------------------------
   Results gallery · uniform before/after tiles + click-to-zoom lightbox.
   Used by public/results.html (generated by scripts/build-results-page.mjs).

   WHY `contain` AND NOT `cover`
   -----------------------------
   Most of these images are side-by-side before/after PAIRS. `cover` would fill
   the tile by cropping — which on a pair means showing one half and hiding the
   other, destroying the point of the image. So every tile is an identical box
   and the image is centred inside it untouched. Wide pairs letterbox against
   the dark backing; that's intentional matting, not a bug.

   3:2 matches the processed canvas exactly, so the matting is now incidental
   rather than structural. Change `aspect-ratio` below if that balance needs
   revisiting.

   Credits are deliberately NOT shown per-tile — the statement at the top of the
   page frames the whole set, and repeating a credit under all 103 is noise. The
   clinic credit moves into the zoomed view instead, so attribution survives.
   --------------------------------------------------------------------------- */
.result-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 18px;
}

.result-tile {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  font: inherit;
  color: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: var(--result-tile-ratio, 3 / 2);
  width: 100%;
  padding: 10px;
  overflow: hidden;
  cursor: zoom-in;
  /* Tiles sit on ivory now, not espresso · a white bed keeps clinical
     before/after photography honest and stops the surround tinting the skin
     tones in the image.

     3 / 2 since the library was processed onto a shared white 3:2 canvas
     (HANDOFF-results-image-processing.md). Every image now matches the tile
     exactly, so `contain` fits edge to edge and no tile carries dead matting —
     which is what the old 4/3 was working around while the sources ranged from
     1.0 to 4.07. The 7 EXO|E files still awaiting masking are the one set not
     yet on this canvas. */
  background: var(--marble);
  border: 1px solid var(--champagne);
  border-radius: var(--radius);
  transition: border-color 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
.result-tile > img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.3s var(--ease-out);
}
.result-tile:hover,
.result-tile:focus-visible {
  border-color: var(--gold-antique);
  box-shadow: var(--shadow);
}
.result-tile:hover > img { transform: scale(1.03); }

/* ---- the zoom overlay ---- */
.lightbox[hidden] { display: none; }
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: clamp(16px, 4vw, 48px);
  background: rgba(18, 15, 11, 0.94);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.lightbox-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 100%;
}
.lightbox-img {
  max-width: 100%;
  max-height: 78vh;      /* leaves room for the caption and close control */
  object-fit: contain;
  display: block;
  border: 1px solid rgba(var(--glow-rgb), 0.3);
  border-radius: var(--radius);
  background: var(--bg-band-deep);
}
.lightbox-caption {
  margin: 0;
  max-width: 46em;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--text-muted-soft);
}
.lightbox-caption b {
  display: block;
  margin-bottom: 3px;
  font-weight: 600;
  color: var(--text-band-muted);
}

/* The detail that used to be an <h3> above every group on the page. Shown
   here, on click, so the gallery stays scannable and nothing is lost. */
.lightbox-caption span { display: block; }
.lightbox-concern {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--porcelain);
  margin-bottom: 2px;
}
.lightbox-detail { color: var(--text-band-muted); }
.lightbox-tech {
  margin-top: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold-leaf);
}
.lightbox-credit { margin-top: 4px; font-size: 11px; color: #8A8278; }
.lightbox-close {
  position: absolute;
  top: clamp(12px, 2vw, 24px);
  right: clamp(12px, 2vw, 24px);
  width: 44px;           /* comfortable touch target */
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 22px;
  line-height: 1;
  color: var(--gold-leaf);
  background: transparent;
  border: 1px solid rgba(var(--glow-rgb), 0.4);
  border-radius: 50%;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.lightbox-close:hover,
.lightbox-close:focus-visible { background: rgba(var(--glow-rgb), 0.14); }

@media (prefers-reduced-motion: reduce) {
  .result-tile, .result-tile > img { transition: none; }
  .result-tile:hover > img { transform: none; }
  .lightbox { backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ===========================================================================
   NAVIGATION SIMPLIFICATION (2026) · mega menu, Concerns menu,
   Services accordions, Results filters.

   Added for the six-item header: Home · About · Services · Concerns ·
   Results · [Book a Consultation]. Everything below builds on the existing
   .nav-dd machinery above rather than replacing it, so the mobile drawer,
   hover bridge and :focus-within behaviour all keep working unchanged.
   =========================================================================== */

/* ---- Services · six-column "table of contents" menu -----------------------
   The plain .nav-dd-menu is a single stack. Services needs six labelled
   columns, so this variant swaps the flex column for a grid and widens it.
   It is anchored to the right of the toggle rather than the left so a menu
   this wide cannot run off the edge of the viewport on smaller laptops.

   The fifth column ("More care") holds Bridal and Medical & Restorative, the
   two areas that previously had no menu entry at all.

   There is no Technology column. This menu is care only: equipment is an
   "about us" claim rather than a care offering, so ICOONE and the technology
   overview live under About → Our Technology. If a column is ever added back
   here, the count and width below have to move with it.

   5*150 + 4*10 gap + 32 padding = 822px, comfortably inside a 1280px
   viewport. */
.nav-dd-menu--mega {
  display: grid;
  grid-template-columns: repeat(5, minmax(150px, 1fr));
  gap: 4px 10px;
  padding: 18px 16px 14px;
  /* An explicit width, not min-width: the menu is anchored by its right edge
     only, so without one the box resolves its left edge from the static
     position and stretches to fill the nav bar. */
  left: auto;
  right: 28px;   /* matches .nav-bar's own horizontal padding */
  width: min(822px, calc(100vw - 56px));
}

/* The Services dropdown is the one menu wider than the space to the left of
   its own toggle, so it does not hang off the toggle like the others.
   .nav-dd--mega opts out of being a containing block; the menu then anchors to
   .nav-bar and opens under the whole header, right-aligned with the nav.

   Anchored to the toggle it started at a negative x — already true at five
   columns, and 50px worse at six. The hover bridge still works because the
   toggle sits within the menu's horizontal span, so the pointer never leaves
   .nav-dd on the way down.

   Desktop only. Below 1024px the mega menu renders inline inside the drawer
   and .nav-bar must stay unpositioned, or it becomes the drawer's containing
   block instead of <header>. */
@media (min-width: 1025px) {
  .nav-bar { position: relative; }
  .nav-dd--mega { position: static; }
}

/* The footer row sits under every column rather than inside one of them, so it
   has to span the whole grid. Its own look lives in collection-links.css. */
.nav-dd-menu--mega .la-nav-footer { grid-column: 1 / -1; }

/* Column heading. Not a link · it names the area, the links beneath are the
   destinations. Matches the eyebrow treatment used across the site. */
.nav-dd-col-title {
  display: block;
  padding: 4px 14px 8px;
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold-deep);
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}

/* Each column is its own stack so rows align to the top of the grid cell. */
.nav-dd-col { display: flex; flex-direction: column; gap: 1px; }

/* Menu links wrap inside the mega menu · some collection names are long and
   nowrap (inherited from .nav-dd-menu a) would force the columns too wide. */
.nav-dd-menu--mega a { white-space: normal; letter-spacing: 0.08em; }

/* ---- Focus Areas · flat, plain-language menu ------------------------------
   Single column, sentence case rather than uppercase: these are the words a
   visitor would use about themselves, so they should read like language, not
   like labels.

   The class name stays --concerns. It is internal, nothing renders it, and
   renaming it would churn the stylesheet, the generator and every page for no
   visitor-facing gain. The menu's *label* is what changed. */
.nav-dd-menu--concerns { min-width: 260px; }
.nav-dd-menu--concerns a { text-transform: none; letter-spacing: 0.02em; font-size: 13px; }

/* A concern that honestly points at two areas of care shows both, stacked in
   the one row, rather than us picking a winner. See section 3.2 of the handoff. */
.nav-dd-split { padding: 8px 14px 10px; }
.nav-dd-split-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  color: var(--espresso);
  margin-bottom: 4px;
}
.nav-dd-split-links { display: flex; flex-direction: column; gap: 1px; padding-left: 10px; }
.nav-dd-menu--concerns .nav-dd-split-links a {
  padding: 5px 8px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

/* ---- Mobile · the mega menu has to become one column in the drawer ------- */
@media (max-width: 1024px) {
  .nav-dd-menu--mega {
    grid-template-columns: 1fr;
    min-width: 0;
    padding: 6px 4px 10px;
    right: auto;
  }
  .nav-dd-menu--concerns { min-width: 0; }
  .nav-dd-col-title { padding: 10px 12px 6px; font-size: 9px; }
  .nav-dd-menu--concerns a { font-size: 12px; }
}

/* ===========================================================================
   Services page · accordion collections
   ---------------------------------------------------------------------------
   Built on native <details>/<summary> so every collection is open-able with
   no JavaScript at all. site.js only adds the "open the one you linked to"
   behaviour on top; without it the page still works, just closed by default.
   =========================================================================== */
.svc-area { scroll-margin-top: 90px; }

/* Four categories on one page, each with a photo, an intro, up to six accordions
   and a results strip. Without a surface behind them they run together and the
   reader loses track of which category they are in — which is the same problem
   the area photos were added to solve, from the other direction.

   Alternating tints rather than a tint on all four: every boundary needs a change
   of colour to be legible, and four identical panels separated by gaps read as a
   list of cards, not as sections. Odd areas keep the page colour and even ones
   take --bg-alt, so the eye gets three clear divisions.

   The padding is on ALL FOUR, tinted or not. Only tinting the padded ones would
   shift the collections left and right down the page, which is more distracting
   than the run-together it fixes. The negative inline margin lets the tint reach
   wider than the text column so it reads as a band rather than a card. */
.svc-area {
  padding: clamp(22px, 3vw, 40px) clamp(18px, 2.4vw, 32px);
  margin-inline: calc(-1 * clamp(18px, 2.4vw, 32px));
  border-radius: var(--radius-lg);
}
.svc-area--tint { background: var(--bg-alt); }

/* The results strips go on a dark band · manufacturer before/after photography is
   clinical, and sitting it on the same ivory as the editorial imagery above made
   the two read as one set. Dark separates them and says "different kind of
   picture" without a caption having to.
   The strip's own text colours are inline and set by scripts/add-service-results.mjs
   — they were switched to on-dark tokens in the same change. Do not re-light them
   here; edit the generator and re-run.
   Bleeds to the area's padding edges so the band spans the full width of the
   section rather than floating inside it. */
[data-service-results] {
  margin-inline: calc(-1 * clamp(18px, 2.4vw, 32px));
  padding: clamp(22px, 2.6vw, 32px) clamp(18px, 2.4vw, 32px);
  background: var(--bg-band-dark);
  border-radius: var(--radius-lg);
}
/* The tiles themselves keep their own light backing — these are photographs, and
   a plum surround behind a clinical image is the point. */
[data-service-results] .result-tile { background: var(--bg-band-deep); }

/* Two columns. Six collections stacked full-width read as a long queue of
   things to get through · in two columns the same six are one glance. A card
   that opens simply makes its own grid row taller; align-items:start keeps the
   card beside it at its natural height instead of stretching to match. */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}

/* An open card takes the full width of the grid.
   ---------------------------------------------------------------------------
   Two cards share a row, and a grid row is as tall as its tallest cell. So
   opening one card stretched the row and left its neighbour as a short box
   with a hole under it — 256px of nothing beside GLP Weight Management,
   344px beside Whole-Person Wellness, which is the widest body copy on the
   page. align-items:start stops the short card being stretched; it cannot
   stop the row growing underneath it.

   Spanning the open card across both columns removes the neighbour from that
   row entirely, so there is no hole to leave. It also gives the body copy the
   full width, which suits it — .svc-body is indented 80px to clear the glyph,
   and in a half-width card that left it a narrow gutter of text. */
.svc-grid > .svc-collection[open] { grid-column: 1 / -1; }

.svc-collection {
  border: var(--hairline);
  border-radius: var(--radius);
  background: var(--marble);
  overflow: hidden;
  scroll-margin-top: 90px;
  box-shadow: var(--shadow-xs);
  transition: box-shadow var(--transition), border-color var(--transition);
}
.svc-collection[open] { box-shadow: var(--shadow-sm); border-color: var(--champagne); }

/* The clickable row · glyph, then name over tagline, then caret.
   list-style:none plus ::-webkit-details-marker kills the browser default
   triangle so we can use our own caret on the right. */
.svc-collection > summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 14px;
  align-items: start;
  padding: 16px 18px;
  cursor: pointer;
  list-style: none;
  transition: background-color 0.18s ease;
}
/* A few cards carry a name with no tagline under it (Bridal, Medical). Their
   text column is one 22px line against a 48px glyph, and `align-items:start`
   above would strand that line at the top of the mark with 26px of air below
   it. Centre the row only in that case, so the taglined cards keep the
   top-alignment they were tuned for. */
.svc-collection > summary:not(:has(.svc-tagline)) { align-items: center; }

.svc-collection > summary::-webkit-details-marker { display: none; }
.svc-collection > summary:hover { background: var(--cream); }
.svc-collection > summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* The line-drawn collection marks, used as bullets · an eye for Eye Revival,
   lips for Lips and Cheeks, lymphatic waves for Body, and so on. Each tile
   names its own file with an inline background-image; everything else is
   shared here. Artwork is 48px-native SVG, gold line on transparent, so the
   espresso ground below is the only thing setting the tile colour — change it
   here and all seventeen follow.

   A BACKGROUND, NOT AN <img>, for two reasons. It is decoration — the
   collection name sits right beside it — so it belongs in CSS rather than the
   document. And site.js's initNaturalSizeCaps pins every non-cover <img> to
   `max-height: min(100%, …)`, which flattened the earlier artwork and rendered
   every bullet as a blank tile. A background is outside that rule's reach. */
.svc-glyph {
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: var(--radius-sm);
  background-color: var(--espresso);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: center;
}

/* Name over tagline in one column, so both start at the same left edge on
   every card no matter how long the name runs. The old single baseline row
   let each tagline begin wherever its name happened to end. */
.svc-text { min-width: 0; }
.svc-name {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: 1.3;
  color: var(--espresso);
}
.svc-tagline {
  display: block;
  margin-top: 5px;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--taupe);
}
.svc-caret {
  flex: none;
  margin-top: 2px;
  font-size: 11px;
  color: var(--gold-deep);
  transition: transform 0.22s var(--ease-out);
}
.svc-collection[open] > summary .svc-caret { transform: rotate(180deg); }

/* The opened panel. Indented to the text column so the body lines up under
   the name rather than under the glyph · 18px card padding + 48px glyph +
   14px gap. Keep these three in step if any of them changes. */
.svc-body { padding: 0 18px 20px 80px; border-top: 1px solid var(--line); }
.svc-body > p { margin: 16px 0 0; font-size: var(--fs-small); line-height: 1.7; color: var(--taupe); }
.svc-actions { display: flex; flex-wrap: wrap; gap: 10px 20px; margin-top: 18px; align-items: center; }
.svc-actions a {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  text-decoration: none;
}
.svc-actions a:hover { text-decoration: underline; }

/* Highlight a collection that was linked to directly, so it is obvious which
   one the menu opened when several sit in view at once. */
.svc-collection.is-targeted { border-color: var(--gold-antique); box-shadow: var(--shadow-gold); }

@media (max-width: 760px) {
  .svc-grid { grid-template-columns: 1fr; gap: 10px; }
  /* The glyph indent costs too much of a narrow screen · drop back to the
     card's own padding once there is only one column. */
  .svc-body { padding: 0 18px 18px; }
}

/* ===========================================================================
   Services page · area jump strip
   ---------------------------------------------------------------------------
   Seventeen closed accordions in one column is a lot to land on. This strip
   sits under the intro and lets someone go straight to the area they came for
   without scrolling past the other three. The header mega menu already does
   this on desktop, but it collapses into a drawer on mobile, which is exactly
   where the scrolling problem is worst.
   =========================================================================== */
.svc-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.svc-jump a {
  display: inline-flex;
  align-items: center;
  padding: 9px 18px;
  background: var(--marble);
  border: var(--hairline);
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--espresso);
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.svc-jump a:hover,
.svc-jump a:focus-visible { border-color: var(--gold-antique); box-shadow: var(--shadow-sm); }

/* ===========================================================================
   Services page · area header band (one photo per area)
   ---------------------------------------------------------------------------
   Photo beside the area's heading and intro, then that area's collections
   listed underneath as accordions. One photo per area rather than one per
   collection is deliberate: seventeen thumbnails would turn a calm page into a
   catalogue, and the photo's job here is to break the page into four readable
   stretches, not to illustrate each treatment.
   =========================================================================== */
.svc-area-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(24px, 3.5vw, 52px);
  align-items: center;
  margin-bottom: clamp(26px, 3vw, 38px);
}

/* Every second area puts the photo on the right, so the four bands don't read
   as one left-hand column of pictures with text hanging off it. */
.svc-area-head--flip .svc-area-photo { order: 2; }

.svc-area-photo {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--champagne);
  box-shadow: var(--shadow-sm);
}
/* object-fit:cover is load-bearing, not styling. It fills and crops the box,
   and site.js deliberately exempts cover images from the never-upscale cap ·
   capping these would leave holes in the band. See initNaturalSizeCaps. */
.svc-area-photo > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.svc-area-copy > p { margin: 0; }

@media (max-width: 860px) {
  /* Photo above the words. The explicit order resets the flip on desktop, so
     an odd-numbered area doesn't end up with its picture below its heading. */
  .svc-area-head { grid-template-columns: 1fr; gap: 20px; }
  .svc-area-head--flip .svc-area-photo { order: 0; }
  .svc-area-photo { aspect-ratio: 16 / 9; }
}

/* ===========================================================================
   Results page · area filter pills
   =========================================================================== */
.rf-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  padding: 0 0 8px;
}
.rf-pill {
  appearance: none;
  border: 1px solid var(--gold-antique);
  background: transparent;
  /* Gold text on a light background is Deep, never Leaf · design-system.css. */
  color: var(--gold-deep);
  padding: 11px 22px;
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.rf-pill:hover { background: rgba(var(--glow-rgb), 0.14); }
.rf-pill:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* aria-pressed is the source of truth for "which filter is on", so the styling
   hangs off it rather than a separate class · no way for the two to disagree. */
.rf-pill[aria-pressed="true"] {
  background-image: var(--gold-metal);
  color: var(--text-on-gold);
  border-color: transparent;
}

/* Shown when a filter matches nothing (Wellness has no imagery yet). */
.rf-empty {
  margin: 40px auto 0;
  max-width: 44em;
  text-align: center;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--taupe);
}

/* ===========================================================================
   Results · the two tracks
   ---------------------------------------------------------------------------
   These answer "where did this image come from", which is an attribution
   question, so the two are kept visually distinct.

     .results--clinical  · manufacturer-supplied clinical imagery. The bulk of
                           the gallery, so it takes the light ivory treatment
                           and lets the photography carry the page.
     .results--patients  · L'Ateleir client features and testimonials. Far
                           fewer of these, so espresso is reserved for them:
                           the dark band is what makes a real client story feel
                           like the occasion it is.

   Espresso is an accent on this site, not a default. If you find yourself
   reaching for .results--patients on more than a couple of sections, the
   contrast that makes it work has already been spent.
   =========================================================================== */
/* The six existing galleries carry their own inline porcelain/cream so they
   alternate; this is the default for any new one added without that. */
.results--clinical { background: var(--porcelain); }

.results--patients {
  background: var(--bg-band-dark);
  border-top: 1px solid rgba(var(--glow-rgb), 0.28);
  border-bottom: 1px solid rgba(var(--glow-rgb), 0.28);
}

/* ---- .results--compact · vendor imagery stays subordinate ------------------
   A marker class carried over from the gallery architecture, and one that has
   to keep doing real work: manufacturer photography is smaller than
   L'Ateleir's own by design, so that when the patient gallery fills it
   out-ranks the vendor's on sight rather than on a caption.

   It is what makes the teaser tiles on a treatment page read as a sample
   rather than as the gallery itself. Do not remove it from a teaser. */
.results--compact .result-grid {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

/* ---- Results teaser · on a treatment page ---------------------------------
   Three manufacturer clinical images and a link through to the full set,
   sitting where the empty "Before & After" grid and the video placeholder used
   to be. It is the SAME component as the clinical track on results.html —
   .results--clinical, .result-grid, .result-tile — not a parallel one. So the
   images are framed and lit identically wherever a visitor meets them, and the
   lightbox in site.js picks the tiles up with no extra wiring.

   Two things differ from the gallery, both deliberate:
     · the grid is capped so a teaser cannot reflow to five across on a wide
       screen and stop reading as a sample;
     · every tile carries a visible credit. On results.html the credit lives in
       the zoomed view, because repeating it under a hundred tiles is noise.
       Under three it is the disclosure doing its job. */
/* The column count is declared, not inferred.
   ---------------------------------------------------------------------------
   auto-fit was wrong at both ends. With three tiles it left a collapsed fourth
   track and a stray gap on the right; with four it fitted only three and
   dropped the last onto a row of its own. A teaser always knows how many tiles
   it holds, so it says so: the generators write --teaser-cols on the grid and
   the width follows from it.

   Two class names in the selector so it outranks .results--compact
   .result-grid above, which the teaser also carries. */
.results--compact .la-teaser-grid {
  grid-template-columns: repeat(var(--teaser-cols, 3), minmax(0, 1fr));
  max-width: calc(var(--teaser-cols, 3) * 220px + (var(--teaser-cols, 3) - 1) * 16px);
}

/* Below the width where the declared count still fits, fall back by hand. Two
   across, then one — four columns squeezed onto a phone is four thumbnails
   nobody can read. */
@media (max-width: 900px) {
  .results--compact .la-teaser-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: none;
  }
}
@media (max-width: 560px) {
  .results--compact .la-teaser-grid { grid-template-columns: minmax(0, 1fr); }
}

.la-teaser-item { margin: 0; }

.la-teaser-caption {
  margin: 9px 2px 0;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.04em;
  line-height: 1.5;
  color: var(--taupe);
}

/* Same caption, but on a dark strip. --taupe is a body-secondary colour tuned
   for cream; on a dark band it drops to about 2.7:1 and stops being readable.
   Scoped to the .results--onDark modifier rather than to .results--compact,
   because most compact strips are on cream and must keep --taupe. */
.results--onDark .la-teaser-caption { color: var(--text-band-faint); }

/* One relevant result: the image sits beside the copy rather than in a grid.
   A three-up grid holding a single item reads as broken, not as restraint. */
.la-teaser-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
}
.la-teaser-split .la-teaser-item { max-width: 460px; }

@media (max-width: 780px) {
  .la-teaser-split { grid-template-columns: 1fr; }
}
.results--patients h2,
.results--patients h3 { color: var(--porcelain); }
.results--patients p { color: var(--text-band-muted); }
/* On espresso, gold goes back to Leaf · Deep is unreadable there. */
.results--patients .eyebrow,
.results--patients a { color: var(--gold-leaf); }
.results--patients .result-tile {
  background: var(--bg-band-deep);
  border-color: rgba(var(--glow-rgb), 0.28);
}

@media (prefers-reduced-motion: reduce) {
  .svc-caret, .svc-collection, .rf-pill { transition: none; }
}

/* Concern sub-group inside a results category. The heading lives inside the
   group so a filter that hides the group can never leave a label stranded
   over empty space. */
.rf-group { scroll-margin-top: 78px; }

/* ---------------------------------------------------------------------------
   Services page · a strip of recent results inside a collection.
   Injected by scripts/inject-service-results.py from the results manifest, so
   it stays in step with what the gallery actually holds.

   Proof belongs here, next to the description, rather than in the Concerns
   menu where it was making people choose a door before they had arrived.
   --------------------------------------------------------------------------- */
.svc-results { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); }

.svc-results-label {
  display: block;
  margin-bottom: 12px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.svc-results-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; }

/* A white bed, same as the gallery tiles, so a small source sits calmly in the
   middle instead of being stretched. The global no-upscale cap in site.js
   applies here too. */
.svc-result {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 168px;
  height: 112px;
  padding: 6px;
  background: var(--marble);
  border: 1px solid var(--champagne);
  border-radius: var(--radius-sm);
  overflow: hidden;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.svc-result:hover,
.svc-result:focus-visible { border-color: var(--gold-antique); box-shadow: var(--shadow-sm); }
.svc-result > img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }

.svc-results-all {
  display: inline-block;
  margin-top: 14px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  text-decoration: none;
}
.svc-results-all:hover { text-decoration: underline; }

@media (max-width: 640px) {
  .svc-result { width: calc(50% - 5px); height: 96px; }
}

/* ---------------------------------------------------------------------------
   Lightbox · a fixed frame with the image centred inside.

   Once images stopped being upscaled, each one opened at a different size and
   the zoom read as broken. The frame is now constant; the photo sits in the
   middle of it at whatever size its own pixels honestly allow. Small and
   sharp, in a consistent window.
   --------------------------------------------------------------------------- */
.lightbox-frame {
  width: min(92vw, 1100px);
  height: min(70vh, 720px);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--marble);
  border: 1px solid rgba(var(--glow-rgb), 0.35);
  border-radius: var(--radius);
  overflow: hidden;
}
/* The frame now owns the surface and the border, so the image drops both. */
.lightbox-img {
  border: 0;
  border-radius: 0;
  background: none;
  max-width: 100%;
  max-height: 100%;
}

/* ---------------------------------------------------------------------------
   Consultation layout · a tall embedded form beside a short info card
   Used by index.html (#booking) and consultation.html (#request). Lives here
   rather than in home-additions.css because consultation.html does not load
   that file.

   WHY THE CARD IS STICKY, and why this is not decoration:
   the Vagaro form iframe is ~1580px tall and the info card is roughly 450-600px.
   Side by side in a plain grid, the browser stretches both columns to the taller
   one, leaving ~1100px of empty panel next to the form. That exact problem (it
   measured 1249px) is what forced the form out of the grid at v0.32.1.

   Sticky solves it properly: the card pins below the nav and travels with the
   reader, so the space is never empty AND the address, hours and phone stay on
   screen for the whole time someone is filling the form in.

   Do not "simplify" this back to a plain two-column grid. The void comes back.
   --------------------------------------------------------------------------- */

.booking-cols {
  display: grid;
  gap: clamp(24px, 3vw, 44px);
  align-items: start;          /* load-bearing: without it the card stretches
                                  to the form's height and the sticky does
                                  nothing, because there is nothing to slide
                                  against. */
}

/* Also load-bearing. Grid items default to `min-width: auto`, meaning a track
   refuses to shrink below its content's intrinsic width. The wrapper Vagaro
   ships is `width: 700px`, so without this the column is pinned at 700px + its
   own padding and pushes the whole PAGE into a horizontal scroll on tablets —
   the wrapper's own `max-width: 100%` cannot save it, because the track it is
   measuring against grew to fit. Caught at 768px. */
.booking-cols > * { min-width: 0; }

@media (min-width: 960px) {
  .booking-cols {
    /* The form wrapper is 700px from 700px up, so the first column wants to be
       at least that before the form starts shrinking. */
    grid-template-columns: minmax(0, 1.6fr) minmax(280px, 1fr);
  }

  .booking-aside {
    position: sticky;
    top: 110px;                /* clears the sticky nav, which measures 97px at
                                  desktop widths. */

    /* Safety valve, and load-bearing on short screens. A sticky element taller
       than the space below `top` gets pinned with its bottom off-screen and
       STAYS there — you cannot scroll to reach it, because the page scrolls
       underneath it. The card measures ~700px and an 800px-tall window leaves
       690px, so it is already marginally over on a laptop.

       These two lines cap it to the visible area and let it scroll internally,
       but only when it genuinely does not fit. Do not remove them to "clean
       up"; the map silently becomes unreachable. */
    max-height: calc(100vh - 130px);
    overflow-y: auto;
  }
}

/* Below the breakpoint the columns stack, form first. Sticky is pointless in a
   single column and can strand the card mid-scroll, so switch it off. */
@media (max-width: 959px) {
  .booking-aside { position: static; }
}

/* ---------------------------------------------------------------------------
   COLOUR SCHEME. This site is light-only by design; saying so explicitly stops
   iOS applying its own dark treatment to form controls, which would re-colour
   inputs and labels on a form never designed for it.
   --------------------------------------------------------------------------- */
:root { color-scheme: light; }

/* ---------------------------------------------------------------------------
   .cform — the consultation request form.

   REPLACED VAGARO'S FORMS EMBED, and it is worth knowing why so nobody puts an
   iframe back. Vagaro's form does not render on mobile: the shell loads, draws
   skeleton placeholders, and content never populates. Confirmed at the raw form
   URLs in mobile Safari with no iframe involved — their defect, not ours. Along
   the way these were each investigated and cleared: fixed pixel dimensions,
   clipping ancestors, X-Frame-Options / frame-ancestors, and colour scheme.

   Everything here is CSS and native HTML. No JavaScript validates, sizes,
   shows or hides this form — it is the only conversion path on the site, and a
   script that fails takes it down silently.
   --------------------------------------------------------------------------- */

.cform__title {
  margin: 0 0 28px;
  padding-bottom: 18px;
  border-bottom: var(--hairline);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.14;
  color: var(--espresso);
}

/* Two-up on anything with room, stacked on a phone. minmax(0,1fr) rather than
   1fr: without the 0 floor a long autofilled value can force the column wider
   than its track and push the page into a horizontal scroll. */
.cform__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}

@media (min-width: 560px) {
  .cform__row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.cform__field { margin-bottom: 22px; }
.cform__row .cform__field { margin-bottom: 0; }
.cform__row { margin-bottom: 22px; }

.cform__label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--espresso);
}

.cform__req { color: var(--gold-deep); }

.cform__input {
  width: 100%;
  padding: 14px 16px;
  background: var(--marble);
  border: 1px solid var(--champagne);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 16px;          /* 16px minimum, deliberately: iOS zooms the whole
                               page when a focused input is smaller, and the
                               layout never zooms back out. */
  line-height: 1.5;
  color: var(--text-primary);
}

.cform__input:focus-visible {
  outline: 2px solid var(--gold-antique);
  outline-offset: 2px;
  border-color: var(--gold-antique);
}

.cform__textarea { resize: vertical; min-height: 130px; }

.cform__group {
  margin: 0 0 22px;
  padding: 0;
  border: 0;                /* fieldset for semantics, not for its default box */
}

.cform__legend {
  padding: 0;
  margin-bottom: 12px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--espresso);
}

/* Whole row is the label, so the tap target is the text as well as the box. */
.cform__check {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--taupe);
  cursor: pointer;
}

.cform__check input {
  flex: none;
  width: 20px;
  height: 20px;
  accent-color: var(--espresso);
}

.cform__gotcha {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.cform__submit {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 18px 32px;
  background: var(--espresso);
  color: var(--gold-leaf);
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}

.cform__submit:focus-visible { outline: 2px solid var(--gold-antique); outline-offset: 3px; }

.cform__note {
  margin: 18px 0 0;
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.04em;
  line-height: 1.7;
  color: var(--taupe);
  text-align: center;
}

.cform__note a { color: var(--gold-deep); text-decoration: underline; }
