/* ==========================================================================
   Tropical Dental — global stylesheet
   Design tokens sourced from Figma (node 6170:200, "Foundations" page)
   unless noted otherwise. See comments for anything that's a proposed
   default rather than a pulled-from-Figma value.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Fonts (self-hosted)
   -------------------------------------------------------------------------- */

/* Hanken Grotesk ships as a variable font — Google serves the same woff2
   for every static weight (300/400/600/700/800) we request, so one file
   backs all five @font-face rules below; the browser reads the descriptor's
   font-weight and instances the variable font's wght axis accordingly. Only
   the "latin" unicode-range subset is kept (site copy is English-only) —
   dropped Google's cyrillic/greek/vietnamese subsets these five weights +
   the italic + JetBrains Mono bold would otherwise each pull in separately. */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/hanken-grotesk-italic-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-bold-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */

:root {
  /* --- Color: raw palette (from Figma variables) --- */
  --palette-primary-highlight: #D1C176;
  --palette-primary-accent: #D6B100;
  --palette-neutral-white: #FFFFFF;
  --palette-neutral-light: #C1C1C1;
  --palette-neutral-medium-light: #9A9A9A;
  --palette-neutral-medium: #585858;
  --palette-neutral-dark: #1B1B1B;
  --palette-neutral-black: #000000;
  /* Backing color under a stone-texture image overlay — the texture
     asset itself still needs to be sourced/exported from Figma. */
  --palette-background-stone: #000000;
  /* Footer copyright/legal text (node 14154:3678) — a raw hex value in
     Figma, not bound to any named variable there either (confirmed via
     get_variable_defs), so this is this project's own name for it. */
  --palette-neutral-faint: #7B7772;

  /* --- Color: semantic tokens (what components should actually use) ---
     Dark theme by default — every section in the Figma file (hero,
     services, practice, technology, gallery, contact, footer) sits on
     the same #1B1B1B (neutral-dark) fill with light/white text and gold
     accents; there's no light/white-background section anywhere in the
     file to justify a light default. --color-border flips from the
     original light-on-white value (neutral-light, #C1C1C1 — too bright/
     high-contrast as a hairline on a dark surface) to neutral-medium
     (#585858), a subtler division on dark backgrounds. */
  --color-text: var(--palette-neutral-light);
  --color-text-muted: var(--palette-neutral-medium-light);
  --color-heading: var(--palette-neutral-white);
  --color-background: var(--palette-neutral-dark);
  --color-border: var(--palette-neutral-medium);
  --color-accent: var(--palette-primary-accent);
  --color-highlight: var(--palette-primary-highlight);
  --color-link: var(--color-text);
  --color-link-hover: var(--color-accent);

  /* Secondary-button backdrop, from Figma's hero CTA ("Btn Tertiary",
     node 14123:116) — a translucent dark overlay designed to sit on top
     of a photo/dark background, not a standalone flat color. Figma has
     no hover spec for this (static export, no interaction states) — the
     hover variant just deepens the same overlay rather than replacing
     it with a flat opaque color, so it keeps reading as "sitting on a
     photo" instead of turning into a plain box. */
  --color-btn-secondary-bg: rgba(27, 27, 27, 0.2);
  --color-btn-secondary-bg-hover: rgba(27, 27, 27, 0.4);

  /* --- Typography: font families ---
     Self-hosted (see "0. Fonts" above) — woff2 files in assets/fonts,
     downloaded once from Google Fonts rather than loaded live from
     fonts.googleapis.com/fonts.gstatic.com. */
  --font-heading: "Hanken Grotesk", system-ui, sans-serif;
  --font-body: "Hanken Grotesk", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;

  --font-weight-light: 300;
  --font-weight-regular: 400;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;

  /* --- Typography: fluid scale ---
     Each heading is clamped between a mobile minimum and its Figma
     desktop value, interpolated across a 375px (mobile) to 1920px
     (Figma's design frame width) viewport range. The mobile-minimum
     sizes are proposed ratios, NOT pulled from Figma — there's no
     mobile type frame in the file yet, so revisit these once one exists.
     Letter-spacing values from Figma are treated as percent-of-font-size
     (e.g. 10 = 10%) and converted to em so they scale with the type. */
  --text-h1: clamp(2.5rem, 1.7263rem + 3.301vw, 5.6875rem);
  --tracking-h1: 0em;

  --text-h2: clamp(2rem, 1.5601rem + 1.877vw, 3.8125rem);
  --tracking-h2: -0.04em;

  --text-h3: clamp(1.75rem, 1.4618rem + 1.2298vw, 2.9375rem);
  --tracking-h3: -0.02em;

  --text-h4: clamp(1.5rem, 1.318rem + 0.7767vw, 2.25rem);
  --tracking-h4: 0em;

  --text-h5: clamp(1.25rem, 1.1438rem + 0.4531vw, 1.6875rem);
  --tracking-h5: 0em;

  --text-h6: clamp(1.125rem, 1.0795rem + 0.1942vw, 1.3125rem);
  --tracking-h6: 0em;

  /* Body text is comfortable at a fixed size across mobile-desktop;
     it still grows gently on ultra-wide screens via the fluid root
     font-size below (both are in rem). */
  --text-body: 1rem;
  --text-body-sm: 0.8125rem;
  --tracking-body-sm: 0.1em;

  /* Button / link-trail label style — from Figma's hero CTA group
     (node 14115:748) and "Link Trail" component (node 14131:234).
     Distinct tracking from --tracking-body-sm above (12.5% vs 10%). */
  --text-btn: 1rem;
  --tracking-btn: 0.125em;

  /* Nav link label style — from Figma's global "Navigation" component
     (node 14115:792, e.g. node 14115:806 "HOME"). Hanken Grotesk, not
     JetBrains Mono, and a different size/tracking ratio than the two
     scales above, so it gets its own tokens rather than reusing one. */
  --text-nav-link: 0.875rem; /* 14px */
  --tracking-nav-link: 0.1em; /* 1.4px / 14px */

  /* --- Spacing scale (from Figma variables) --- */
  --space-1: 0.25rem;  /* 4px */
  --space-2: 0.5rem;   /* 8px */
  --space-3: 1rem;     /* 16px */
  --space-4: 1.5rem;   /* 24px */
  --space-5: 2.5rem;   /* 40px */
  --space-6: 5rem;     /* 80px */
  --space-7: 10rem;    /* 160px */

  /* --- Layout ---
     Both max-widths are in rem, not px, so they inherit the same fluid
     root-font-size growth as typography — beyond 1920px viewport width,
     root font-size grows 16px→20px (see html{} below), so these two
     values grow proportionally along with it, not just typography. */
  --container-max-width: 88.5rem; /* 1416px baseline — explicit override, not re-verified against Figma (was 1200px, sourced from Figma at the time) */
  --container-padding-inline: var(--space-3);
  /* In-between "popout" breakout width — wider than .container but not
     full viewport (proposed default — not in Figma). */
  --breakout-popout: 106.5rem; /* 1704px baseline */

  /* --- Border radius (proposed default — not in Figma yet) --- */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;
  --radius-full: 9999px;

  /* --- Shadow / elevation (proposed default — not in Figma yet) --- */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);

  /* --- Motion (proposed default — not in Figma yet) --- */
  --transition-fast: 150ms ease;
  --transition-base: 250ms ease;
  --transition-slow: 400ms ease;

  /* --- Z-index scale (proposed default — not in Figma yet) --- */
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-header: 300;
  --z-overlay: 400;
  --z-modal: 500;
  --z-toast: 600;

  /* --- Header (from Figma's "Navigation" component, node 14115:792) ---
     A fixed, translucent dark bar with a blurred backdrop, meant to
     overlay the hero image beneath it. */
  --header-height: 6rem; /* 96px */
  /* Fluid left inset — 108px is Figma's exact value at its 1920px design
     width, but that's excessive on narrow screens, so it scales down to
     --space-4 (24px, the same floor the old ≤1024px breakpoint used),
     reaching that floor at the 768px tablet breakpoint rather than all
     the way down at 375px — stays flat at 24px through the whole mobile
     range below that. Same clamp()-interpolation method as the fluid
     type scale above: value = intercept + slope*vw, solved so it lands
     on 1.5rem at 768px and 6.75rem at 1920px exactly. */
  --header-pl: clamp(1.5rem, -2rem + 7.2917vw, 6.75rem);
  --header-bg: rgba(27, 27, 27, 0.8);
  --header-blur: 8px;
  /* Elevation for the always-fixed, floating header — needs to read as
     "above" whatever scrolls under it (a dark page, or eventually a busy
     hero photo). A light-theme-style black drop shadow (this project's
     generic --shadow-sm/md/lg tokens) barely registers against the dark
     page background now behind it — the standard dark-theme substitute
     (see Material Design's dark-theme elevation guidance) is a subtle
     light hairline border, paired here with a deeper/more opaque shadow
     than those generic tokens so it still shows up against a solid dark
     page, not just a lighter photo. */
  --header-border-bottom: rgba(255, 255, 255, 0.08);
  --header-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);

  /* Dev-only banner (see "8. Dev tools" below) stacks above the fixed
     header, on top of it, when present — this is its own fixed height so
     both bars have a deterministic size to lay out against. */
  --dev-banner-height: 2rem; /* 32px */
}

/* Breakpoints (documented, not custom properties — CSS can't reference
   custom properties inside @media conditions):
   mobile:  up to 767px
   tablet:  768px – 1024px
   laptop:  1025px – 1400px
   large:   1400px and up */

/* Root font-size stays fixed at 16px through the whole fluid-heading
   range above (0–1920px), then grows gently from 16px to 20px between
   1920px and 2560px — every rem-based value (headings, spacing, etc.)
   inherits this without needing its own ultra-wide breakpoint. */
html {
  font-size: clamp(16px, 4px + 0.625vw, 20px);
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  color-scheme: dark;
  scroll-behavior: smooth;
}

body {
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: none;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

table {
  border-collapse: collapse;
}

/* Respects the OS-level "reduce motion" setting — near-instant instead of
   animated for anyone who's opted out of motion, including our own
   --transition-* tokens and the scroll-behavior: smooth above. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   3. Accessibility
   -------------------------------------------------------------------------- */

/* Consistent, on-brand focus ring everywhere (links, buttons, nav, future
   form controls) instead of leaving it to each browser's own default. */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Visually hidden until focused — lets keyboard users jump straight past
   the nav to #main. Markup: the very first element inside <body>,
   see views/partials/header.php. translateY (not `top: -100%`) because a
   percentage `top` on an absolutely-positioned element resolves against
   its containing block's height — with no positioned ancestor here,
   that's the whole document, not the link itself, so `-100%` could push
   it an arbitrary, page-length-dependent distance off-screen. transform
   percentages resolve against the element's OWN size instead. */
.skip-link {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: var(--z-toast);
  padding: var(--space-2) var(--space-3);
  background-color: var(--palette-neutral-black);
  color: var(--palette-neutral-white);
  transform: translateY(-150%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Standard visually-hidden-but-announced utility — for labels that only
   assistive tech needs (e.g. the hamburger button's "Toggle navigation"). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   4. Base element styles
   -------------------------------------------------------------------------- */

body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--font-weight-regular);
  color: var(--color-text);
  background-color: var(--color-background);
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  color: var(--color-heading);
  line-height: 1.2;
  text-wrap: balance;
}

h1 {
  font-size: var(--text-h1);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: var(--tracking-h1);
  line-height: 1;
}

h2 {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: var(--tracking-h2);
  line-height: 1;
}

h3 {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: var(--tracking-h3);
}

h4 {
  font-size: var(--text-h4);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: var(--tracking-h4);
}

h5 {
  font-size: var(--text-h5);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-h5);
}

h6 {
  font-size: var(--text-h6);
  font-weight: var(--font-weight-light);
  letter-spacing: var(--tracking-h6);
}

p {
  font-size: var(--text-body);
}

/* Desktop/P - body - Strong and Desktop/P - body - Italic in Figma */
strong, b {
  font-weight: var(--font-weight-bold);
}

em, i {
  font-style: italic;
}

a {
  color: var(--color-link);
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-link-hover);
}

/* Small uppercase label/tag style (Desktop/body-sm in Figma) */
.label {
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   5. Buttons
   From Figma's hero CTA group (node 14115:748) — "Btn Primary" and
   "Btn Tertiary" (the latter functions as the page's secondary action;
   that's Figma's actual component name, kept here so the two stay easy
   to trace back to the source) — plus the "Link Trail" text-link
   component (node 14131:234, e.g. "Meet Dr. Lalla", "All Services").
   All three share the same label typography; only background/color and
   presence of an icon differ.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-btn);
  font-weight: var(--font-weight-bold);
  line-height: 1;
  letter-spacing: var(--tracking-btn);
  text-transform: uppercase;
  border: none;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.btn-primary {
  background-color: var(--color-accent);
  color: var(--palette-neutral-dark);
}

.btn-primary:hover {
  background-color: var(--color-highlight);
  color: var(--palette-neutral-dark);
}

/* Translucent dark overlay, from Figma's hero CTA — reads correctly
   anywhere now that the whole site shares that same dark background
   (see the color tokens above), not just over the hero photo. */
.btn-secondary {
  background-color: var(--color-btn-secondary-bg);
  color: var(--palette-neutral-white);
}

.btn-secondary:hover {
  background-color: var(--color-btn-secondary-bg-hover);
  color: var(--palette-neutral-white);
}

/* Text-only link with a trailing arrow — no background/padding. */
.btn-text {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-btn);
  font-weight: var(--font-weight-bold);
  line-height: 1;
  letter-spacing: var(--tracking-btn);
  text-transform: uppercase;
  color: var(--color-accent);
  transition: color var(--transition-fast);
}

.btn-text:hover {
  color: var(--color-highlight);
}

.btn-text svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   6. Layout primitives
   -------------------------------------------------------------------------- */

/* Edge-padding is folded into the width calc (not a separate `padding`
   property) so --container-max-width is a TRUE content max — at exactly
   max-width, .container has zero padding of its own; the whitespace on
   either side is just the centered box not touching the viewport edge.
   Below max-width, width shrinks to "100% minus 2x padding", which keeps
   content off the viewport edge on mobile without ever overflowing (the
   classic risk with a real `padding` property + box-sizing games). */
.container {
  width: min(var(--container-max-width), 100% - 2 * var(--container-padding-inline));
  margin-inline: auto;
}

/* Full-bleed section (proposed default — not in Figma).
   Outer full-width wrapper that carries a section background (color or
   image) while an inner .container keeps content at --container-max-width.
   Markup: <section class="full-bleed"><div class="container">...</div></section> */
.full-bleed {
  width: 100%;
}

/* Breakout (proposed default — not in Figma).
   Opt-in child of a .container that extends wider than the content column,
   via the standard negative-margin technique. .breakout goes full viewport
   width; .breakout--popout stops at --breakout-popout (1400px). Both stay
   centered on the viewport (the math works out from .container's own
   centering). Requires the overflow-x: clip guard on #main below to
   absorb the vertical-scrollbar gutter that 100vw doesn't account for. */
.breakout {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

.breakout--popout {
  width: min(var(--breakout-popout), 100vw);
  margin-inline: calc(50% - min(var(--breakout-popout), 100vw) / 2);
}

/* clip (not hidden) doesn't create a scroll container, so it doesn't
   break position: sticky inside #main — relevant since a future
   "content + sticky sidebar" page pattern is already anticipated for
   this project. Modern-browsers-only (Safari 16+, Firefox 113+); a rare
   ~15px horizontal scroll could reappear on older browsers. Accepted
   tradeoff, matches this project already choosing modern CSS (native
   Grid) elsewhere over older/more-compatible techniques. */
#main {
  overflow-x: clip;
}

/* 12-column grid (proposed default — not in Figma; the file's Foundations
   page defines no named layout-grid token, so this is a best-practice
   addition, consistent with radius/shadow/motion above).
   Meant to be used INSIDE .container. Gutter reuses --space-4 (24px) —
   the conventional gutter width and the more commonly-used step on the
   spacing scale. Went 24px → 48px (one-off) → 40px (--space-5) → back
   to 24px (--space-4) on review. */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-4);
}

/* Mobile-first: every child stacks full-width below the tablet breakpoint;
   column spans take effect from 768px up (matches the file's documented
   breakpoints). Note: `.grid > *` and `.col-span-N` are equal specificity
   (0,1,0) — the span rules win purely on source order (media query comes
   later), which is fine as long as this section isn't reordered later. */
.grid > * {
  grid-column: 1 / -1;
}

@media (min-width: 768px) {
  .col-span-1  { grid-column: span 1; }
  .col-span-2  { grid-column: span 2; }
  .col-span-3  { grid-column: span 3; }
  .col-span-4  { grid-column: span 4; }
  .col-span-5  { grid-column: span 5; }
  .col-span-6  { grid-column: span 6; }
  .col-span-7  { grid-column: span 7; }
  .col-span-8  { grid-column: span 8; }
  .col-span-9  { grid-column: span 9; }
  .col-span-10 { grid-column: span 10; }
  .col-span-11 { grid-column: span 11; }
  .col-span-12 { grid-column: span 12; }
}

/* --------------------------------------------------------------------------
   7. Navigation
   Header chrome (fixed bar, blur, colors, type, spacing) is from Figma's
   global "Navigation" component (node 14115:792). Nav item labels/dropdown
   structure are this project's own (see data/site.php) since the sitemap
   has since diverged from Figma's original nav items (General/Cosmetic/
   Dental Implant/Orthodontics) — only the visual treatment is Figma-sourced.
   The Figma file has no mobile frame at all (Desktop canvas only), so the
   hamburger/off-canvas mobile behavior below is a proposed default, not
   pulled from a mockup.
   -------------------------------------------------------------------------- */

/* align-items: stretch (not center) so children fill the full header
   height by default, matching Figma's Navigation frame — the logo/nav
   list opt back into their own natural, content-sized height via
   align-items: center on their own wrappers below, while the phone/CTA
   group (.site-header__actions) is the one piece Figma explicitly marks
   "self-stretch"/"h-full" and is meant to reach the header's full height. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-header);
  display: flex;
  align-items: stretch;
  width: 100%;
  min-height: var(--header-height);
  padding-left: var(--header-pl);
  padding-right: var(--space-4);
  background-color: var(--header-bg);
  backdrop-filter: blur(var(--header-blur));
  -webkit-backdrop-filter: blur(var(--header-blur));
  border-bottom: 1px solid var(--header-border-bottom);
  box-shadow: var(--header-shadow);
}

/* Book Now already reaches the true right edge once expanded (see
   .site-header__cta below), so the fallback right padding — there for
   the compact/hamburger state's visual balance — drops out. */
.site-header.is-expanded {
  padding-right: 0;
}

/* The fixed header overlays page content, so every page needs the same
   breathing room pushed under it. A hero section designed to bleed under
   the translucent bar (matching Figma) can opt out with a negative
   top margin equal to --header-height once that section is built. */
#main {
  padding-top: var(--header-height);
}

/* Dev mode stacks its banner above the header (see "8. Dev tools" below) —
   push both the header and the page content down to make room, instead
   of letting the fixed header cover the banner. */
body.has-dev-banner .site-header {
  top: var(--dev-banner-height);
}

body.has-dev-banner #main {
  padding-top: calc(var(--header-height) + var(--dev-banner-height));
}

.site-header__brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.site-header__logo {
  display: block;
  height: 5rem; /* 80px, node 14123:96 */
  width: auto;
}

/* No fixed viewport breakpoint can reliably predict when the desktop row
   (logo + 5 nav items + phone + wide CTA) actually fits — it depends on
   real content width (font metrics, item count, translations), not just
   window width. So "compact" (hamburger/off-canvas) is the unconditional
   DEFAULT below, and .is-expanded is an opt-in that assets/js/app.js adds
   only after actually measuring that the full row fits in one line
   (ResizeObserver + a scrollWidth/clientWidth check) — re-checked on
   every resize, so it flips at whatever width truly stops overflowing,
   not a guessed number. This also means the header degrades safely with
   JS disabled: you always get a working hamburger nav, never a row that
   silently overflows off-screen. */
/* Bordered 3-bar hamburger, styling approach borrowed from
   roytberg-infosite's .nav-toggle (not a Figma component — this project's
   header has no mobile mockup at all). Bars are real elements (not an
   SVG icon) so each one can be independently transformed into an X on
   open, same technique as that reference project. */
.site-header__toggle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3125rem; /* 5px */
  align-self: center;
  /* Pushes the toggle to the far right of the header — it's the last
     visible flex item in compact mode (.site-header__nav sits after it
     in the markup but takes no space while display: none). */
  margin-left: auto;
  width: 2.75rem; /* 44px */
  height: 2.75rem;
  padding: 0.625rem;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-md);
  color: var(--palette-neutral-white);
  cursor: pointer;
}

.site-header.is-expanded .site-header__toggle {
  display: none;
}

.site-header__toggle-bar {
  display: block;
  width: 100%;
  height: 2px;
  background-color: currentColor;
  transition: transform var(--transition-base), opacity var(--transition-fast);
}

/* Bars collapse into an X — same nth-child approach as roytberg-infosite. */
.site-header.is-open .site-header__toggle-bar:nth-child(2) {
  transform: translateY(0.4375rem) rotate(45deg);
}

.site-header.is-open .site-header__toggle-bar:nth-child(3) {
  opacity: 0;
}

.site-header.is-open .site-header__toggle-bar:nth-child(4) {
  transform: translateY(-0.4375rem) rotate(-45deg);
}

/* Compact default: an off-canvas panel that slides/fades in on open,
   rather than an instant display swap — matches roytberg-infosite's
   .nav-menu transition exactly: translateY(-110%) slides the whole panel
   in from fully off-screen above the header (not just a subtle nudge),
   at that reference's own timing (.3s transform/visibility, .25s
   opacity), kept as literal values here rather than this project's
   --transition-base/--transition-fast tokens specifically to match it
   precisely. visibility (not display) gates interactivity/focusability
   while closed, so the transition can animate transform/opacity — and
   position: absolute (vs. that reference's fixed) is a deliberate
   difference: it positions relative to .site-header itself, which
   already shifts down for the dev-mode banner (see body.has-dev-banner
   above), so this panel tracks that automatically instead of needing
   its own dev-banner-aware offset. */
.site-header__nav {
  display: flex;
  position: absolute;
  top: 100%;
  left: 0;
  flex-direction: column;
  width: 100%;
  max-height: calc(100vh - var(--header-height));
  overflow-y: auto;
  background-color: var(--palette-neutral-dark);
  transform: translateY(-110%);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.3s ease, opacity 0.25s ease, visibility 0.3s;
}

.site-header.is-open .site-header__nav {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

/* Expanded: back to Figma's normal-flow row, always visible — resets the
   compact panel's slide/fade-in properties, which don't apply here. */
.site-header.is-expanded .site-header__nav {
  display: flex;
  position: static;
  flex-direction: row;
  align-items: center;
  flex: 1;
  width: auto;
  max-height: none;
  overflow: visible;
  background-color: transparent;
  transform: none;
  opacity: 1;
  visibility: visible;
}

/* Direct-child combinator ("> ul", not "ul") so this only targets the
   top-level nav list, not the nested <ul class="dropdown"> inside each
   <li> — both are real <ul> elements, and a plain descendant selector
   here previously also matched the dropdown, silently forcing it into a
   row layout once the expanded-only override below kicked in. */
.site-header__nav > ul {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* flex: 1 + justify-content: center centers the links within the space
   the ul itself claims — which is everything left over after the fixed-
   width .site-header__actions takes its share of .site-header__nav, so
   this reads as "centered between the logo and the phone/CTA group"
   without needing to know either one's width. A small explicit gap
   supplements each link's own padding now that there are only 5 items
   (Figma's original mockup had 8, dense enough that padding alone read
   fine) rather than leaving them touching. */
.site-header.is-expanded .site-header__nav > ul {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  flex: 1;
  gap: var(--space-2); /* 8px */
}

.site-header__nav li {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.site-header.is-expanded .site-header__nav li {
  flex-direction: row;
  align-items: stretch;
}

/* Wraps the label (link or toggle-button) for a single nav item — always
   a row, even in the compact/stacked panel, so the label and its
   disclosure arrow (when present) sit side by side while the dropdown
   itself still stacks below via the <li>'s own column direction. */
.site-header__nav-row {
  display: flex;
  align-items: stretch;
}

/* Scoped to "ul a"/"ul .site-header__nav-toggle-label" (not a bare
   ".site-header__nav a") so this doesn't also match the phone link /
   Book Now button, which live in the sibling .site-header__actions and
   have their own Figma colors — an earlier, unscoped version of this
   rule had higher specificity than .btn-primary/.site-header__phone and
   was silently overriding both to neutral-light. */
.site-header__nav ul a,
.site-header__nav ul .site-header__nav-toggle-label {
  display: flex;
  align-items: center;
  flex: 1;
  padding: var(--space-3); /* 16px */
  font-family: var(--font-heading);
  font-weight: var(--font-weight-bold);
  font-size: var(--text-nav-link);
  letter-spacing: var(--tracking-nav-link);
  text-transform: uppercase;
  color: var(--palette-neutral-light);
  white-space: nowrap;
  text-align: left;
  background: none;
  border: none;
  transition: color var(--transition-fast);
}

/* "About"-style items (a real link + a separate arrow button) need two
   corrections the single-button "Services" case doesn't:
   1. The link must NOT grow to fill the row — flex: 1 here would push
      the separate arrow button away to the far edge instead of letting
      it sit right after the text (see the arrow button's own flex: 1
      below, which takes over filling the row instead).
   2. Its own right padding has to drop to 0 — otherwise it stacks with
      the arrow button's left padding into a ~32px gap between the text
      and the arrow, vs. Services' single 8px margin (one element, one
      padding). */
.site-header__nav li.has-dropdown > .site-header__nav-row > a {
  flex: 0 1 auto;
  padding-right: 0;
}

.site-header__nav ul a:hover,
.site-header__nav ul a:focus-visible,
.site-header__nav ul .site-header__nav-toggle-label:hover,
.site-header__nav ul .site-header__nav-toggle-label:focus-visible,
.site-header__nav li.is-active > .site-header__nav-row > a,
.site-header__nav li.is-active > .site-header__nav-row > .site-header__nav-toggle-label {
  color: var(--color-accent);
}

/* Dropdown disclosure control (About, Services) — a small CSS-triangle
   chevron, styling approach borrowed from roytberg-infosite's
   .has-dropdown arrow (not a Figma component). Padding matches the
   label's own (var(--space-3)) rather than a separate fixed-width icon
   box, so "About" (label + separate arrow button) and "Services" (one
   button, arrow built in) read with the same spacing rhythm.

   Hover/focus ANYWHERE in the row — the linked label or the arrow —
   colors and rotates the arrow together (see the li-level rule below),
   so it always matches how a single button like "Services" naturally
   behaves as one unit, rather than only reacting when the arrow itself
   is hovered.

   Click/tap only toggles when there's no mouse to hover with (see the
   `(hover: hover)` check in assets/js/app.js) — with a mouse, hover
   already opens and closes it, so a redundant click-toggle would just
   fight with mouseleave. Touch devices, and a mouse-driven compact/
   narrow window (which has no hover-reveal CSS at all), still get an
   explicit click-to-toggle. */
.site-header__dropdown-toggle {
  display: flex;
  align-items: center;
  /* Grows to absorb the row's remaining width instead of the link (see
     above) — its own content (just the arrow) stays left-aligned via the
     default justify-content: flex-start, so it renders immediately after
     the text either way; any extra width becomes invisible trailing
     space, same as "Services" already does within its own single
     button. padding-left: 0 pairs with the link's padding-right: 0
     above — together they leave exactly one var(--space-2) gap (the
     arrow's own margin-left) between text and arrow, matching Services. */
  flex: 1;
  padding: var(--space-3);
  padding-left: 0;
  background: none;
  border: none;
  color: var(--palette-neutral-light);
  cursor: pointer;
}

.site-header__dropdown-toggle::after,
.site-header__nav-toggle-label::after {
  content: '';
  width: 0;
  height: 0;
  flex-shrink: 0;
  margin-left: var(--space-2);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform var(--transition-fast);
}

.site-header__nav li.has-dropdown:hover .site-header__nav-row a,
.site-header__nav li.has-dropdown:hover .site-header__nav-row .site-header__nav-toggle-label,
.site-header__nav li.has-dropdown:hover .site-header__dropdown-toggle,
.site-header__nav li.has-dropdown:focus-within .site-header__nav-row a,
.site-header__nav li.has-dropdown:focus-within .site-header__nav-row .site-header__nav-toggle-label,
.site-header__nav li.has-dropdown:focus-within .site-header__dropdown-toggle,
.site-header__nav li.has-dropdown.is-open .site-header__nav-row a,
.site-header__nav li.has-dropdown.is-open .site-header__nav-row .site-header__nav-toggle-label,
.site-header__nav li.has-dropdown.is-open .site-header__dropdown-toggle {
  color: var(--color-accent);
}

.site-header__nav li.has-dropdown:hover .site-header__dropdown-toggle::after,
.site-header__nav li.has-dropdown:hover .site-header__nav-toggle-label::after,
.site-header__nav li.has-dropdown:focus-within .site-header__dropdown-toggle::after,
.site-header__nav li.has-dropdown:focus-within .site-header__nav-toggle-label::after,
.site-header__nav li.has-dropdown.is-open .site-header__dropdown-toggle::after,
.site-header__nav li.has-dropdown.is-open .site-header__nav-toggle-label::after {
  transform: rotate(180deg);
}

/* Compact default: static, indented, toggled via .is-open (see app.js).
   flex-direction: column lives here — unscoped to a specific trigger —
   so it applies no matter how the dropdown becomes visible. It used to
   only be set inside the .is-open (tap) rule below; the expanded hover/
   focus-within reveal further down had no direction of its own, so it
   silently fell back to flex's default row axis, listing "Pricing"/
   "Technology" side by side instead of stacked — a real bug, not a
   style choice. */
.site-header__nav .dropdown {
  display: none;
  flex-direction: column;
  position: static;
  min-width: 0;
  padding: 0;
  margin: 0;
  list-style: none;
  background-color: transparent;
  border-top: none;
  border-left: 2px solid var(--color-accent);
}

.site-header__nav li.has-dropdown.is-open > .dropdown {
  display: flex;
}

/* Expanded: back to Figma's absolutely-positioned dropdown, now fading +
   sliding down on reveal — matching roytberg-infosite's desktop
   .nav-dropdown, the same slide/fade idea as the mobile panel above just
   shorter (0.5rem, not a full off-screen slide) and quicker (0.18s) to
   suit a small menu rather than a full-screen one. display: flex is
   unconditional here (not toggled by hover/is-open) because opacity/
   transform/visibility need something to animate FROM — jumping from
   display: none can't transition. */
.site-header.is-expanded .site-header__nav .dropdown {
  display: flex;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 12rem;
  padding: var(--space-2) 0;
  background-color: var(--palette-neutral-dark);
  border-top: 2px solid var(--color-accent);
  border-left: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(0.5rem);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
  z-index: var(--z-dropdown);
}

.site-header.is-expanded .site-header__nav li.has-dropdown.is-open .dropdown,
.site-header.is-expanded .site-header__nav li.has-dropdown:hover .dropdown,
.site-header.is-expanded .site-header__nav li.has-dropdown:focus-within .dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.site-header__nav .dropdown li {
  display: block;
}

.site-header__nav .dropdown a {
  padding: var(--space-2) var(--space-3);
}

/* Compact default: full-width, stacked beneath the nav list in the same
   off-canvas panel. */
.site-header__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
}

/* Expanded: the one part of the header Figma marks "self-stretch"/
   "h-full" (node 14115:826) — reaches the header's full height while the
   logo/nav list around it stay content-sized. Also carries the 16px gap
   between the phone link and the Book Now button (node 14115:830's
   parent). */
.site-header.is-expanded .site-header__actions {
  flex-direction: row;
  align-items: stretch;
  align-self: stretch;
  gap: var(--space-3); /* 16px */
  width: auto;
  flex-shrink: 0;
}

.site-header__phone {
  display: flex;
  align-items: center;
  padding: var(--space-4); /* 24px */
  font-family: var(--font-mono);
  font-size: var(--text-btn);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-btn);
  color: var(--color-accent);
  white-space: nowrap;
  transition: color var(--transition-fast);
}

.site-header__phone:hover,
.site-header__phone:focus-visible {
  color: var(--color-highlight);
}

/* Compact default: full-width, centered. Header's Book Now button
   reuses .btn-primary's color/type but with Figma's own asymmetric
   padding once expanded (64px/24px, node 14117:38) — wider than the
   shared --space scale provides, kept as an explicit value the same way
   --container-max-width is. */
.site-header__cta {
  padding-inline: var(--space-4);
  padding-block: var(--space-4);
  text-align: center;
}

.site-header.is-expanded .site-header__cta {
  padding-inline: 4rem;
  text-align: left;
}

/* --------------------------------------------------------------------------
   8. Footer
   From Figma's two footer pieces: the map (node 14154:3683) and the main
   footer (node 14154:3641). The "Navigation"/"Services" column *content*
   is driven by $site['nav'] (see views/partials/footer.php), not Figma's
   stale mock content there — same reasoning already applied to the
   header nav. Several spacing values below (96px/32px top/bottom padding,
   32px stack gap) are Figma-exact but don't land on the --space scale,
   kept as explicit one-off values the same way --container-max-width is.
   -------------------------------------------------------------------------- */

.site-footer {
  background-color: var(--palette-neutral-black);
}

/* --- Map --- */

.site-footer__map {
  position: relative;
  display: block;
  overflow: hidden;
}

/* aspect-ratio keeps the image's natural 1920:420 proportions at wide
   viewports (same effect the old width:100%/height:auto pairing had),
   but min-height overrides it once that ratio would otherwise shrink
   the map down to a sliver on narrow screens — the browser uses
   whichever of the two computes taller. object-fit: cover crops to fill
   that height without distorting or stretching, and object-position:
   center keeps the location pin (roughly centered in the source image)
   in view rather than letting the crop drift toward an edge. Sits on
   the <img> itself, not the <a> wrapper — the wrapper's height just
   follows the image's, so the hover label (inset: 0, positioned
   relative to the <a>) still covers it exactly either way. */
.site-footer__map img {
  display: block;
  width: 100%;
  aspect-ratio: 1920 / 420;
  min-height: 14rem; /* 224px floor — proposed default, not in Figma */
  object-fit: cover;
  object-position: center;
}

/* A real link now (Figma's own export was just a static image) — this
   label fades in on hover/focus to signal it's clickable, since a plain
   map screenshot has no built-in affordance for that on its own. */
.site-footer__map-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.6);
  color: var(--palette-neutral-white);
  font-family: var(--font-mono);
  font-size: var(--text-btn);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-btn);
  text-transform: uppercase;
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.site-footer__map:hover .site-footer__map-label,
.site-footer__map:focus-visible .site-footer__map-label {
  opacity: 1;
}

/* --- Main footer --- */

.site-footer__main {
  position: relative;
  overflow: hidden; /* clips the watermark below so it can't cause horizontal scroll */
  padding-top: 6rem; /* 96px */
  padding-bottom: 2rem; /* 32px */
}

/* Giant decorative background text (Figma node 14154:3642) — purely
   textural (its color is barely lighter than the pure-black footer bg),
   so it's aria-hidden and given a floor small enough not to overwhelm
   mobile layouts rather than scaling all the way down to Figma's literal
   375px-viewport value. Fluid between a 4rem floor at 375px and Figma's
   exact ~320px (20rem) value at 1920px — same clamp()-interpolation
   method as the fluid type scale in "1. Design tokens". */
.site-footer__watermark {
  position: absolute;
  left: 0;
  bottom: -0.1em;
  z-index: 0;
  margin: 0;
  font-family: var(--font-mono);
  font-weight: var(--font-weight-bold);
  font-size: clamp(4rem, 0.1165rem + 16.5696vw, 20rem);
  line-height: 0.8;
  color: var(--palette-neutral-dark);
  opacity: 0.33;
  text-transform: uppercase;
  white-space: nowrap;
  user-select: none;
}

.site-footer__watermark span {
  display: block;
}

.site-footer__container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 2rem; /* 32px */
}

.site-footer__columns {
  row-gap: var(--space-6); /* 80px — breathing room once columns stack on mobile */
}

/* Manually re-exported (node 14154:3645) at 370x400 — exactly 2x the
   185x200 box below, already cropped tight with no padding, and
   genuinely transparent — no crop/zoom transform needed to compensate
   for anything, unlike the earlier raw asset this replaced. */
.site-footer__brand img {
  display: block;
  width: 11.5625rem; /* 185px */
  height: 12.5rem; /* 200px */
}

.site-footer__column {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 24px */
}

.site-footer__column ul {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 32px in Figma; --space-4 (24px) reused for
                           consistency with the label-to-list gap above,
                           close enough to not read as a mismatch */
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer__column a {
  color: var(--palette-neutral-white);
}

.site-footer__find-us li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3); /* 16px */
  color: var(--palette-neutral-medium-light);
}

.site-footer__find-us li:nth-child(2) {
  align-items: center; /* phone row has no wrapping text to align against */
}

.site-footer__find-us svg {
  flex-shrink: 0;
  width: 0.8125rem; /* 13px */
  height: 0.8125rem;
  margin-top: 0.2em; /* optically aligns the icon with the first line of text */
}

.site-footer__find-us address,
.site-footer__find-us a,
.site-footer__find-us span {
  font-style: normal;
  color: inherit;
}

.site-footer__divider {
  height: 1px;
  margin: 0;
  border: none;
  background-color: var(--color-border);
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  color: var(--palette-neutral-faint);
}

.site-footer__legal p {
  margin: 0;
}

/* Targets "p" and "a" explicitly, not just the shared ".site-footer__legal"
   parent — a directly-matched rule (the global "p { font-size: ... }" in
   "4. Base element styles") always wins over an inherited value regardless
   of specificity, so the copyright <p> was silently falling back to the
   standard body size instead of inheriting this smaller one. */
.site-footer__legal,
.site-footer__legal p,
.site-footer__legal a {
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
}

.site-footer__legal a {
  color: inherit;
}

.site-footer__legal a:hover {
  color: var(--color-accent);
}

.site-footer__legal-links {
  display: flex;
  gap: var(--space-4); /* 24px */
}

/* Columns stack full-width below the grid's own 768px breakpoint (see
   ".grid" in "6. Layout primitives") — the brand column just needs a
   little breathing room under the logo once that happens. */
@media (max-width: 767px) {
  .site-footer__brand {
    margin-bottom: var(--space-3);
  }
}

/* Tablet only (this project's own documented 768–1024px range — see the
   breakpoints comment in "1. Design tokens" — narrower than the shared
   ".grid" utility's single, open-ended 768px+ breakpoint). Four equal
   .col-span-3 columns are comfortable against desktop's much wider
   container but cramped at tablet width. This is a layered override on
   top of that shared utility, not a change to it (the style guide's own
   grid demo still relies on the unmodified version) — explicit
   grid-column/grid-row placement repositions the same four items
   without touching markup: the logo becomes a tall left column, and the
   three nav columns stack in a single column beside it. (Tried them
   three-across first — at this width that was just as cramped as the
   four-across problem this is fixing, so stacking reads better here.) */
@media (min-width: 768px) and (max-width: 1024px) {
  .site-footer__columns {
    grid-template-columns: auto 1fr;
    column-gap: var(--space-7); /* 160px */
  }

  .site-footer__brand {
    grid-column: 1;
    grid-row: 1 / -1;
    align-self: center;
  }

  .site-footer__column {
    grid-column: 2;
  }
}

/* --------------------------------------------------------------------------
   9. CTA
   "Book Your Consultation" section (Figma node 14151:916), rendered by
   views/partials/cta.php near the bottom of every page. Pure black
   background (Figma's own bg-black, distinct from the site's #1B1B1B
   page background — see "1. Design tokens") with a low-opacity
   stone-texture photo overlay: the same "Background/Stone" token flagged
   back when the design tokens were first pulled as needing a real asset
   ("the texture asset itself still needs to be sourced from Figma"),
   finally sourced here. 120px section padding and column gap don't land
   on the --space scale — kept as explicit one-off values the same way
   --container-max-width is.
   -------------------------------------------------------------------------- */

.cta {
  position: relative;
  overflow: hidden;
  background-color: var(--palette-neutral-black);
  padding-block: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
  /* The header's Book Now button (site-header__cta) links to #book —
     without this, the fixed header would cover the top of the section
     when the browser jumps to the anchor. Same idea as #main's own
     dev-banner-aware padding-top below, just for scroll position instead
     of layout. */
  scroll-margin-top: var(--header-height);
}

body.has-dev-banner .cta {
  scroll-margin-top: calc(var(--header-height) + var(--dev-banner-height));
}

.cta__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.cta__bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.2;
}

.cta .container {
  position: relative;
  z-index: 1;
}

/* A dedicated 2-column grid, not the shared 12-column .grid utility (see
   "6. Layout primitives") — that utility's gap applies between every one
   of its 11 internal column-track pairs, not just the single visible gap
   between two spanning items. Reusing it here with this section's 120px
   gap silently required 11 × 120px = 1320px just for gap space alone
   (confirmed: that's exactly the bogus width every child was reporting),
   overflowing at any viewport and getting invisibly clipped by .cta's
   own overflow: hidden. A grid with only the 2 tracks it actually needs
   has exactly 1 real gap, matching Figma's actual single-gap intent.
   Mobile-first: single column below 768px (matches the shared .grid's
   own breakpoint), 5:7 split (≈ Figma's 576px:720px column widths) at
   768px+. */
.cta__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6); /* 80px — comfortable stacked gap below 768px */
}

@media (min-width: 768px) {
  .cta__grid {
    grid-template-columns: 5fr 7fr;
    /* Fluid between tablet and desktop instead of jumping straight to
       Figma's exact 120px (7.5rem) at 768px — 40px (2.5rem) floor keeps
       the two columns from crowding at the low end of this breakpoint,
       scaling up to the full 120px by the time there's room for it. */
    gap: clamp(2.5rem, 5vw, 7.5rem);
  }
}

.cta__info {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 32px in Figma; --space-4 (24px) reused — same
                           call already made for the footer's identical
                           recurring ~32px rhythm */
  min-width: 0; /* grid items default to min-width: auto (their content's
                    intrinsic min-content size) instead of shrinking with
                    the track — without this, a long word/value anywhere
                    inside silently pushes the whole column wider than
                    its grid track and gets clipped by .cta's own
                    overflow: hidden rather than actually reflowing */
}

/* Reusable eyebrow (dot + label) — this exact "dot, small-caps label"
   component recurs across several page sections in Figma (The Doctor,
   The Practice, Technology, Real Results, Contact Us...), so it's a
   shared utility rather than a one-off, even though this CTA section is
   the first place any of those sections has actually been built. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2); /* 8px */
}

.eyebrow::before {
  content: '';
  flex-shrink: 0;
  width: 0.25rem; /* 4px */
  height: 0.25rem;
  border-radius: var(--radius-full);
  background-color: currentColor;
}

.cta__heading {
  margin: 0;
}

.cta__heading-accent {
  color: var(--color-highlight);
}

.cta__blurb {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  color: var(--color-text);
}

.cta__blurb p {
  margin: 0;
}

.cta__divider {
  height: 1px;
  margin: 0;
  border: none;
  background-color: var(--color-border);
}

.cta__contact-buttons {
  display: flex;
  align-items: center;
  gap: var(--space-4); /* 32px */
}

.cta__whatsapp {
  display: block;
  flex-shrink: 0;
  width: 3.75rem; /* 60px */
  height: 3.75rem;
}

.cta__whatsapp svg {
  display: block;
  width: 100%;
  height: 100%;
}

.cta__meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-4); /* 32px */
}

.cta__meta-label {
  flex-shrink: 0;
  width: 4.3125rem; /* 69px, node 14151:1011 */
  margin: 0;
  color: var(--color-text);
}

.cta__meta-value {
  margin: 0;
  color: var(--palette-neutral-white);
}

.cta__directions {
  color: var(--color-accent);
  text-decoration: underline;
}

.cta__form-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-5); /* 40px, node 14151:956 */
  min-width: 0; /* see .cta__info's comment — the "Reason for visit"
                    <select>'s longest option ("Restorative Dentistry")
                    is the actual culprit that was forcing this column to
                    ~1320px and getting silently clipped on narrow screens */
}

.cta__form-heading {
  margin: 0;
  text-align: center;
}

/* Placeholder form — see the doc comment at the top of cta.php. First
   real form in this codebase, so its input styling lives here rather
   than a shared "forms" section, matching this project's established
   "don't build speculatively" approach — easy to extract once a second
   form (e.g. the /contact/ page's own) needs the same styles. */
.cta__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.cta__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
}

.cta__field-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 24px */
}

.cta__field-row .cta__field {
  flex: 1;
  min-width: 0;
}

@media (min-width: 601px) and (max-width: 767px) {
  .cta__field-row {
    flex-direction: row;
  }
}

@media (min-width: 1025px) {
  .cta__field-row {
    flex-direction: row;
  }
}

.cta__field label {
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
  color: var(--palette-neutral-white);
}

.cta__field input,
.cta__field select,
.cta__field textarea {
  width: 100%;
  min-width: 0; /* the real source of the min-width: auto chain fixed
                    above on .cta__info/.cta__form-wrap — <select> in
                    particular sizes to its longest <option> by default
                    ("Restorative Dentistry"), which is wider than
                    width: 100% alone can override */
  padding: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--palette-neutral-dark);
  background-color: var(--palette-neutral-white);
  border: none;
}

.cta__field input::placeholder,
.cta__field textarea::placeholder {
  color: var(--palette-neutral-medium);
}

.cta__field textarea {
  resize: vertical;
  min-height: 8rem;
}

.cta__select-wrap {
  position: relative;
}

.cta__select-wrap select {
  padding-right: var(--space-6); /* room for the arrow below */
  color: var(--palette-neutral-medium); /* placeholder-styled until a real option is picked */
  appearance: none;
}

.cta__select-wrap select:invalid {
  color: var(--palette-neutral-medium);
}

.cta__select-wrap select:not(:invalid) {
  color: var(--palette-neutral-dark);
}

/* Same small-triangle technique as the header's dropdown arrows (see
   "7. Navigation") — a <select> can't take a real ::after itself, so it
   goes on this wrapper instead. */
.cta__select-wrap::after {
  content: '';
  position: absolute;
  top: 50%;
  right: var(--space-3);
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--palette-neutral-medium);
  pointer-events: none;
}

.cta__submit {
  width: 100%;
}


/* --------------------------------------------------------------------------
   10. Hero
   Two variants: the unique home hero (Figma node 14115:748, hand-built in
   views/home.php) and the shared internal-page hero (views/partials/hero.php,
   confirmed identical structure across all 4 real page mockups this
   project has — About 14152:1749, Fastbraces 14152:2296, Gallery
   14154:2653, Contact 14154:3207). Shared rules below cover what both
   have in common (photo background, vignette, bleeding up under the
   fixed header); each variant's own section adds what's different.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  color: var(--palette-neutral-white);
  text-align: center;
  /* Bleeds up behind the fixed, translucent header — matching Figma,
     where the header floats over the hero photo instead of sitting
     above a solid-color gap. #main's own padding-top (see "6. Layout
     primitives") already reserves this space; pulling the hero up into
     it with a negative margin, while adding the same amount back as the
     hero's own padding-top, keeps its content exactly where it would've
     landed anyway — the negative margin and the padding cancel out on
     the box's own height, so nothing after the hero shifts position. */
  margin-top: calc(-1 * var(--header-height));

  /* Extra breathing room a variant can add above/below its content, on
     top of (not instead of) the header clearance below — set once here
     via a variable so a variant (.hero--inner) only ever needs to set
     --hero-extra-pad, instead of declaring its own padding-top/bottom
     and fighting this rule's specificity (which must always win, since
     the header-clearance amount is structural, not a design choice). */
  --hero-extra-pad: 0px;
  padding-top: calc(var(--header-height) + var(--hero-extra-pad));
  padding-bottom: var(--hero-extra-pad);
}

body.has-dev-banner .hero {
  margin-top: calc(-1 * (var(--header-height) + var(--dev-banner-height)));
  padding-top: calc(var(--header-height) + var(--dev-banner-height) + var(--hero-extra-pad));
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Simplified from Figma's 12-color-stop inline SVG radial gradient
   (mix-blend-multiply over a white-to-black gradient, duplicated once
   per hero instance in the source file) — a direct transparent-to-black
   radial gradient reads as the same "darkened toward the edges" vignette
   without the blend-mode indirection, defined once here instead. */
.hero__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.6) 100%);
}

/* --- Internal-page hero (views/partials/hero.php) --- */

.hero--inner,
.hero--404 {
  /* Fluid instead of the old flat 6rem (96px, node 14152:1749 py-96) —
     scales with viewport width so the section (and its full-bleed photo)
     grows proportionally from small screens up through x-large ones,
     then plateaus past the clamp's ceiling instead of growing unbounded
     like a raw % value would on ultra-wide monitors. Previously this also
     set padding-top directly, which (via the cascade tie against .hero's
     own padding-top above) left content sitting flush against the header
     with no real buffer — --hero-extra-pad now adds genuine breathing
     room on both sides instead.

     Sized so the section lands at ~720px tall on a 1920px-wide screen,
     matching the Figma mockup: header (96px, fixed) + 2x this value +
     the eyebrow/title content block (~242px) ≈ 720px, i.e. this needs to
     reach ~12rem (192px) by 1920px — solved via 10vw so it lands there
     almost exactly (10vw * 1920px = 192px) and plateaus beyond it. */
  --hero-extra-pad: clamp(3rem, 10vw, 12rem);
}

.hero__container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5); /* 48px in Figma; --space-5 (40px) reused — same
                           "close enough, don't add a token for one use"
                           call already made for the footer's 32px gap */
  max-width: 50rem; /* proposed default — a comfortable centered reading
                        column, not a literal conversion of Figma's fixed
                        396px side padding (sized for one 1920px frame) */
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* Internal pages: white eyebrow — differs from home's gold (see
   .hero--home__eyebrow below), node 14152:1754 vs. 14115:753. */
.hero__eyebrow {
  color: var(--palette-neutral-white);
}

/* Font-size/weight/tracking/line-height all already come from the base
   "h1" rule in "4. Base element styles" (this IS an <h1>) — including
   color, since --color-heading is already white in this dark-theme
   project (see "1. Design tokens"). Only margin needs resetting. */
.hero__title {
  margin: 0;
}

.hero__title > span {
  display: block;
}

/* --- Home hero (views/home.php) --- */

.hero--home {
  min-height: 100vh; /* fills the viewport, matching Figma's justify-end layout */
  justify-content: flex-end;
  gap: 7.5rem; /* 120px, node 14115:748 gap-120 */

  /* Fluid, was a flat 96px py-96. Routed through --hero-extra-pad (see
     the base .hero rule) instead of setting padding-block directly —
     that always lost the padding-top half of the tie against .hero's own
     header-clearance padding-top (equal specificity, and with equal
     specificity .hero--home's later position in the file doesn't help
     once body.has-dev-banner .hero's higher-specificity override is
     added into the mix) — meaning content had *zero* real buffer above
     the header, dev banner or not. On a viewport short enough that this
     hero's own content taller than min-height: 100vh (routine on real
     laptop screens once browser chrome is subtracted), justify-content:
     flex-end can't create any bottom-alignment gap either, so the logo
     ended up rendered flush against the header — this fixes both. */
  --hero-extra-pad: clamp(2rem, 6vw, 6rem);
}

.hero--home__container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  gap: var(--space-5); /* 48px in Figma; --space-5 (40px) reused, same as the internal hero */
  max-width: 64rem; /* wider than the internal hero's reading column to
                        comfortably fit the trust-bar row; not a literal
                        conversion of Figma's 396px. Must stay wide enough
                        that "Exceptional Dentistry" fits on one line at
                        --text-h1's clamped max (5.6875rem) — 58rem wrapped
                        it to a 3rd line above ~1879px viewports. */
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.hero--home__logo {
  display: block;
  width: 9.5rem; /* 152px, node 14122:93 */
  height: 10.25rem; /* 164px */
}

/* Home: gold eyebrow — differs from internal pages' white (see
   .hero__eyebrow above), node 14115:753. */
.hero--home__eyebrow {
  color: var(--color-highlight);
}

.hero--home__title {
  margin: 0;
}

.hero--home__title > span {
  display: block;
}

.hero--home__title-accent {
  color: var(--color-highlight);
}

.hero--home__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-3); /* 16px, node 14123:115 */
  padding-block: var(--space-4); /* 32px in Figma py-32; --space-4 (24px) reused */
}

.hero--home__trust-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2); /* 8px */
  list-style: none;
  margin: 0;
  width: 100%;
  max-width: 47.125rem; /* 754px, node 14123:121's own fixed width */
  padding-top: var(--space-4); /* 32px in Figma pt-32; --space-4 (24px) reused */
  border-top: 1px solid rgba(209, 193, 118, 0.5); /* Primary/Highlight at 50% opacity, node 14123:121 */
}

/* Trust-bar entries are gold too, matching the main eyebrow above —
   .eyebrow's dot (::before) picks this up automatically via currentColor. */
.hero--home__trust-bar li {
  color: var(--color-highlight);
}

.hero--home > svg {
  position: relative; /* lifts it above .hero__bg (absolutely positioned,
                          z-index: auto — without this the static-flow
                          svg painted underneath it) */
  z-index: 1;
  flex-shrink: 0;
  width: 1.3125rem; /* 21px */
  height: 2rem; /* 32px */
  color: var(--palette-neutral-white);
}

/* Scroll-down cue, home hero only — the "wheel" line inside the mouse
   glyph travels down and fades out, then resets, looping. Reduced-motion
   users get the static icon via the global override in "2. Accessibility". */
.hero--home .mouse-scroll__wheel {
  transform-origin: center;
  animation: mouse-scroll-wheel 1.6s ease-in-out infinite;
}

@keyframes mouse-scroll-wheel {
  0% {
    transform: translateY(0);
    opacity: 1;
  }
  50% {
    transform: translateY(6px);
    opacity: 0;
  }
  50.01% {
    transform: translateY(0);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   11. Testimonials
   "Results That Speak for Themselves" (Figma nodes 14150:740 Home,
   14152:1936 About, 14152:2356 Fastbraces — confirmed identical content
   and styling across all three; a 4th, duplicate card on every instance
   is treated as a Figma mockup copy-paste artifact and dropped). Manually
   required by each view at the point it belongs (see
   views/partials/testimonials.php's own docblock) rather than auto-
   rendered by views/layout.php, since — unlike hero/cta — its position
   in the page varies: between Technology and Smile Results on home,
   right above the CTA section on About and Fastbraces.

   Deliberately the one light-background section on an otherwise all-dark
   site — Figma's own choice (a white card/photo treatment breaking up
   the dark theme), not a deviation from it. Colors below are literal
   palette values rather than the dark-theme semantic tokens (--color-*)
   for that reason. */

.testimonials {
  position: relative;
  overflow: hidden;
  padding-block: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px (node 14150:740 py-120) */
  background-color: var(--palette-neutral-white);
  color: var(--palette-neutral-medium);
}

.testimonials__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.testimonials__bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Lightens the photo — Figma's own translucent white wash over it
   (node 14150:740's sibling rect, bg-white/50). */
.testimonials__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: rgba(255, 255, 255, 0.5);
}

/* Fades the photo out toward the section's bottom edge so it blends into
   whatever comes next, matching Figma's "Overlay" node (14151:842). */
.testimonials__fade {
  position: absolute;
  z-index: 0;
  inset: auto 0 0 0;
  height: 25rem; /* 407px */
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, var(--palette-neutral-white) 100%);
}

.testimonials__grid {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 6rem; /* 96px, not on the --space scale */
}

.testimonials__intro {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4); /* 32px in Figma; --space-4 (24px) reused — same
                           "close enough, don't add a token for one use"
                           call already made for the footer's 32px gap */
  flex: 1 1 20rem;
  max-width: 30rem; /* 480px, node 14150:741 */
}

.testimonials__heading {
  margin: 0;
  color: var(--palette-neutral-medium);
}

.testimonials__heading-accent {
  color: var(--color-highlight);
}

.testimonials__list {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  flex: 2 1 40rem;
  max-width: 49rem; /* 784px, node 14150:808 */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  background-color: rgba(255, 255, 255, 0.01);
}

.testimonials__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 32px */
  padding: 3rem; /* 48px, not on the --space scale */
}

.testimonials__stars {
  color: var(--palette-primary-accent);
  font-size: 0.75rem;
  letter-spacing: 0.1875rem; /* ~3px gap between stars */
}

.testimonials__quote {
  margin: 0;
  font-family: var(--font-heading);
  font-style: italic;
  font-size: 1.3125rem; /* 21px */
  line-height: 1.5;
  color: var(--palette-neutral-medium);
}

.testimonials__attribution {
  margin: 0;
  color: var(--palette-neutral-medium-light);
}

.testimonials__divider {
  height: 1px;
  margin: 0;
  border: none;
  /* Proposed default — Figma's divider is a raster gradient-fade image;
     a flat, low-opacity hairline reads the same on this light card. */
  background-color: rgba(88, 88, 88, 0.15);
}

/* No mobile frame exists for this section in Figma (Desktop-only file,
   same caveat as the header) — stacking the two columns below a
   reasonable breakpoint is this project's own responsive fallback. */
@media (max-width: 56.25rem) {
  .testimonials__grid {
    flex-direction: column;
  }

  .testimonials__intro,
  .testimonials__list {
    /* Resets the desktop row layout's flex-basis (20rem / 40rem) — once
       flex-direction flips to column, that basis applies to height
       instead of width, forcing .testimonials__intro to reserve 320px
       of vertical space it doesn't need and leaving a large gap before
       .testimonials__list. */
    flex: none;
    max-width: none;
    width: 100%;
  }

  .testimonials__card {
    padding: var(--space-4); /* 24px — Figma's 48px is too generous once
                                 the card goes full-width on a narrow screen */
  }
}

/* --------------------------------------------------------------------------
   12. The Doctor
   "An Authority Trusted Worldwide" (Figma node 14115:105, home page only —
   the About page has its own, differently-shaped doctor bio section, not
   this one). Plain section on the page's existing dark background — no
   full-bleed photo/texture layer like Testimonials or CTA have.
   -------------------------------------------------------------------------- */

.doctor {
  /* Mobile-first (below 768px): the photo is the first stacked item, so
     it gets real top padding instead of sitting flush at the very top
     edge — flush-against-the-hero is a two-column-only effect. The
     min-width override below removes this once columns go side-by-side
     at 768px+, where .doctor__content's own padding-top (below) takes
     over giving the text column equivalent breathing room instead. */
  padding-top: clamp(2.5rem, 8vw, 5rem);
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px —
                              same one-off value already used by Testimonials and CTA */
}

@media (min-width: 48rem) {
  .doctor {
    padding-top: 0;
  }
}

/* Mobile-first: single stacked column below 768px. Two-column, photo-
   flush layout only kicks in at 768px+ (see min-width override below) —
   an explicit breakpoint rather than relying on flex-wrap to decide when
   to wrap, so the switch always happens at the same width. */
.doctor__grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(2rem, 5vw, 6rem); /* 32-96px, fluid — was a flat 96px */
}

@media (min-width: 48rem) {
  .doctor__grid {
    flex-direction: row;
  }
}

/* Same two-column proportions as Testimonials (photo ~550px : content
   ~770px in Figma, roughly a 4:5.6 ratio) via the same flex-basis
   technique — only applied at 768px+ (see above), full-width below it. */
.doctor__photo {
  margin: 0;
  width: 100%;
}

@media (min-width: 48rem) {
  .doctor__photo {
    flex: 1 1 20rem;
    max-width: 34.375rem; /* 550px, node 14115:106 */
    width: auto;
  }
}

.doctor__photo img {
  width: 100%;
}

.doctor__photo figcaption {
  margin-top: var(--space-4); /* 24px */
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
}

.doctor__photo-name {
  color: var(--color-heading);
}

.doctor__photo-role {
  color: var(--color-highlight);
}

.doctor__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5); /* 40px */
  width: 100%;
}

@media (min-width: 48rem) {
  .doctor__content {
    flex: 2 1 30rem;
    max-width: 48.125rem; /* 770px, node 14115:119 */
    width: auto;
    /* Matches .doctor's own removed top padding at this breakpoint, so
       the content column keeps the same breathing room the photo gave
       up to sit flush above — fluid (same clamp as .cta__grid's gap) so
       it shrinks on narrower desktop widths instead of holding a flat
       120px. */
    padding-top: clamp(2.5rem, 5vw, 7.5rem);
  }
}

.doctor__heading {
  margin: 0;
}

.doctor__heading > span {
  display: block;
}

.doctor__heading-accent {
  color: var(--color-highlight);
}

.doctor__lead {
  margin: 0;
  color: var(--color-text);
}

.doctor__credentials {
  display: flex;
  flex-direction: column;
  gap: var(--space-5); /* 40px — close to Figma's 86px row height once its
                           own content height is subtracted out */
  margin: 0;
  padding: 0;
  list-style: none;
  width: 100%;
}

.doctor__credential {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4); /* 24px, node 14115:136's x-offset (41px) minus the
                           index number's own width (17px) */
}

.doctor__credential > div {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
  min-width: 0;
}

.doctor__credential-index {
  flex-shrink: 0;
  width: 1.5rem;
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
}

.doctor__credential-title {
  margin: 0;
  font-weight: var(--font-weight-bold);
  color: var(--color-heading);
}

.doctor__credential-desc {
  margin: 0;
  font-style: italic;
  color: var(--color-text-muted);
}

/* Affiliations/Awards logo strip (Figma node 14152:1284) — sits directly
   below The Doctor, one row of 4 accreditation badges spread edge-to-edge
   across the same container The Doctor uses (their combined left/right
   edges land exactly on --container-max-width). Section height (240px)
   minus the tallest logo (120px) splits evenly top/bottom in Figma, so
   that's expressed as padding-block rather than a fixed section height. */
.affiliations {
  padding-block: clamp(1.5rem, 4vw, 3.75rem); /* fluid, was a flat 60px */
}

.affiliations__row {
  display: flex;
  flex-wrap: nowrap; /* logos shrink to stay on one line instead of wrapping */
  align-items: center;
  justify-content: space-between;
  gap: clamp(1rem, 4vw, 5rem); /* Figma's own ~200px one-off gaps only work
                                   at the full 1416px container width */
}

.affiliations__logo {
  flex: 1 1 7.5rem; /* 120px basis, free to shrink below it */
  min-width: 0; /* let a replaced element shrink past its intrinsic size */
  width: auto;
  max-width: 7.5rem;
  height: auto;
  max-height: 7.5rem;
  object-fit: contain; /* ratio comes from the img's width/height attributes */
}

/* iACD is a wide text wordmark (453x63 in Figma), not a square badge like
   the other three — sized by its own aspect ratio instead of the shared
   square box. */
.affiliations__logo--wordmark {
  flex-basis: 28.3125rem; /* 453px */
  max-width: 28.3125rem;
  max-height: 3.9375rem; /* 63px */
}

/* --------------------------------------------------------------------------
   13. Services
   "Every Aspect of Your Dental Health" (Figma node 14115:184) — a 2x2 quilt
   of photo cards with zero gap between them (unlike the shared .grid
   utility's gutter), contained within the standard .container max-width
   (not edge-to-edge with the viewport — the quilt effect is relative to
   the container, matching Figma), each with a translucent info panel
   bottom-right and a thin gold bar along its bottom edge. Card photos are
   edge-to-edge with no gutter in Figma, and roughly square (~708x710px
   there), expressed below as aspect-ratio: 1 rather than a literal pixel
   height so it stays proportional at any container width.

   Background is the same stone-texture photo the CTA section uses (see
   "9. CTA" — Figma's own asset appears in this section's own raw image
   fills too, just a different color-graded export of the identical
   texture), reused here rather than duplicating a second near-identical
   asset.

   Card text/CTAs are NOT wrapped in a shared section-level link — see the
   doc comment in views/home.php: the 4 categories shown don't map to real
   pages yet, so "Learn More" is a plain, non-interactive label
   (.btn-text's styling reused on a <span>, not an <a>) until they do.
   -------------------------------------------------------------------------- */

.services {
  position: relative;
  overflow: hidden;
  background-color: var(--palette-neutral-black);
  padding-top: clamp(2rem, 6vw, 6rem);    /* fluid, was a flat 96px — Figma node 14115:184 */
  padding-bottom: clamp(3rem, 10vw, 10rem); /* fluid, was a flat 160px */
}

.services__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.services__bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0.2;
}

.services .container {
  position: relative;
  z-index: 1;
}

.services__header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding-block: var(--space-5); /* 40px */
  margin-bottom: var(--space-6); /* 80px */
}

.services__header-text {
  display: flex;
  flex-direction: column;
  gap: 2rem; /* 32px — Figma gap between eyebrow and heading */
}

.services__heading {
  margin: 0;
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-h2);
  line-height: 1;
}

.services__heading > span {
  display: block;
}

.services__heading-accent {
  color: var(--color-highlight);
}

.services__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4); /* 24px */
}

@media (min-width: 768px) {
  .services__grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* Mobile/tablet default: blurb sits below the photo in normal flow — the
   overlaid-on-photo treatment (below, 768px+) gets too cramped/overflows
   once the fixed 386px overlay no longer has room beside the image. */
.services__card {
  display: block; /* now an <a> (see home.php) — links to that category's
                      Pricing section; <a> defaults to inline, which broke
                      the grid-cell sizing/absolute-positioned children
                      below without this */
  position: relative;
  overflow: hidden;
  border-bottom: 0.5rem solid var(--color-highlight); /* 8px gold bar */
}

.services__card-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.services__card-overlay {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4); /* 24px */
  padding: 1.5rem;
  background-color: rgba(88, 88, 88, 0.1);
}

/* 1025px+ only — the overlaid-on-photo treatment needs room for the
   fixed 386px overlay beside the image, which the 768-1024px tablet
   range (see footer's own tablet breakpoint above) doesn't have; tablet
   stays on the stacked mobile layout above instead. */
@media (min-width: 1025px) {
  .services__card {
    aspect-ratio: 1;
  }

  .services__card-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
  }

  /* Gradient tint over each card photo — mix-blend-multiply darkens the
     bottom half while keeping the top half brighter, matching Figma's
     flattened comp (node 14131:318). */
  .services__card::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.5),
      rgba(27, 27, 27, 0.5)
    );
    mix-blend-mode: multiply;
    pointer-events: none;
  }

  .services__card-overlay {
    position: absolute;
    right: 3rem; /* 48px — Figma's card px-[48px] inset */
    bottom: 0;
    z-index: 1;
    width: 24.125rem; /* 386px — Figma fixed width */
    max-width: 100%;
    padding: 2rem 1.5rem;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-top: 1px solid var(--color-highlight);
    border-left: 1px solid var(--color-highlight);
    border-right: 1px solid var(--color-highlight);
  }
}

.services__card-title {
  margin: 0;
  color: var(--palette-neutral-white);
  font-size: var(--text-h3);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: var(--tracking-h3);
  line-height: 1.2;
}

.services__card-desc {
  margin: 0;
  color: var(--palette-neutral-white);
  font-size: var(--text-body);
  line-height: 1.5;
}

/* --------------------------------------------------------------------------
   14. The Practice — "Why Patients Choose Us" (Figma node 14115:257)
   Sits on the page's default #1B1B1B background (--color-background), so
   unlike Services this needs no full-bleed/bg-image wrapper of its own —
   only the stats grid on the right carries its own stone-texture layer.
   -------------------------------------------------------------------------- */

.practice {
  position: relative;
  overflow: hidden; /* clips the rotated watermark logo below */
  padding-block: clamp(3rem, 10vw, 10rem); /* fluid, was a flat var(--space-7)/160px */
}

.practice__watermark {
  position: absolute;
  left: -8rem;
  bottom: -6rem;
  z-index: 0;
  width: 22rem;
  opacity: 0.2;
  transform: rotate(-15.66deg);
  pointer-events: none;
}

.practice__grid {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6rem; /* 96px — same one-off gap constant as .doctor__grid */
}

.practice__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem; /* 32px, not on the --space scale */
  flex: 1 1 20rem;
  max-width: 30rem; /* 480px, node 14136:542 */
}

.practice__heading {
  margin: 0;
}

.practice__heading > span {
  display: block;
}

.practice__heading-accent {
  color: var(--color-highlight);
}

.practice__lead {
  margin: 0;
  color: var(--color-text);
}

.practice__stats {
  position: relative;
  overflow: hidden;
  flex: 2 1 30rem;
}

.practice__stats-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: var(--palette-neutral-black);
}

.practice__stats-bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0.2;
}

.practice__stats-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.125rem; /* 2px — reveals the stats bg as hairline dividers */
}

@media (min-width: 768px) {
  .practice__stats-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.practice__stat {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
  min-height: 12.5rem; /* 200px */
  padding: 3rem; /* 48px */
  background-color: rgba(0, 0, 0, 0.5);
}

.practice__stat--primary {
  background-color: rgba(0, 0, 0, 0.75);
}

.practice__stat-value {
  margin: 0;
  color: var(--color-highlight);
}

.practice__stat-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

.practice__stat-label {
  color: var(--color-highlight);
}

.practice__stat-desc {
  margin: 0;
  color: var(--palette-neutral-light);
}

/* --------------------------------------------------------------------------
   15. Technology — "State-of-the-Art as Standard" (Figma node 14136:676)
   Interactive section: clicking a tech item swaps the featured image.
   Gallery bleeds to the left edge; content stays in .container on the
   right. JS toggles .is-active on items, thumbs, and featured images.
   -------------------------------------------------------------------------- */

.technology {
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
}

.technology__layout {
  display: flex;
  flex-direction: column;
  gap: 3rem;
}

@media (min-width: 1025px) {
  .technology__layout {
    flex-direction: row;
    align-items: flex-start;
    gap: 6rem; /* 96px */
    padding-right: 6.75rem; /* 108px — Figma's pr-[108px] */
  }
}

.technology__gallery {
  display: flex;
  flex-direction: column;
  gap: 0.5rem; /* 8px */
}

@media (min-width: 1025px) {
  .technology__gallery {
    flex: 1 1 0;
    min-width: 20rem;
  }
}

.technology__featured {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
}

.technology__featured-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--transition-slow);
}

.technology__featured-img.is-active {
  opacity: 1;
}

.technology__index {
  position: absolute;
  top: 0;
  right: 0;
  font-family: var(--font-mono);
  font-weight: var(--font-weight-bold);
  font-size: clamp(5rem, 3rem + 8vw, 12.5rem);
  line-height: 1;
  letter-spacing: -0.1em;
  color: var(--color-highlight);
  mix-blend-mode: overlay;
  text-transform: uppercase;
  pointer-events: none;
  user-select: none;
}

.technology__thumbs {
  display: flex;
  gap: 0.5rem; /* 8px */
  overflow-x: auto;
}

.technology__thumb {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: 5rem; /* 80px mobile */
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity var(--transition-fast);
}

@media (min-width: 1025px) {
  .technology__thumb {
    height: 8rem; /* 128px */
  }
}

.technology__thumb.is-active {
  opacity: 1;
}

.technology__thumb:hover {
  opacity: 0.8;
}

.technology__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.technology__content {
  display: flex;
  flex-direction: column;
  gap: 3rem; /* 48px */
}

@media (min-width: 1025px) {
  .technology__content {
    flex: 1 1 0;
    min-width: 20rem;
    padding-top: 2.5rem; /* 40px */
    padding-bottom: 2.5rem;
  }
}

.technology__header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem; /* 32px */
}

.technology__heading {
  margin: 0;
}

.technology__heading > span {
  display: block;
}

.technology__heading-accent {
  color: var(--color-highlight);
}

.technology__lead {
  margin: 0;
  color: var(--color-text);
}

.technology__list {
  display: flex;
  flex-direction: column;
}

.technology__item {
  display: flex;
  align-items: center;
  gap: var(--space-4); /* 24px */
  width: 100%;
  padding: var(--space-4) var(--space-3); /* 24px 16px */
  border: 1px solid transparent;
  background: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  transition: border-color var(--transition-fast);
}

.technology__item.is-active {
  border-color: var(--color-highlight);
}

.technology__item-index {
  flex-shrink: 0;
  color: var(--color-border); /* #585858 — muted when inactive */
  transition: color var(--transition-fast);
}

.technology__item.is-active .technology__item-index {
  color: var(--color-highlight);
}

.technology__item-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3); /* 16px */
}

.technology__item-title {
  margin: 0;
  font-weight: var(--font-weight-bold);
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--palette-neutral-white);
  transition: color var(--transition-fast);
}

.technology__item.is-active .technology__item-title {
  color: var(--color-highlight);
}

.technology__item-desc {
  margin: 0;
  font-style: italic;
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   16. Results — "Real Results" before/after gallery (Figma node 14151:853)
   Reusable partial (views/partials/results.php) — dark section with an
   interactive before/after comparison slider on the left and content/CTA
   on the right. Slider ported from roytberg's gallery-slider: clip-path
   on the "after" layer driven by a CSS custom property (--position) that
   JS updates via pointer drag and a hidden <input type="range"> for
   keyboard/screen-reader accessibility.
   -------------------------------------------------------------------------- */

.results {
  position: relative;
  overflow: hidden; /* clips the glow below so it can't cause horizontal scroll */
  padding-block: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
  /* Soft gold glow in the bottom-right corner (Figma node 14154:2649),
     exported directly from Figma as a flattened PNG rather than
     recreated with CSS — its flat dark background already matches this
     section's own (#1B1B1B) exactly, so it drops in seamlessly as a
     background-image with no extra pseudo-element/z-index needed. */
  background-image: image-set(url('../img/decorative/results-glow.webp') type('image/webp'), url('../img/decorative/results-glow.png') type('image/png'));
  background-repeat: no-repeat;
  background-position: bottom right;
  background-size: min(87.5rem, 90%) auto; /* 1400px — the asset's native width — capped so it never upscales */
}

.results__grid {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(2rem, 6vw, 6rem); /* fluid, was a flat 96px */
}

.results__comparison {
  position: relative;
  flex: 1 1 20rem;
  max-width: 46.5rem; /* 744px, node 14151:891 */
  margin: 0;
}

.results__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  background-color: var(--palette-neutral-dark);
  cursor: ew-resize;
  touch-action: none;
  user-select: none;
}

.results__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

.results__img--before {
  position: absolute;
  inset: 0;
}

.results__after {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 0 var(--position));
}

.results__label {
  position: absolute;
  z-index: 3;
  bottom: 1rem;
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
  color: var(--palette-neutral-white);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.results__label--before {
  left: 1rem;
}

.results__label--after {
  right: 1rem;
}

.results__divider {
  position: absolute;
  z-index: 4;
  top: 0;
  bottom: 0;
  left: var(--position);
  width: 2px;
  background: rgba(255, 255, 255, 0.95);
  transform: translateX(-50%);
  pointer-events: none;
}

.results__divider > span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.75rem;
  height: 2.75rem;
  border: 2px solid var(--palette-neutral-white);
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.14);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  transform: translate(-50%, -50%);
}

.results__divider > span::before,
.results__divider > span::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0.65rem;
  height: 0.65rem;
  border-top: 2px solid var(--palette-neutral-white);
  border-left: 2px solid var(--palette-neutral-white);
}

.results__divider > span::before {
  left: 0.62rem;
  transform: translateY(-50%) rotate(-45deg);
}

.results__divider > span::after {
  right: 0.62rem;
  transform: translateY(-50%) rotate(135deg);
}

.results__range {
  position: absolute;
  z-index: 5;
  left: 50%;
  bottom: 0.75rem;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.results__comparison:focus-within {
  outline: 3px solid var(--color-highlight);
  outline-offset: 6px;
}

.results__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem; /* 32px, node 14151:854 */
  flex: 1 1 20rem;
}

.results__heading {
  margin: 0;
}

.results__heading-accent {
  color: var(--color-highlight);
}

.results__subtitle {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--font-weight-light);
  font-size: 1.3125rem; /* 21px, Desktop/H6 */
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--palette-neutral-light);
}

.results__cta {
  padding-top: 2rem; /* 32px, node 14151:931 py-32 */
}

@media (max-width: 47.9375rem) { /* 767px */
  .results__grid {
    flex-direction: column;
  }

  .results__comparison,
  .results__content {
    flex: none;
    max-width: none;
    width: 100%;
  }
}

/* --------------------------------------------------------------------------
   17. About — practice intro, extended doctor bio, accordion
   About page-specific sections (Figma nodes 14152:1842, 14152:1773,
   14152:2210). The Doctor section here is a fuller bio than the home
   page's abridged version (section 12) — separate class namespace
   (about-doctor vs. doctor) keeps them independent.

   The .accordion__* rules below are NOT About-specific despite living in
   this section — they're shared with the Restorative Dentistry page's
   treatment list (section 19) and its open/close JS (assets/js/app.js,
   scoped to [data-accordion]).
   -------------------------------------------------------------------------- */

/* --- Practice intro (14152:1842) --- */

.about-intro {
  position: relative;
  overflow: hidden;
  padding-block: clamp(3rem, 10vw, 10rem); /* fluid, was a flat 160px */
}

.about-intro__watermark {
  position: absolute;
  top: -3rem;
  left: 7rem;
  z-index: 0;
  width: 18rem; /* scaled down from Figma's 436px — reads too large/heavy at full size */
  opacity: 0.15;
  pointer-events: none;
}

.about-intro__content {
  position: relative;
  z-index: 1;
  max-width: 52.5rem; /* 840px, node 14152:1844 */
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 24px */
}

.about-intro__content p {
  margin: 0;
  color: var(--color-text);
}

/* --- Doctor bio (14152:1773) --- */

.about-doctor {
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
  /* Nav's "Meet Dr. Lalla" link jumps here (#doctor) — without this the
     fixed header would cover the top of the section, same idea as
     .cta's own scroll-margin-top for #book. */
  scroll-margin-top: var(--header-height);
}

body.has-dev-banner .about-doctor {
  scroll-margin-top: calc(var(--header-height) + var(--dev-banner-height));
}

.about-doctor__grid {
  display: flex;
  align-items: flex-start;
  /* nowrap (not wrap): the photo (20rem basis) + content (30rem basis) +
     gap exceed the container between 768-900px, which used to force an
     unwanted wrap to a stacked layout despite flex-direction already
     being row there — both items have flex-grow/shrink so they shrink to
     fit instead. Stacking below 768px is handled explicitly by the
     flex-direction: column media query further down, not by wrapping. */
  flex-wrap: nowrap;
  gap: clamp(2rem, 6vw, 6rem); /* fluid, was a flat 96px */
}

.about-doctor__photo {
  margin: 0;
  flex: 1 1 20rem;
  max-width: 34.375rem; /* 550px */
}

.about-doctor__photo img {
  width: 100%;
}

.about-doctor__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4); /* 24px */
  flex: 2 1 30rem;
  max-width: 48.125rem; /* 770px */
  padding-top: 2.5rem; /* 40px */
}

.about-doctor__heading {
  margin: 0;
}

.about-doctor__heading-accent {
  color: var(--color-highlight);
}

.about-doctor__content p {
  margin: 0;
  color: var(--color-text);
}

.about-doctor__training {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
  margin: 0;
  padding-left: 1.25rem;
  list-style: disc;
  color: var(--color-text);
}

/* --- Accordion (14152:2210) --- */

.accordion-section {
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
}

.accordion {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 24px — gap between collapsed items */
}

.accordion__item {
  border: 1px solid var(--color-highlight);
}

.accordion__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  padding: 1.5rem 1rem; /* 24px 16px */
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  color: var(--color-highlight);
  text-align: left;
}

.accordion__title {
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
}

.accordion__icon {
  position: relative;
  flex-shrink: 0;
  width: 1.5rem; /* 24px */
  height: 1.5rem;
}

.accordion__icon::before,
.accordion__icon::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  background-color: var(--color-highlight);
  transition: transform var(--transition-fast);
}

.accordion__icon::before {
  width: 1rem;
  height: 2px;
  transform: translate(-50%, -50%);
}

.accordion__icon::after {
  width: 2px;
  height: 1rem;
  transform: translate(-50%, -50%);
}

.accordion__trigger[aria-expanded="true"] .accordion__icon::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.accordion__panel {
  overflow: hidden;
  transition: max-height var(--transition-base);
}

.accordion__body {
  display: flex;
  flex-direction: column;
  gap: 2rem;
  padding: 0 2rem 2rem;
  color: var(--color-text);
}

.accordion__image {
  display: block;
  width: 100%;
  height: auto;
  max-width: 26rem; /* 416px — keeps the photo from dominating the panel */
  flex-shrink: 0;
}

.accordion__text {
  min-width: 0;
  flex: 1 1 20rem;
}

.accordion__text p {
  margin: 0 0 var(--space-3);
}

.accordion__text p:last-child {
  margin-bottom: 0;
}

.accordion__text ul,
.accordion__text ol {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding-left: 1.25rem;
}

.accordion__text ul:last-child,
.accordion__text ol:last-child {
  margin-bottom: 0;
}

.accordion__text ul {
  list-style: disc; /* overrides the global ul/ol reset (see "2. Reset") */
}

.accordion__text ol {
  list-style: decimal; /* Bone Grafting's numbered procedure steps */
}

.accordion__text blockquote {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-4);
  border-left: 3px solid var(--color-highlight);
  font-style: italic;
  color: var(--color-text-muted);
}

.accordion__text blockquote cite {
  font-style: normal;
  color: var(--color-highlight);
}

.accordion__text strong {
  color: var(--palette-neutral-white);
}

@media (min-width: 48rem) {
  .accordion__body {
    flex-direction: row;
    align-items: flex-start;
  }

  .accordion__image {
    width: 26rem;
  }
}

@media (max-width: 47.9375rem) { /* 767px */
  .about-doctor__grid {
    flex-direction: column;
  }

  .about-doctor__photo,
  .about-doctor__content {
    flex: none;
    max-width: none;
    width: 100%;
  }

  .about-doctor__content {
    padding-top: 0;
  }
}

/* --------------------------------------------------------------------------
   18. Fastbraces — "What are Fastbraces®?" (14152:2308) and "Townie Choice
   Award" (14154:2605). Both are one-off two-column intro layouts, mirror
   images of each other (photo-left/content-right, then content-left/
   photo-right) — kept as separate classes rather than a single reversible
   component since neither is reused elsewhere yet. The gallery section
   further down this page (14154:2624) reuses the shared results partial
   (see views/partials/results.php and index.php's per-route 'results'
   override) instead of a fastbraces-specific class, since it's the exact
   same before/after-slider layout as home's Results section already
   built — only the heading/CTA copy differs.
   -------------------------------------------------------------------------- */

.fastbraces-intro {
  /* Mobile-first (below 768px): the photo is the first stacked item, so
     it gets real top padding instead of sitting flush at the very top
     edge — flush-against-the-hero is a two-column-only effect, same as
     .doctor. Removed again at 768px+ below, where .fastbraces-intro__content's
     own padding-top takes over. */
  padding-top: clamp(2.5rem, 8vw, 5rem);
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
}

@media (min-width: 48rem) {
  .fastbraces-intro {
    padding-top: 0;
  }
}

/* Mobile-first: single stacked column below 768px, explicit two-column
   breakpoint at 768px+ (see min-width override below) — same approach
   as .doctor__grid. */
.fastbraces-intro__grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(2rem, 5vw, 6rem); /* 32-96px, fluid — was a flat 96px */
}

@media (min-width: 48rem) {
  .fastbraces-intro__grid {
    flex-direction: row;
  }
}

.fastbraces-intro__photo {
  margin: 0;
  width: 100%;
  /* 550/733.3125, node 14152:2309 — the source photo is landscape
     (1200x802), so without this it renders far shorter/wider than
     Figma's portrait frame; object-fit: cover below does the cropping.
     Applied at all widths, unlike the flex sizing below, since the crop
     itself isn't a two-column-only concern. */
  aspect-ratio: 550 / 733.3125;
  overflow: hidden;
}

@media (min-width: 48rem) {
  .fastbraces-intro__photo {
    flex: 1 1 20rem;
    max-width: 34.375rem; /* 550px */
    width: auto;
  }
}

.fastbraces-intro__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 32% 20%; /* keeps the face/braces centered, not the empty background or gesturing hands */
}

.fastbraces-intro__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem; /* 32px */
  width: 100%;
}

@media (min-width: 48rem) {
  .fastbraces-intro__content {
    flex: 2 1 30rem;
    max-width: 48.125rem; /* 770px */
    width: auto;
    padding-top: clamp(2.5rem, 5vw, 7.5rem); /* 120px at desktop (node 14152:2311 pt-120), fluid so it shrinks on narrower screens — same clamp as .cta__grid's gap */
  }
}

.fastbraces-intro__heading {
  margin: 0;
}

.fastbraces-intro__heading-accent {
  color: var(--color-highlight);
}

.fastbraces-intro__body {
  display: flex;
  flex-direction: column;
  gap: 2rem; /* 32px */
}

.fastbraces-intro__body p {
  margin: 0;
}

.fastbraces-intro__lead {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-semibold);
  font-size: 1.6875rem; /* 27px, Desktop/H5 */
  line-height: 1.2;
  color: var(--palette-neutral-white);
}

.fastbraces-award {
  /* Content is the first stacked item here (not the photo — see the
     award__grid markup order), so unlike .fastbraces-intro there's no
     flush-hero concern to correct for; only the grid direction, gap, and
     content padding-top below need the responsive treatment. */
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px */
}

.fastbraces-award__grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(2rem, 5vw, 6rem); /* 32-96px, fluid — was a flat 96px */
}

@media (min-width: 48rem) {
  .fastbraces-award__grid {
    flex-direction: row;
  }
}

.fastbraces-award__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2rem; /* 32px */
  width: 100%;
}

@media (min-width: 48rem) {
  .fastbraces-award__content {
    flex: 1 1 20rem;
    width: auto;
    padding-top: clamp(2.5rem, 5vw, 7.5rem); /* 120px at desktop (node 14154:2608 pt-120), fluid so it shrinks on narrower screens — same clamp as .cta__grid's gap */
  }
}

.fastbraces-award__heading {
  margin: 0;
}

.fastbraces-award__heading-accent {
  color: var(--color-highlight);
}

.fastbraces-award__body {
  display: flex;
  flex-direction: column;
  gap: 2rem; /* 32px */
}

.fastbraces-award__body p {
  margin: 0;
}

.fastbraces-award__highlight {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-light);
  font-size: 1.3125rem; /* 21px, Desktop/H6 */
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--color-highlight);
}

.fastbraces-award__photo {
  margin: 0;
  width: 100%;
  border: 4px solid var(--color-highlight);
  /* This section's photo sits on the right at desktop (content comes
     first in the markup, order: 0 by default) — but when stacked below
     768px, the image column should always display first regardless of
     which side it's on at desktop. order: -1 here does that visually
     without reordering the DOM (keeps content first for a11y/SEO). */
  order: -1;
}

@media (min-width: 48rem) {
  .fastbraces-award__photo {
    flex: 1 1 20rem;
    max-width: 41.25rem; /* 660px */
    width: auto;
    order: 0; /* restore natural DOM order — content-left, photo-right */
  }
}

.fastbraces-award__photo img {
  display: block;
  width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------------
   19. Restorative Dentistry — no Figma mockup exists for this page (see
   views/services-restorative-dentistry.php's own doc comment); a compact
   intro heading above the shared .accordion component (built for About's
   specialty credentials, section 17) listing the page's 7 treatments.
   -------------------------------------------------------------------------- */

.restorative-intro {
  padding-top: clamp(2.5rem, 8vw, 5rem);
  padding-bottom: clamp(1.25rem, 3vw, 2rem); /* fluid, was a flat 32px */
}

.restorative-intro__content {
  display: flex;
  flex-direction: column;
  gap: var(--space-4); /* 24px */
  max-width: 48rem;
}

.restorative-intro__heading {
  margin: 0;
}

.restorative-intro__heading-accent {
  color: var(--color-highlight);
}

/* --------------------------------------------------------------------------
   20. Pricing — no Figma mockup exists for this page either (same
   situation as Restorative Dentistry, section 19). A plain scannable
   price list grouped into 3 categories (Family & General, Cosmetic &
   Implant, Orthodontic) — a price list is compared at a glance, not read
   one item at a time, so it stays fully visible rather than behind the
   accordion pattern used elsewhere.
   -------------------------------------------------------------------------- */

/* Secondary jump-nav — added once the 3-category price list got long
   enough that scrolling past two categories to reach a third became a
   real ask. Sticks just below the fixed header (same --header-height
   offset .cta's #book anchor already uses below) so it stays reachable
   the whole time a visitor is scrolling the list, not just at the top
   of the page. */
.pricing-nav {
  position: sticky;
  top: var(--header-height);
  z-index: 2;
  background-color: var(--color-background);
  border-bottom: 1px solid var(--color-border);
}

body.has-dev-banner .pricing-nav {
  top: calc(var(--header-height) + var(--dev-banner-height));
}

.pricing-nav__list {
  display: flex;
  /* "safe" falls back to start-aligned once the row overflows (narrow
     screens) — plain `center` would centre the flex content past the
     scroll container's left edge, making the first pill unreachable
     since scrollLeft can't go negative. */
  justify-content: safe center;
  gap: var(--space-3); /* 16px */
  overflow-x: auto; /* 3 pills always fit today, but shrinks gracefully
                        if a 4th category is ever added on a narrow screen */
  padding-block: var(--space-3); /* 16px */
}

.pricing-nav__link {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4); /* 8px 24px */
  font-family: var(--font-mono);
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-body-sm);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.pricing-nav__link:hover,
.pricing-nav__link:focus-visible {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--palette-neutral-dark);
}

.pricing {
  padding-top: clamp(2.5rem, 8vw, 5rem);
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px — same
                              one-off value used elsewhere (Testimonials, CTA, Doctor) */
}

/* Lands each category below both the fixed header AND the sticky
   .pricing-nav above once its own height is included — matching the
   scroll-margin-top pattern .cta's #book anchor uses for the header
   alone, just with the nav's own rendered height (~4.75rem: two lots
   of --space-3 padding plus its pill row, measured at ~70px, plus a
   little breathing room) added on top. */
.pricing__category {
  scroll-margin-top: calc(var(--header-height) + 4.75rem);
}

body.has-dev-banner .pricing__category {
  scroll-margin-top: calc(var(--header-height) + var(--dev-banner-height) + 4.75rem);
}

/* Narrower than the shared .container's 1416px max — a price list's
   descriptions read as long, uncomfortable line lengths at the full site
   width, unlike sections built around a photo/grid that fill the space. */
.pricing .container {
  max-width: 60rem; /* 960px */
}

.pricing__category {
  display: flex;
  flex-direction: column;
  gap: var(--space-5); /* 40px */
}

.pricing__category + .pricing__category {
  margin-top: 5rem; /* 80px between category groups */
}

.pricing__category-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
}

.pricing__category-heading {
  margin: 0;
}

.pricing__category-note {
  margin: 0;
  font-style: italic;
  color: var(--color-highlight);
}

.pricing__list {
  display: flex;
  flex-direction: column;
}

.pricing__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
  padding-block: var(--space-4); /* 24px */
  border-bottom: 1px solid var(--color-border);
}

.pricing__item:first-child {
  border-top: 1px solid var(--color-border);
}

.pricing__item-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3); /* 16px */
}

.pricing__item-name {
  margin: 0;
  font-size: var(--text-body);
  font-weight: var(--font-weight-bold);
  color: var(--palette-neutral-white);
}

.pricing__item-price {
  flex-shrink: 0;
  color: var(--color-highlight);
  white-space: nowrap;
}

.pricing__item-desc {
  margin: 0;
  color: var(--color-text);
}

.pricing__item-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2); /* 8px */
  margin: 0;
  padding-left: 1.25rem;
  list-style: disc; /* overrides the global ul/ol reset (see "2. Reset") */
  color: var(--color-text);
}

.pricing__footnote {
  margin: 5rem 0 0; /* 80px — same rhythm as the category gap above */
  text-align: center;
  font-style: italic;
  color: var(--color-text-muted);
}

/* --------------------------------------------------------------------------
   21. Technology — no Figma mockup exists for this page either (same
   situation as Restorative Dentistry and Pricing). Alternating photo/
   content rows for the page's 7 technologies (views/about-technology.php
   has the full reasoning) — the photo is always first in the markup, so
   it's what shows first once a row stacks on mobile; flex-direction
   (row vs. row-reverse) decides which side it sits on at desktop.
   -------------------------------------------------------------------------- */

.tech-features {
  padding-top: clamp(2.5rem, 8vw, 5rem);
  padding-bottom: clamp(2.5rem, 8vw, 7.5rem); /* fluid, was a flat 120px — same one-off value used elsewhere */
}

.tech-features__list {
  display: flex;
  flex-direction: column;
  gap: clamp(3rem, 8vw, 6rem);
}

.tech-feature {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
}

@media (min-width: 48rem) {
  .tech-feature {
    flex-direction: row;
  }

  .tech-feature--reverse {
    flex-direction: row-reverse;
  }
}

.tech-feature__photo {
  margin: 0;
  width: 100%;
}

@media (min-width: 48rem) {
  .tech-feature__photo {
    flex: 1 1 20rem;
    max-width: 34.375rem; /* 550px, same photo-column width used elsewhere (Doctor, Fastbraces) */
    width: auto;
  }
}

/* No aspect-ratio/object-fit crop — these are product photos and
   software screenshots at varying natural proportions; cropping them
   would cut off real content (screenshots, device labels), unlike the
   portrait headshot on the Fastbraces intro. */
.tech-feature__photo img {
  display: block;
  width: 100%;
  height: auto;
}

.tech-feature__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4); /* 24px */
  width: 100%;
}

@media (min-width: 48rem) {
  .tech-feature__content {
    flex: 1 1 20rem;
    width: auto;
  }
}

.tech-feature__title {
  margin: 0;
}

.tech-feature__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3); /* 16px */
}

.tech-feature__body p {
  margin: 0;
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   22. Smile Gallery — before/after comparison grid
   Responsive card grid: 1 col ≤600px, 2 cols 601–1024px, 3 cols 1025px+.
   Each card is a self-contained before/after slider reusing the same
   clip-path + pointer-drag pattern as the home results section (§16).
   -------------------------------------------------------------------------- */

.gallery-section {
  padding-block: clamp(2rem, 6vw, 4rem) clamp(2.5rem, 7vw, 5rem); /* fluid, was a flat 64px/80px */
}

.gallery-section + .gallery-section {
  padding-top: 0;
}

.gallery-section__header {
  text-align: center;
  margin-bottom: 3rem;
}

.gallery-section__title {
  margin: 0;
}

.gallery-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 37.5625rem) { /* 601px */
  .gallery-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 64.0625rem) { /* 1025px */
  .gallery-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.gallery-card {
  position: relative;
  margin: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.gallery-card__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  background-color: var(--palette-neutral-dark);
  cursor: ew-resize;
  touch-action: none;
  user-select: none;
}

.gallery-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

.gallery-card__img--before {
  position: absolute;
  inset: 0;
}

.gallery-card__after {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 0 var(--position));
}

.gallery-card__label {
  position: absolute;
  z-index: 3;
  bottom: 0.625rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--palette-neutral-white);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

.gallery-card__label--before {
  left: 0.625rem;
}

.gallery-card__label--after {
  right: 0.625rem;
}

.gallery-card__divider {
  position: absolute;
  z-index: 4;
  top: 0;
  bottom: 0;
  left: var(--position);
  width: 2px;
  background: rgba(255, 255, 255, 0.9);
  transform: translateX(-50%);
  pointer-events: none;
}

.gallery-card__divider > span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2rem;
  height: 2rem;
  border: 2px solid var(--palette-neutral-white);
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.14);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  transform: translate(-50%, -50%);
}

.gallery-card__divider > span::before,
.gallery-card__divider > span::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0.45rem;
  height: 0.45rem;
  border-top: 2px solid var(--palette-neutral-white);
  border-left: 2px solid var(--palette-neutral-white);
}

.gallery-card__divider > span::before {
  left: 0.4rem;
  transform: translateY(-50%) rotate(-45deg);
}

.gallery-card__divider > span::after {
  right: 0.4rem;
  transform: translateY(-50%) rotate(135deg);
}

.gallery-card__range {
  position: absolute;
  z-index: 5;
  left: 50%;
  bottom: 0.5rem;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.gallery-card:focus-within {
  outline: 3px solid var(--color-highlight);
  outline-offset: 4px;
}

/* --------------------------------------------------------------------------
   23. WhatsApp float — site-wide floating button (Figma node 14151:1034)
   Rendered unconditionally by views/layout.php (views/partials/
   whatsapp-float.php), so it's fixed over every page regardless of scroll
   position. --z-sticky (200) sits below the fixed header (--z-header,
   300) so an open mobile nav overlay still wins, but above ordinary page
   content.
   -------------------------------------------------------------------------- */

.whatsapp-float {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: var(--z-sticky);
  display: block;
  width: 3.75rem; /* 60px, node 14151:1034 */
  height: 3.75rem;
  border-radius: var(--radius-full);
  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.35));
  transition: transform var(--transition-fast);
}

.whatsapp-float svg {
  display: block;
  width: 100%;
  height: 100%;
}

.whatsapp-float:hover,
.whatsapp-float:focus-visible {
  transform: scale(1.08);
}

.whatsapp-float:focus-visible {
  outline: 3px solid var(--color-highlight);
  outline-offset: 4px;
}

/* --------------------------------------------------------------------------
   24. Privacy Policy — long-form legal text
   -------------------------------------------------------------------------- */

.privacy-policy {
  padding: clamp(3rem, 5vw, 5rem) 0;
}

.privacy-policy__content {
  max-width: 48rem;
  margin-inline: auto;
}

.privacy-policy__content h2 {
  margin-top: var(--space-6);
  margin-bottom: var(--space-3);
}

.privacy-policy__content p {
  margin-bottom: var(--space-3);
  line-height: 1.8;
}

.privacy-policy__content ul {
  margin-bottom: var(--space-3);
  padding-left: var(--space-4);
  list-style: disc;
}

.privacy-policy__content ol {
  margin-bottom: var(--space-3);
  padding-left: var(--space-4);
  list-style: decimal;
}

.privacy-policy__content li {
  margin-bottom: var(--space-2);
  line-height: 1.8;
}

.privacy-policy__content a {
  color: var(--color-accent);
}

.privacy-policy__content a:hover {
  color: var(--color-highlight);
}

/* --------------------------------------------------------------------------
   25. Dev tools
   Only ever rendered when config/env.php's "env" is "development" — see
   views/partials/header.php. Structurally can't leak into production.
   -------------------------------------------------------------------------- */

.dev-banner {
  position: fixed;
  top: 0;
  left: 0;
  z-index: calc(var(--z-header) + 1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--dev-banner-height);
  background-color: var(--palette-neutral-black);
  text-align: center;
  padding: var(--space-1) var(--space-2);
}

.dev-banner a {
  color: var(--palette-neutral-white);
}

.dev-banner a:hover {
  color: var(--color-highlight);
}
