@layer components {
  /* Header */
  .site-header {
    background: color-mix(in srgb, var(--color-cream-soft) 92%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--color-line);
    position: sticky; top: 0; z-index: 10;
  }
  .header-row {
    display: flex; align-items: center; justify-content: space-between;
    /* padding-BLOCK only. This element is `.wrap .header-row`, and .wrap
       supplies the horizontal gutter (24px, 32px from 768px). A `padding:
       Ypx 0` shorthand here would reset padding-inline to 0 and win, because
       @layer components outranks @layer base — which is exactly the bug that
       left the navbar edge-to-edge below 1280px while the footer sat on the
       gutter. Keep the gutter in one place: .wrap. */
    padding-block: var(--space-4);
    gap: var(--space-4);
  }
  .brand {
    display: flex; align-items: center; gap: 0.625rem;   /* 10px; no --space token */
    font-family: var(--font-serif); font-size: 1.125rem; color: var(--color-ink);
    text-decoration: none; line-height: 1.1; letter-spacing: 0.01em;
  }
  .brand-mark { width: 34px; height: auto; flex: none; }
  .brand-text { display: flex; flex-direction: column; }
  .brand-name { white-space: nowrap; }
  /* Subtitle hidden on phones (canvas 1a omits it); appears from the tablet tier. */
  .brand small { display: none; color: var(--color-ink-muted); font-size: 0.75rem; line-height: 1.4; }
  @media (min-width: 768px) {
    .brand { gap: var(--space-3); }        /* 12px */
    .brand-mark { width: 40px; }
    .brand small { display: block; }
  }
  @media (min-width: 880px) {
    .brand { gap: 0.875rem; }              /* 14px; no --space token */
    .brand-mark { width: 44px; }
  }
  .nav-list { display: flex; gap: var(--space-5); list-style: none; padding: 0; margin: 0; }
  .nav-list a { color: var(--color-ink); text-decoration: none; padding: var(--space-2); }
  .nav-list a:hover, .nav-list a[aria-current="page"] { color: var(--color-primary-deep); }
  .header-tools { display: flex; gap: var(--space-2); align-items: center; }

  /* W2 — opt header links + brand out of the global <a> ink-draw underline.
     base.css paints background-image: linear-gradient(currentColor, currentColor)
     on every <a>; the 0%-at-rest toggle is scoped to main/footer only. Header
     links and the wordmark were carrying the static 1px line. */
  .site-header a,
  .brand {
    background-image: none;
    padding-bottom: 0;
  }
  .site-header a:hover,
  .site-header a:focus-visible,
  .brand:hover,
  .brand:focus-visible {
    background-size: 0% 1px;
  }

  /* W6.2 — hamburger nav.
     Mobile-first: toggle visible below 768px, panel collapsed; nav.js opens
     it via the .is-open class. Above 768px the toggle hides and the nav
     reverts to the original horizontal flex row. Anchor for the absolute
     panel: .site-header is already position: sticky (line ~7). */
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;            /* 44×44 — generous touch target */
    padding: 0;
    background: transparent;
    border: 0;
    color: var(--color-ink);
    cursor: pointer;
    border-radius: var(--radius-1);
    /* Hug the viewport edge below 768px. The button is 44px wide around a
       1.5rem glyph, so it already carries ~10px of its own inset; sitting it
       on the 24px .wrap gutter as well pushed the glyph to ~34px, visibly
       further in than everything below it. Cancel the gutter here and let the
       button's own padding be the optical inset. The touch target keeps its
       full 44px — this moves the box, it does not shrink it. Reset at 768px,
       where the tablet header keeps the full gutter. */
    margin-inline-start: calc(var(--space-6) * -1);
  }
  @media (min-width: 768px) { .nav-toggle { margin-inline-start: 0; } }
  .nav-toggle:focus-visible { outline-offset: 2px; }
  .nav-toggle svg {
    width: 1.5rem;
    height: 1.5rem;
    stroke: currentColor;
    stroke-width: 1.2;
    stroke-linecap: round;
    fill: none;
  }
  .nav-toggle svg .line-top,
  .nav-toggle svg .line-bot {
    transition: transform var(--dur-2) var(--ease-paper);
    transform-origin: center;
  }
  /* Crossfade two strokes into an X when open — paper-and-ink idiom */
  .nav-toggle[aria-expanded="true"] svg .line-top {
    transform: translateY(3px) rotate(45deg);
  }
  .nav-toggle[aria-expanded="true"] svg .line-bot {
    transform: translateY(-3px) rotate(-45deg);
  }
  @media (prefers-reduced-motion: reduce) {
    .nav-toggle svg .line-top,
    .nav-toggle svg .line-bot { transition: none; }
  }

  /* Nav panel — hidden by default below 768px */
  #primary-nav {
    display: none;
  }
  #primary-nav.is-open {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-cream-soft);
    border-bottom: 1px solid var(--color-line);
    padding: var(--space-4) var(--space-6);
    box-shadow: var(--shadow-1);
    z-index: 12;
  }
  #primary-nav.is-open .nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  #primary-nav.is-open .nav-list a {
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid color-mix(in srgb, var(--color-line) 60%, transparent);
  }
  #primary-nav.is-open .nav-list li:last-child a { border-bottom: 0; }

  /* Locale + text-size controls exist twice in the markup: once in
     .header-tools (desktop) and once in #primary-nav .nav-tools (mobile
     menu). Only one set is visible per breakpoint — i18n.js/a11y.js bind
     both, and aria-pressed stays in sync across the pair. */
  .header-tools .locale-toggle,
  .header-tools .fs-toggle { display: none; }
  #primary-nav .nav-tools {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding: var(--space-4) var(--space-2) var(--space-1);
    border-top: 1px solid color-mix(in srgb, var(--color-line) 60%, transparent);
  }

  /* Below 880px the toggle sits first in the row; the brand hugs it on the
     left while the Book CTA keeps the right edge. */
  .brand { margin-inline-end: auto; }

  /* Make the header-row the absolute anchor for the panel */
  .header-row { position: relative; }

  /* Above 880px: revert to horizontal layout, hide the toggle. The
     hamburger covers phone landscape AND narrow tablets (768-879) where
     the brand + 5 nav links + 3 header-tools controls don't all fit. */
  @media (min-width: 880px) {
    .nav-toggle { display: none; }
    .brand { margin-inline-end: 0; }
    .header-tools .locale-toggle,
    .header-tools .fs-toggle { display: inline-flex; }
    #primary-nav .nav-tools { display: none; }
    #primary-nav {
      display: block;
      position: static;
      padding: 0;
      border: 0;
      box-shadow: none;
      background: transparent;
    }
    #primary-nav .nav-list {
      flex-direction: row;
      gap: var(--space-5);
    }
    #primary-nav .nav-list a {
      padding: var(--space-2);
      border-bottom: 0;
    }
  }

  /* All three header-tools controls (locale-toggle, fs-toggle, Book a session)
     share a 40px height + pill radius for a consistent right-side rhythm.
     Vertical centering comes from .header-tools (display: flex, align-items: center). */
  .locale-toggle, .fs-toggle {
    background: transparent;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-pill);
    font: 500 0.875rem var(--font-sans);
    cursor: pointer;
    height: 2.5rem;            /* 40px */
    display: inline-flex;
    align-items: center;
    box-sizing: border-box;
  }
  .locale-toggle { padding: 4px; gap: 2px; }
  .fs-toggle {
    padding: 0 var(--space-3);
    gap: var(--space-1);
    color: var(--color-ink);
    min-block-size: 2.5rem;
  }
  .fs-toggle-letter { font: 600 0.9375rem var(--font-sans); }
  .fs-toggle [data-icon] { width: 0.875rem; height: 0.875rem; }
  .locale-toggle button {
    background: transparent;
    border: none;
    padding: 0 var(--space-3);
    cursor: pointer;
    color: var(--color-ink);
    border-radius: var(--radius-pill);
    font: inherit;
    align-self: stretch;        /* fill wrapper content-box so the pill insets evenly */
    min-block-size: 0;          /* override W6.5 floor — wrapper provides the 40px touch target */
    display: inline-flex;
    align-items: center;
  }
  .locale-toggle [aria-pressed="true"] {
    background: var(--color-primary-deep);
    color: var(--color-cream-soft);
  }

  /* BM-DEFERRED — Bahasa Malaysia is drafted but unreviewed, so the site serves
     English only and the EN/BM toggle is not offered.

     Hidden in CSS rather than removed from markup for three reasons: it covers all
     46 pages that carry the toggle (8 non-blog production pages + all 38 generated
     post-*.html) plus content/blog/_post.html.erb without a single markup edit;
     there is no flash of the toggle before the ES module executes; and display:none
     takes both buttons out of the tab order AND the accessibility tree, so the
     axe-core 0-violation ratchet is unaffected.

     Placement matters: this must stay AFTER the `.locale-toggle, .fs-toggle`
     display:inline-flex rule above, which has the same specificity.

     To ship BM: delete this rule and flip LOCALES_ENABLED in assets/js/i18n.js. */
  .locale-toggle { display: none; }
  /* The desktop copy needs the two-class form: the @media (min-width: 880px)
     block above sets `.header-tools .locale-toggle` to display inline-flex at
     specificity (0,2,0), which outranks the (0,1,0) rule regardless of source
     order. A media query adds no specificity, so this later (0,2,0) rule wins at
     every width. Delete it alongside the rule above when BM ships. */
  .header-tools .locale-toggle { display: none; }

  /* Book a session in header-tools matches the toggle height. */
  .header-tools .btn--primary {
    height: 2.5rem;
    padding: 0 1.125rem;      /* 18px on phones; no --space token */
    font-size: 0.875rem;
    white-space: nowrap;
    flex: none;
  }
  @media (min-width: 768px) {
    .header-tools .btn--primary { padding: 0 var(--space-5); }   /* 20px */
  }
  /* Responsive CTA label — short "Book Now" on phones, "Book a session" from
     768px. display:none removes the hidden span from the accessibility tree,
     so screen readers announce exactly one label. No JS involved. */
  .header-cta .header-cta-label--long { display: none; }
  @media (min-width: 768px) {
    .header-cta .header-cta-label--short { display: none; }
    .header-cta .header-cta-label--long { display: inline; }
  }

  /* Buttons */
  .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
    padding: var(--space-3) var(--space-6);
    border-radius: var(--radius-pill);
    border: none;
    font: 600 1rem var(--font-sans);
    cursor: pointer;
    text-decoration: none;
    transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
  }
  .btn:active { transform: scale(0.97); transition: transform 80ms var(--ease-out); }
  .btn--primary { background: var(--color-primary-deep); color: var(--color-cream-soft); box-shadow: 0 10px 26px -10px var(--color-primary); }
  .btn--primary:hover { transform: translateY(-1px); background: color-mix(in srgb, var(--color-primary-deep) 88%, #000); }
  .btn--secondary { background: transparent; color: var(--color-primary-deep); border: 1.5px solid var(--color-primary); }
  .btn--secondary:hover { background: var(--color-primary-deep); color: var(--color-cream-soft); }
  /* Icon-only button — square 40px, transparent ghost. Used in chatbot panel. */
  .btn--icon {
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    background: transparent;
    color: var(--color-ink);
    border-radius: var(--radius-1);
  }
  .btn--icon:hover { background: var(--color-line); }
  .btn--icon [data-icon] { width: 1.25rem; height: 1.25rem; }
  .chatbot-header-actions { display: inline-flex; gap: var(--space-1); }
  .btn--ghost { background: transparent; color: var(--color-ink-soft); }
  .btn--ghost:hover { color: var(--color-ink); }

  /* Chip / pill */
  .chip-pill {
    display: inline-flex; padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill); border: 1.5px solid var(--color-line);
    background: var(--color-cream-soft); color: var(--color-ink-soft); font: 500 0.9rem var(--font-sans); cursor: pointer;
    transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform 120ms var(--ease);
  }
  .chip-pill[aria-pressed="true"] { background: var(--color-primary-deep); color: var(--color-cream-soft); border-color: var(--color-primary-deep); }
  /* Personalization chips are radio-in-label (no aria-pressed) — light up the
     selected one via :has() so the choice reads clearly. */
  .chip-pill:has(input:checked) { background: var(--color-primary-deep); color: var(--color-cream-soft); border-color: var(--color-primary-deep); }
  .chip-pill:active { transform: scale(0.95); }

  /* Card */
  .card { background: var(--color-cream-soft); border-radius: var(--radius-3); padding: var(--space-8); box-shadow: var(--shadow-1); }
  .card h3 { margin-bottom: var(--space-3); }
  a.card, .card[href] {
    display: block;
    color: inherit;
    text-decoration: none;
    transition: transform var(--dur-2) var(--ease-paper), box-shadow var(--dur-2) var(--ease-paper);
    will-change: transform;
  }
  .card-inner { display: block; }

  @media (prefers-reduced-motion: no-preference) {
    a.card:hover, .card[href]:hover {
      transform: translateY(-2px) rotate(-0.4deg);
      box-shadow: -6px 8px 16px -8px rgba(57, 51, 80, 0.22);
    }
    .locale-toggle [aria-pressed="true"] {
      animation: page-turn-slide var(--dur-2) var(--ease-paper) both;
    }
  }

  /* Hero */
  .hero { padding: var(--space-24) 0 var(--space-20); background: linear-gradient(180deg, var(--color-cream) 0%, var(--color-cream-soft) 100%); }
  .hero .eyebrow { font: 600 var(--type-eyebrow) var(--font-sans); letter-spacing: var(--tracking-loose); text-transform: uppercase; color: var(--color-primary-deep); margin: 0 0 var(--space-4); }
  .hero .lede { font-size: 1.25rem; color: var(--color-ink-soft); max-width: var(--content-max); margin-bottom: var(--space-8); }

  /* Hero spacing — give the considered-photo / yt-embed breathing room
     below the CTA so the small primary button isn't visually crushed by
     the dominant image (huashu followup, 2026-06-11). */
  .hero .cta-row + .anchor-photo,
  .hero .cta-row + .yt-embed,
  .hero .anchor-photo + .yt-embed {
    margin-top: var(--space-8);
  }

  /* Lavender import — decorative floating hero blobs (home only; markup lives
     in index.html). They sit behind hero content; the float animation is added
     in motion.css and disabled under prefers-reduced-motion. */
  .hero { position: relative; overflow: hidden; }
  .hero > .wrap { position: relative; z-index: 1; }
  .hero-decor { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
  .hero-blob { position: absolute; border-radius: 50%; }
  .hero-blob--1 { width: 300px; height: 300px; background: var(--color-green); opacity: .4; top: -70px; right: 6%; }
  .hero-blob--2 { width: 200px; height: 200px; background: var(--color-primary-soft); opacity: .55; bottom: -40px; left: -30px; }
  .hero-blob--3 { width: 120px; height: 120px; background: var(--color-blue); opacity: .35; top: 40%; right: 42%; }
  .hero-blob--dot1 { width: 16px; height: 16px; background: var(--color-sun); top: 120px; left: 14%; }
  .hero-blob--dot2 { width: 10px; height: 10px; background: var(--color-pink); top: 220px; right: 30%; }

  /* Lavender import — multicolor service cards on the home "How we can help"
     grid. Each card gets a pastel tint + a coloured icon chip by position.
     Scoped to #home-services so blog / recommended cards stay on white. */
  #home-services .card:nth-child(1) { background: var(--svc-1); }
  #home-services .card:nth-child(2) { background: var(--svc-2); }
  #home-services .card:nth-child(3) { background: var(--svc-3); }
  #home-services .card:nth-child(4) { background: var(--svc-4); }
  #home-services .card:nth-child(5) { background: var(--svc-5); }
  #home-services .card:nth-child(6) { background: var(--svc-6); }
  #home-services .card-chip {
    display: block; width: 40px; height: 40px;
    border-radius: 14px; margin-bottom: var(--space-4);
    background: var(--color-primary-deep);
  }
  #home-services .card:nth-child(1) .card-chip { background: var(--svc-d-1); }
  #home-services .card:nth-child(2) .card-chip { background: var(--svc-d-2); }
  #home-services .card:nth-child(3) .card-chip { background: var(--svc-d-3); }
  #home-services .card:nth-child(4) .card-chip { background: var(--svc-d-4); }
  #home-services .card:nth-child(5) .card-chip { background: var(--svc-d-5); }
  #home-services .card:nth-child(6) .card-chip { background: var(--svc-d-6); }

  /* ── Lavender import — 45° diagonal stripe placeholders ─────────────────
     The design's "photo coming here" idiom: a palette tint striped against the
     white surface. Mixed with the pastel anchor art (which stays on the about/
     services heroes + service moods) so placeholders read as intentional, not
     broken. Colour set via --stripe / a modifier. */
  .stripe-fill {
    --stripe: var(--color-primary-soft);
    background: repeating-linear-gradient(135deg,
      var(--stripe), var(--stripe) 16px,
      var(--color-cream-soft) 16px, var(--color-cream-soft) 32px);
    display: flex; align-items: center; justify-content: center;
  }
  .stripe-fill--primary { --stripe: var(--color-primary-soft); }
  .stripe-fill--blue    { --stripe: var(--color-blue); }
  .stripe-fill--green   { --stripe: var(--color-green); }
  .stripe-fill--pink    { --stripe: var(--color-pink); }
  .stripe-label {
    font-family: ui-monospace, monospace; font-size: 0.74rem; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--color-ink-muted);
    background: var(--color-cream); padding: 9px 15px; border-radius: var(--radius-pill);
  }

  /* Two-column hero (design layout): copy left, media right; stacks below the
     880px nav breakpoint. */
  .hero-grid {
    display: grid; grid-template-columns: 1.05fr 0.95fr;
    gap: var(--space-12); align-items: center;
  }
  @media (max-width: 879px) {
    .hero-grid { grid-template-columns: 1fr; gap: var(--space-8); }
  }
  .hero-media { display: flex; flex-direction: column; gap: var(--space-4); margin: 0; }
  .media-card {
    margin: 0; border-radius: var(--radius-3); overflow: hidden;
    background: var(--color-cream-soft); border: 1px solid var(--color-line);
    box-shadow: var(--shadow-3);
  }
  .media-card .stripe-fill { aspect-ratio: 4/3; }
  .media-card img { width: 100%; height: auto; display: block; }
  .media-card figcaption { padding: 14px 18px; font-size: 0.84rem; color: var(--color-ink-muted); margin: 0; }

  /* ── Banner heroes ────────────────────────────────────────────────────────
     Interior pages (about, staff, services, blog, contact) carry a soft
     pastel header artwork as a full-bleed backdrop. Heading text rides inside
     a frosted `.hero-panel` so ink stays WCAG AA-readable no matter what sits
     under it (panel ~90% opaque over a light illustration → ink ≈ 10:1). The
     artwork per page is set on the body-class rules further down. */
  .hero--banner {
    background: none;                 /* drop the cream gradient from .hero */
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
  /* Frost the whole banner (not just the panel) on every page that carries
     one, to calm the busier artworks. A full-bleed cream wash + blur sits
     above the image and below the content panel (z-index:0 vs .hero > .wrap
     z-index:1). */
  .hero--banner::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    background: color-mix(in srgb, var(--color-cream) 55%, transparent);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
  }
  .hero-panel {
    max-width: var(--content-max);
    padding: var(--space-8) var(--space-10);
    border-radius: var(--radius-3);
    background: color-mix(in srgb, var(--color-cream-soft) 90%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid color-mix(in srgb, var(--color-line) 70%, transparent);
    box-shadow: var(--shadow-2);
  }
  .hero-panel > :last-child { margin-bottom: 0; }
  @media (max-width: 640px) {
    .hero-panel { padding: var(--space-6) var(--space-5); }
  }
  /* Anchor-photo captions inside a banner would otherwise float as muted
     italic over the mural (fails contrast). Give them the same frosted chip
     as the panel and lift the colour to ink-soft (5.6:1 worst case). */
  .hero--banner .anchor-photo figcaption {
    align-self: start;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--color-cream-soft) 90%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--color-ink-soft);
  }

  /* Blog cards carry a striped image top (design idiom), cycling palette tints. */
  .card--media { padding: 0; overflow: hidden; }
  .card--media .card-media { aspect-ratio: 16/10; display: block; }
  .card--media img.card-media { width: 100%; object-fit: cover; object-position: center top; background: var(--color-cream-soft); }
  .card--media .card-inner { padding: var(--space-6); }
  .grid-3 .card--media:nth-child(3n+1) .card-media { --stripe: var(--svc-1); }
  .grid-3 .card--media:nth-child(3n+2) .card-media { --stripe: var(--svc-2); }
  .grid-3 .card--media:nth-child(3n+3) .card-media { --stripe: var(--svc-3); }

  /* ── Home sections aligned to the Lavender reference ──────────────────── */

  /* Hero headline accent — the last word is wrapped by renderHome() so it
     colours correctly in both EN and BM regardless of word order. */
  .hero-accent { color: var(--color-primary-deep); }

  /* Visit us — styled opening-hours rows (label / value). */
  .hours-rows { display: flex; flex-direction: column; gap: var(--space-2); max-width: 22rem; margin-top: var(--space-5); }
  .hour-row {
    display: flex; justify-content: space-between; gap: var(--space-4);
    padding: 12px 18px; background: var(--color-cream-soft);
    border: 1px solid var(--color-line); border-radius: var(--radius-2);
  }
  .hour-row .hour-label { font-weight: 700; color: var(--color-ink); }
  .hour-row .hour-value { color: var(--color-ink-soft); }
  .visit-map { align-self: center; width: 100%; }
  .map-frame { display: block; width: 100%; aspect-ratio: 4/3; border: 0; }

  /* Click-to-load map facade (assets/js/map-embed.js). Holds the 4/3 box so
     swapping in the real iframe causes no layout shift, and loads no Google
     resource until the visitor presses "Load map". */
  .map-embed { width: 100%; aspect-ratio: 4/3; border-radius: var(--radius-3); overflow: hidden; }
  .map-embed .map-frame { height: 100%; aspect-ratio: auto; }
  .map-embed-facade {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 12px; height: 100%; padding: 24px; text-align: center;
    /* White ground keeps the secondary-button + note text comfortably above
       WCAG AA contrast (a lavender-soft ground dropped the button to 3.8:1);
       the soft inset border keeps the facade visually distinct from the card. */
    background: var(--color-cream-soft); color: var(--color-ink);
    box-shadow: inset 0 0 0 1px var(--color-primary-soft);
  }
  .contact-map-embed { margin-top: var(--space-6) !important; }
  .map-embed-facade [data-icon] { font-size: 1.6rem; color: var(--color-primary-deep); }
  .map-embed-note { margin: 0; max-width: 34ch; font-size: 0.84rem; color: var(--color-ink-soft); }

  /* Testimonial — centred pull-quote with an oversized opening mark. */
  .testimonial-block { text-align: center; margin-inline: auto; }
  .testimonial-mark { display: block; font-family: var(--font-serif); font-size: 3.4rem; line-height: 1; color: var(--color-primary); }
  .testimonial-quote {
    font-family: var(--font-serif); font-size: 1.5rem; line-height: 1.5;
    color: var(--color-ink); font-style: italic; margin: 0 0 var(--space-5); border: 0; padding: 0;
  }
  .testimonial-attr { font-weight: 700; color: var(--color-primary-deep); margin: 0; }

  /* What we do — three tinted numbered points. Number circles use the AA-safe
     violet (white on the pastel svc-d tints would fail contrast). */
  .wwd-points { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-5); }
  .wwd-point { display: flex; gap: var(--space-4); align-items: flex-start; border-radius: var(--radius-2); padding: 18px 22px; }
  .wwd-point:nth-child(1) { background: var(--svc-1); }
  .wwd-point:nth-child(2) { background: var(--svc-3); }
  .wwd-point:nth-child(3) { background: var(--svc-2); }
  .wwd-num {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
    background: var(--color-primary-deep); color: var(--color-cream-soft);
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.9rem;
  }
  .wwd-point p { margin: 0; color: var(--color-ink-soft); line-height: 1.55; }

  /* Staff — small gradient avatar circles (home featured cards). */
  .staff-avatar {
    /* Scale with the card: grows on wide 3-up desktop cards, floors at 96px
       when the grid stacks 1-up on mobile. */
    width: clamp(96px, 9vw, 132px); height: clamp(96px, 9vw, 132px); border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-serif); font-weight: 600; font-size: clamp(1.4rem, 2.6vw, 2rem);
    color: var(--color-ink); margin-bottom: var(--space-4);
    background: linear-gradient(135deg, var(--av-a), var(--color-primary-soft));
  }
  .staff-grid .staff-card:nth-child(3n+2) .staff-avatar { background: linear-gradient(135deg, var(--av-b), var(--color-blue)); }
  .staff-grid .staff-card:nth-child(3n+3) .staff-avatar { background: linear-gradient(135deg, var(--av-c), var(--color-green)); }
  /* Real (interim) headshot fills the avatar circle instead of initials;
     top-biased crop keeps faces in frame for portrait headshots. */
  .staff-avatar--img { object-fit: cover; object-position: center top; }

  /* Events — soft gradient band with the CTA on the right. */
  .events-band {
    background: linear-gradient(135deg, var(--color-primary-soft), var(--svc-3));
    border-radius: var(--radius-3); padding: 48px 44px;
    display: flex; flex-wrap: wrap; gap: var(--space-6);
    align-items: center; justify-content: space-between;
  }
  .events-copy { max-width: 38rem; }
  .events-band .section-eyebrow { color: var(--color-ink); }
  .events-band h2 { margin-bottom: var(--space-3); }

  /* Heroicons base — icons inherit text colour, size from font.
     [data-icon] is a placeholder span filled by icons.js with an SVG. */
  [data-icon] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25em;
    height: 1.25em;
    line-height: 1;
    color: currentColor;
  }
  [data-icon] svg {
    width: 100%;
    height: 100%;
    display: block;
  }

  /* W6.3 — mobile hero. Tighten line-height, padding, lede so first
     impression on a 360-414px viewport stays above (or near) the fold. */
  @media (max-width: 640px) {
    .hero { padding: var(--space-10) 0 var(--space-12); }
    .hero h1 { line-height: 1.1; }
    .hero .lede { font-size: 1.0625rem; max-width: 100%; margin-bottom: var(--space-6); }
    .hero .cta-row { gap: var(--space-2); }
  }
  .cta-row { display: flex; gap: var(--space-3); flex-wrap: wrap; }

  /* W6.4 — mobile grid tightening. Below 640px collapse to one column
     and reduce gap so vertical reading flows naturally. */
  @media (max-width: 640px) {
    .grid-3, .grid-2 { gap: var(--space-5); }
    .staff-grid { gap: var(--space-8); }
    .stat-grid { gap: var(--space-3); }
    .section { padding: var(--space-12) 0; }
  }

  /* W6.5 — WCAG 2.5.8 touch-target floor (24×24 CSS px minimum AA).
     Bump primary interactive controls to a comfortable 40+ so they're
     easy to tap on phones. Chip-pills stay slightly tighter (32px) to
     fit two-row layouts on narrow viewports. */
  .fs-toggle,
  .chatbot-launcher,
  .yt-embed .yt-play,
  .nav-toggle {
    min-block-size: 2.5rem;   /* 40px at default rem */
  }
  /* .locale-toggle button intentionally omitted: the 40px wrapper is the touch
     target; each button fills its width and stays ≥24px (WCAG 2.5.8 AA). */
  .nav-list a {
    min-block-size: 2.25rem;
    display: inline-flex;
    align-items: center;
  }
  .chip-pill {
    min-block-size: 2rem;
  }

  /* Sections */
  .section { padding: var(--space-20) 0; }
  .section--alt { background: var(--color-alt); }
  .section-eyebrow { font: 600 var(--type-eyebrow) var(--font-sans); letter-spacing: var(--tracking-loose); text-transform: uppercase; color: var(--color-ink-muted); margin: 0 0 var(--space-3); }
  .section-note { max-width: 52ch; margin: calc(-1 * var(--space-2)) 0 var(--space-4); color: var(--color-ink-soft); font-size: 0.9375rem; }

  /* Grids */
  .grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--space-6); }
  .grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-8); }

  /* Forms */
  .form-field { display: grid; gap: var(--space-2); margin-bottom: var(--space-5); }
  .form-field label { font: 600 0.9rem var(--font-sans); }
  .form-field input, .form-field textarea, .form-field select {
    font: var(--type-body) var(--font-sans);
    padding: var(--space-3); border: 1.5px solid var(--color-line); border-radius: var(--radius-2);
    background: var(--color-cream-soft); color: var(--color-ink);
    transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
  }
  .form-field input:focus-visible, .form-field textarea:focus-visible, .form-field select:focus-visible {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary) 30%, transparent);
  }
  .form-field [aria-invalid="true"] { border-color: var(--color-error); }
  .form-error { color: var(--color-error); font-size: 0.875rem; }

  /* Footer — navbar+footer redesign (2026-08-14 plan).
     Mobile-first: single stacked column with hairline separators.
     ≥768px: 3-col grid; the brand block spans the first row and splits
     internally into brand | contact. ≥1024px: explicit 4-col grid.
     Tints are STATIC color-mix over --color-cream (#F4F0FB = the canvas's
     rgba(244,240,251,…) alphas) — never animated, so the "no animated
     color-mix" perf rule is not in play. Link hover transitions plain
     tokens only (--color-cream → --color-cream-soft). */
  .site-footer { background: var(--color-ink); color: var(--color-cream); padding: var(--space-16) 0 var(--space-8); }
  .site-footer .footer-grid { display: flex; flex-direction: column; gap: 1.75rem; }   /* 28px; no token */
  .footer-col + .footer-col {
    border-top: 1px solid color-mix(in srgb, var(--color-cream) 16%, transparent);
    padding-top: var(--space-6);
  }
  .site-footer h4 {
    font: 600 0.8125rem var(--font-sans);
    letter-spacing: var(--tracking-loose);
    text-transform: uppercase;
    color: color-mix(in srgb, var(--color-cream) 72%, transparent);
    margin: 0 0 var(--space-3);
  }
  .footer-col--hours h4 { margin-bottom: var(--space-4); }
  .site-footer p { margin: 0 0 var(--space-3); line-height: 1.6; color: color-mix(in srgb, var(--color-cream) 92%, transparent); }
  .site-footer a { color: var(--color-cream); }
  .site-footer a:hover { color: var(--color-cream-soft); }

  /* Column 1 — brand block */
  .footer-brand { display: flex; align-items: center; gap: var(--space-3); margin: 0 0 var(--space-4); }
  .footer-brand-plate {
    display: inline-flex; align-items: center; justify-content: center;
    width: 48px; height: 48px; flex: none;
    border-radius: var(--radius-pill); background: var(--color-cream-soft);
  }
  .footer-brand-plate img { width: 30px; height: auto; }
  .footer-brand-name {
    font-family: var(--font-serif); font-size: 1.25rem;
    line-height: 1.15; letter-spacing: 0.01em; color: var(--color-cream);
  }
  .footer-contact { display: flex; flex-direction: column; gap: var(--space-2); }
  .footer-line { display: flex; align-items: center; gap: 0.625rem; margin: 0; }
  .footer-line--address { align-items: flex-start; }
  .footer-line--address [data-icon] { margin-top: 0.2em; }
  .footer-line [data-icon] {
    width: 1.25em; height: 1.25em; flex: none;
    color: color-mix(in srgb, var(--color-cream) 62%, transparent);
  }
  .footer-line a { display: inline-flex; align-items: center; min-block-size: 2.5rem; word-break: break-word; }

  /* Hours — spans injected by assets/js/footer-hours.js */
  .footer-hours { display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-4); align-items: baseline; }
  .footer-hours-day { color: color-mix(in srgb, var(--color-cream) 72%, transparent); }
  .footer-hours-time { color: var(--color-cream); white-space: nowrap; }
  .footer-hours-note { grid-column: 1 / -1; color: color-mix(in srgb, var(--color-cream) 72%, transparent); }

  /* Link columns */
  .footer-links { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-1); }
  .footer-links li { margin: 0; }
  .footer-links a { display: inline-flex; align-items: center; min-block-size: 2.5rem; }
  .footer-links--site { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-1) var(--space-4); }

  /* Copyright bar (wordmark removed — it now lives in the brand block) */
  .site-footer .footer-base {
    margin-top: var(--space-12); padding-top: var(--space-6);
    border-top: 1px solid color-mix(in srgb, var(--color-cream) 16%, transparent);
  }
  .site-footer .footer-base p { margin: 0; font-size: var(--type-small); color: color-mix(in srgb, var(--color-cream) 72%, transparent); }

  /* Tablet tier (canvas 834 frame → repo bp-md) */
  @media (min-width: 768px) {
    .site-footer .footer-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-10) var(--space-12); }
    .footer-col + .footer-col { border-top: 0; padding-top: 0; }
    .footer-col--brand {
      grid-column: 1 / -1;
      display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
      gap: var(--space-10) var(--space-12); align-items: start;
    }
    .footer-brand { margin: 0; gap: 0.875rem; }
    .footer-brand-plate { width: 56px; height: 56px; }
    .footer-brand-plate img { width: 34px; }
    .footer-brand-name { font-size: 1.375rem; }
    .footer-line--address [data-i18n] { max-width: 34ch; }
    .footer-line a, .footer-links a { min-block-size: 2.25rem; }
    .footer-links { gap: 2px; }
    .footer-links--site { display: flex; flex-direction: column; }
    /* Hours become stacked label-over-value pairs, 12px between pairs
       (2px flex gap + 10px margin). Cross-row column alignment (the canvas's
       auto/1fr grid) is mobile-only: subgrid would be needed to keep li-level
       semantics AND aligned columns, and Chrome's floor here is 110 (<117). */
    .footer-hours { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
    .footer-hours-day:not(:first-child), .footer-hours-note:not(:first-child) { margin-top: 0.625rem; }
  }

  /* Desktop tier (canvas 1440 frame → repo bp-lg) */
  @media (min-width: 1024px) {
    .site-footer .footer-grid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 0.8fr) minmax(0, 0.9fr); }
    .footer-col--brand { grid-column: auto; display: block; }
    .footer-brand { margin: 0 0 var(--space-4); }
    .footer-brand-plate { width: 60px; height: 60px; }
    .footer-brand-plate img { width: 36px; }
    .footer-brand-name { font-size: 1.5rem; }
    .footer-line { gap: var(--space-3); }
    .footer-line--address [data-i18n] { max-width: 32ch; }
  }

  /* Arrow link — design-system text link with a nudging arrow (CSS ::after so
     i18n textContent swaps don't wipe it). */
  .link-arrow {
    display: inline-flex; align-items: baseline; gap: var(--space-2);
    font: 600 var(--type-body) var(--font-sans); color: var(--color-primary-deep);
    background: none; /* opt out of the global ink-draw underline */
  }
  /* Trailing "view all / more" link under a section's card grid — the <p>'s
     default 0 top-margin otherwise leaves it hugging the cards. */
  .section-more { margin-top: var(--space-8); }
  .link-arrow::after { content: "\2192"; transition: transform 220ms var(--ease-out); }
  .link-arrow:hover { color: var(--color-primary); }
  .link-arrow:hover::after { transform: translateX(4px); }
  @media (prefers-reduced-motion: reduce) { .link-arrow::after { transition: none; } }

  /* Chatbot launcher.
     The stacking ladder for the fixed bottom-right corner is:
       .chatbot-launcher = .chatbot-panel 20 → .consent-banner 25 →
       .consent-modal-root 50 → .clear-data-dialog (native top layer).
     The consent banner sits ABOVE the launcher deliberately: it is a gate the
     visitor must answer, and below 640px it is full-bleed, so the round
     launcher otherwise punched a hole through its action row. */
  .chatbot-launcher {
    position: fixed; bottom: var(--space-6); right: var(--space-6);
    width: 56px; height: 56px; border-radius: var(--radius-pill);
    background: var(--color-primary-deep); color: var(--color-cream-soft); border: none;
    font-size: 1.5rem; cursor: pointer; box-shadow: var(--shadow-2);
    display: flex; align-items: center; justify-content: center;
    z-index: 20;
  }
  .chatbot-panel {
    position: fixed; bottom: var(--space-20); right: var(--space-6);
    width: min(380px, calc(100vw - var(--space-12)));
    height: min(560px, calc(100vh - var(--space-32)));
    background: var(--color-cream-soft);
    border-radius: var(--radius-2);
    box-shadow: var(--shadow-3);
    display: flex; flex-direction: column;
    z-index: 20;
  }
  .chatbot-panel[hidden] { display: none; }
  .chatbot-header { padding: var(--space-4); border-bottom: 1px solid var(--color-line); display: flex; align-items: center; justify-content: space-between; }
  .chatbot-log { flex: 1; overflow-y: auto; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
  .chat-bubble { padding: var(--space-3) var(--space-4); border-radius: var(--radius-2); max-width: 85%; }
  .chat-bubble--bot { background: var(--color-cream); color: var(--color-ink); align-self: flex-start; }
  .chat-bubble--user { background: var(--color-ink); color: var(--color-cream-soft); align-self: flex-end; }
  .chatbot-options { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 var(--space-4) var(--space-3); }
  .chatbot-input { padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-line); display: flex; gap: var(--space-2); }
  .chatbot-input input { flex: 1; border: 1px solid var(--color-line); border-radius: var(--radius-pill); padding: var(--space-2) var(--space-4); }

  /* Consent — discrete floating card */
  .consent-banner {
    position: fixed; bottom: var(--space-6); right: var(--space-6);
    width: 360px; max-width: calc(100vw - var(--space-8));
    /* Height is content-driven. The cap is only a last-resort guard for very
       short viewports (landscape phones): it is measured off the visible
       viewport (dvh, so a mobile URL bar cannot eat the action row) minus the
       banner's own top+bottom inset. A fixed 30vh cap used to clip the buttons
       off on small screens — "Accept all" was unreachable without scrolling
       inside the banner. Never reintroduce a cap the content cannot fit in. */
    max-height: calc(100dvh - (2 * var(--space-6))); overflow: auto;
    background: var(--color-cream-soft); color: var(--color-ink);
    border: 1px solid var(--color-line);
    border-left: 3px solid var(--color-primary-deep);
    border-radius: var(--radius-2);
    padding: var(--space-5) var(--space-5) var(--space-5) calc(var(--space-5) - 2px);
    box-shadow: var(--shadow-3);
    z-index: 25;
    transition: opacity 200ms var(--ease-paper);
  }
  .consent-banner[hidden] { display: none; }
  .consent-banner.is-leaving { opacity: 0; pointer-events: none; }
  .consent-heading { display: block; font: 600 1rem var(--font-sans); margin-bottom: var(--space-2); }
  .consent-banner .consent-body { font-size: 0.9rem; color: var(--color-ink-soft); margin: 0 0 var(--space-4); line-height: 1.5; }
  .consent-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
  /* min-width:0 lets a long label wrap inside the pill instead of pushing the
     row past the card's padding — the BM strings are longer than the EN ones. */
  .consent-actions .btn { padding: var(--space-2) var(--space-4); font-size: 0.875rem; min-width: 0; }
  .consent-banner a { color: var(--color-ink); text-decoration-thickness: 1px; }
  .consent-banner a:hover { color: var(--color-primary-deep); }
  .consent-saved {
    display: flex; align-items: center; gap: var(--space-3);
    color: var(--color-sage-deep); padding: var(--space-2) 0;
  }
  .consent-saved[hidden] { display: none; }
  .consent-saved-text {
    font-family: var(--font-serif);
    font-size: 1.125rem;
    color: var(--color-ink);
  }
  @media (prefers-reduced-motion: reduce) {
    .consent-banner { transition: none; }
    .sage-stamp svg animate { display: none; }
  }

  @media (max-width: 640px) {
    .consent-banner {
      left: var(--space-4); right: var(--space-4); bottom: var(--space-4);
      width: auto; max-width: none;
      max-height: calc(100dvh - (2 * var(--space-4)));
    }
    /* Stack the three actions full-width. Side by side they overflowed the
       card on narrow phones (~340px): the labels' min-content widths sum past
       the available row, and flex-shrink cannot go below min-content. */
    .consent-actions { width: 100%; flex-direction: column; align-items: stretch; }
    .consent-actions .btn { width: 100%; }
  }

  /* Consent — modal sheet */
  .consent-modal-root {
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 50;
    padding: var(--space-4);
  }
  .consent-modal-backdrop {
    position: absolute; inset: 0;
    background: rgba(57, 51, 80, 0.45);
    cursor: pointer;
  }
  .consent-modal {
    position: relative;
    background: var(--color-cream-soft);
    border-radius: var(--radius-3);
    box-shadow: var(--shadow-3);
    max-width: 480px; width: 100%;
    /* dvh, not vh: on mobile 100vh is the LARGE viewport, so an expanded URL
       bar would push the sheet's Save row below the visible area. */
    max-height: calc(100dvh - var(--space-8));
    overflow-y: auto;
    padding: var(--space-8);
  }
  .consent-modal-title { font-family: var(--font-serif); font-size: 1.5rem; margin: 0 0 var(--space-4); line-height: 1.2; }
  .consent-modal-toggles { display: grid; gap: var(--space-4); margin-bottom: var(--space-4); }
  .consent-toggle { display: flex; gap: var(--space-3); align-items: flex-start; }
  .consent-toggle input[type="checkbox"] { margin-top: 0.25rem; transform: scale(1.2); flex-shrink: 0; }
  .consent-toggle small { color: var(--color-ink-muted); font-size: 0.85rem; line-height: 1.5; }
  .consent-modal-link { margin: 0 0 var(--space-5); font-size: 0.9rem; }
  .consent-modal-link a { color: var(--color-ink); }
  .consent-modal-link a:hover { color: var(--color-primary-deep); }
  .consent-modal-actions {
    display: flex; gap: var(--space-2); justify-content: flex-end; flex-wrap: wrap;
  }

  /* Personalization micro-survey */
  .personalization-card { background: var(--color-cream-soft); border-radius: var(--radius-3); padding: var(--space-8); box-shadow: var(--shadow-1); margin-block: var(--space-12); }
  .personalization-row { margin-bottom: var(--space-5); }
  .personalization-row legend { font: 600 0.95rem var(--font-sans); margin-bottom: var(--space-2); }
  .personalization-chips { display: flex; gap: var(--space-2); flex-wrap: wrap; }
  /* Consent-gated invite shown when personalization cookies are off */
  .personalization-card--locked { text-align: center; }
  .personalization-card--locked .section-eyebrow { color: var(--color-primary-deep); }
  .personalization-card--locked > p:not(.section-eyebrow) { max-width: 54ch; margin-inline: auto; }
  .personalization-card--locked .cta-row { justify-content: center; }

  /* Staff card */
  .staff-grid { display: grid; gap: var(--space-12); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
  .staff-card { background: var(--color-cream-soft); border-radius: var(--radius-3); overflow: hidden; box-shadow: var(--shadow-1); }
  .staff-photo { background: var(--color-line); aspect-ratio: 4/5; display: flex; align-items: center; justify-content: center; color: var(--color-ink-soft); font-size: 0.75rem; text-align: center; padding: var(--space-4); }
  .staff-photo--img { padding: 0; object-fit: cover; width: 100%; }
  .staff-body { padding: var(--space-6); }
  .staff-greeting { font-family: var(--font-serif); font-size: 1.5rem; margin: 0 0 var(--space-2); }
  .staff-role { color: var(--color-ink-muted); margin: 0 0 var(--space-3); }
  .staff-video-btn { background: transparent; border: 1px dashed var(--color-line); padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill); cursor: pointer; color: var(--color-ink-soft); font: 500 0.875rem var(--font-sans); }

  /* Locale notice (blog) */
  .locale-notice { background: var(--color-sun); color: var(--color-ink); padding: var(--space-3) var(--space-4); border-radius: var(--radius-2); margin-bottom: var(--space-6); }

  /* Service block */
  .service-block { padding-block: var(--space-12); border-top: 1px solid var(--color-line); }
  .service-block:first-of-type { border-top: 0; }
  .faq summary { cursor: pointer; padding: var(--space-3) 0; font-weight: 600; }
  .faq[open] summary { color: var(--color-primary-deep); }

  /* Analytics demo */
  .stat-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(180px,1fr)); margin-bottom: var(--space-12); }
  .stat-card { background: var(--color-cream-soft); border-radius: var(--radius-2); padding: var(--space-6); }
  .stat-value { font: 700 2rem var(--font-serif); }

  /* A2 canggih cursor — sage ink-dot, decorative (OS cursor visible underneath).
     pointer-events: none is critical so the dot never intercepts clicks/hovers. */
  .canggih-cursor {
    position: fixed;
    top: 0;
    left: 0;
    width: var(--canggih-cursor-rest);
    height: var(--canggih-cursor-rest);
    border-radius: 50%;
    background: var(--color-primary);
    opacity: var(--canggih-cursor-opacity-rest);
    pointer-events: none;
    z-index: 9999;
    mix-blend-mode: multiply;
    will-change: transform, opacity;
    transition: width var(--dur-1) var(--ease-out),
                height var(--dur-1) var(--ease-out),
                opacity var(--dur-1) var(--ease-out);
  }
  .canggih-cursor--active {
    width: var(--canggih-cursor-active);
    height: var(--canggih-cursor-active);
    opacity: var(--canggih-cursor-opacity-active);
  }
  .canggih-cursor--fade {
    opacity: 0;
  }
  @media (pointer: coarse) {
    .canggih-cursor { display: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .canggih-cursor { display: none; }
  }

  /* C1 — 100vh hero, per-P3 contextual placement. Subtracts --canggih-header-h
     to fit the sticky header. Hero on every page; trust-beat blocks on
     home (values band), about (ethos), services (per-service intro). */
  body.home .hero,
  body.about .hero,
  body.services .hero,
  body.contact .hero,
  body.staff .hero,
  body.blog .hero,
  body.analytics .hero,
  body.privacy .hero {
    min-height: calc(100vh - var(--canggih-header-h));
    min-height: calc(100svh - var(--canggih-header-h));
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* Per-page banner artwork (see `.hero--banner` above). Relative paths so the
     prototype works at any deploy root. Optimised webp derivatives in anchors/
     (tracked + deployed); ~150-200KB each, from ~2MB PNG masters kept locally
     in the gitignored artworks/ dir. */
  body.about    .hero--banner { background-image: url("../img/anchors/header_about.webp"); }
  body.staff    .hero--banner { background-image: url("../img/anchors/header-meet-the-team.webp"); }
  body.services .hero--banner { background-image: url("../img/anchors/header-our-services.webp"); }
  body.blog     .hero--banner { background-image: url("../img/anchors/header-blog.webp"); }
  body.contact  .hero--banner { background-image: url("../img/anchors/header-contact-us.webp"); }

  /* Trust-beat blocks — one per page per P3.
     Home: values band (data-trust-beat="values").
     About: ethos block (data-trust-beat="ethos").
     Services: per-service intro (data-trust-beat="service-intro"). */
  body.home [data-trust-beat="values"],
  body.about [data-trust-beat="ethos"],
  body.services [data-trust-beat="service-intro"] {
    min-height: calc(100vh - var(--canggih-header-h));
    min-height: calc(100svh - var(--canggih-header-h));
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* W8 — mobile/tablet: the immersive 100vh hero + trust-beat rhythm is a
     desktop luxury. Below the 880px nav breakpoint it forces a full dead
     screen on content pages whose hero holds only a heading (blog, contact,
     services + its per-service intro). Collapse to content height so the
     first real content sits near the fold; padding (W6.3/W6.4) still gives
     breathing room. Desktop keeps the full-height experience. */
  @media (max-width: 879px) {
    body.home .hero,
    body.about .hero,
    body.services .hero,
    body.contact .hero,
    body.staff .hero,
    body.blog .hero,
    body.analytics .hero,
    body.privacy .hero,
    body.home [data-trust-beat="values"],
    body.about [data-trust-beat="ethos"],
    body.services [data-trust-beat="service-intro"] {
      min-height: 0;
    }
  }

  /* Required by C4 paper-fold (T8): parent containers need preserve-3d to avoid
     rotateX flicker, and overflow: visible to avoid clipping during the fold. */
  main, section, .hero {
    transform-style: preserve-3d;
  }
  main { overflow: visible; }

  /* Phase 2 anchor photos — section anchor figures with intent captions.
     Caption is small serif italic in muted ink, sits flush under the image.
     The figure constrains width to the parent (wrap), preserves aspect ratio
     via the JPG's intrinsic dimensions, and rounds the image corners gently
     to match the prototype's paper-craft idiom. */
  .anchor-photo {
    margin: 0 0 var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }
  .anchor-photo img {
    width: 100%;
    height: auto;
    border-radius: var(--radius-2);
    display: block;
  }
  .anchor-photo figcaption {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--type-small);
    color: var(--color-ink-muted);
    line-height: 1.4;
    margin: 0;
  }

  /* Phase 2 lazy YouTube embed — renders as a static thumbnail with a
     centered play button until clicked. On click, yt-embed.js swaps the
     inner content for the real <iframe>. CSS handles the visual frame +
     the play button; JS handles the swap. */
  .yt-embed {
    position: relative;
    display: block;
    width: 100%;
    margin: 0 0 var(--space-6);
    border-radius: var(--radius-2);
    overflow: hidden;
    background: var(--color-cream-soft);
    aspect-ratio: 16 / 9;
  }
  .yt-embed img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .yt-embed iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
  }
  .yt-embed .yt-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 64px;
    height: 64px;
    border-radius: 50%;
    border: 2px solid var(--color-cream-soft);
    background: color-mix(in srgb, var(--color-ink) 75%, transparent);
    color: var(--color-cream-soft);
    font-size: 1.5rem;
    cursor: pointer;
    display: grid;
    place-items: center;
    z-index: 2;
    transition: transform var(--dur-1) var(--ease-out),
                background var(--dur-1) var(--ease-out);
  }
  /* Bottom scrim so the (white) thumbnail caption stays legible over light
     pastel placeholder art — and over any real photo swapped in later. */
  .yt-embed::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 45%;
    background: linear-gradient(to top, rgba(57, 51, 80, 0.5), transparent);
    pointer-events: none;
    z-index: 1;
  }
  .yt-embed:hover .yt-play,
  .yt-embed:focus-within .yt-play {
    transform: translate(-50%, -50%) scale(1.06);
    background: var(--color-ink);
  }
  .yt-embed .yt-caption {
    position: absolute;
    inset: auto 0 var(--space-3) 0;
    text-align: center;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--type-small);
    color: var(--color-cream-soft);
    text-shadow: 0 1px 4px rgba(57, 51, 80, 0.55);
    padding: 0 var(--space-4);
    z-index: 2;
  }
  /* When iframe is live (post-click), hide the thumb-overlay caption + button + scrim. */
  .yt-embed[data-yt-state="playing"] .yt-play,
  .yt-embed[data-yt-state="playing"] .yt-caption {
    display: none;
  }
  .yt-embed[data-yt-state="playing"]::after {
    display: none;
  }

  /* Compact "Our intro" video card (home hero) — a small launcher instead of a
     second big thumbnail, so the intro video has one clear entry (the design's
     redundant "Watch our intro" button is dropped). Must follow the base
     .yt-embed rules above to override them. On click yt-embed.js swaps in the
     iframe and the playing rule expands it back to 16/9. */
  .yt-embed--compact {
    aspect-ratio: auto; display: flex; align-items: center; gap: var(--space-4);
    background: var(--color-cream-soft); border: 1px solid var(--color-line);
    border-radius: var(--radius-2); padding: 12px 16px; box-shadow: var(--shadow-1);
  }
  .yt-embed--compact img { display: none; }
  .yt-embed--compact::after { display: none; }
  .yt-embed--compact .yt-play {
    position: static; transform: none; inset: auto; width: 44px; height: 44px;
    border-width: 0; background: var(--color-primary-deep); font-size: 0.9rem; flex: 0 0 auto;
  }
  .yt-embed--compact:hover .yt-play,
  .yt-embed--compact:focus-within .yt-play { transform: scale(1.06); background: var(--color-primary-deep); }
  .yt-embed--compact .yt-caption {
    position: static; inset: auto; text-align: left; color: var(--color-ink);
    text-shadow: none; padding: 0; font-style: normal; font-weight: 700; font-size: 0.95rem;
  }
  .yt-embed--compact[data-yt-state="playing"] { display: block; aspect-ratio: 16/9; padding: 0; }
}

/* ── W9 — services page responsive content layout ─────────────────────────
   Mobile: single column (title → photo → copy → FAQs → CTA).
   ≥768px: two-column grid per service — sticky head (title + mood photo)
   left, measure-capped copy right — so the 7-block page scans instead of
   scrolling as one monotonous column. Jump-nav chips in the hero deep-link
   to each block (scroll-margin clears the sticky header). Programmes grid
   caps at 2-up ≥880px for a readable card measure (4 items → clean 2×2). */
@layer components {
  .service-jump { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
  .service-jump a {
    background-image: none;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-pill);
    background: var(--color-cream-soft);
    color: var(--color-ink);
    font: 500 var(--type-small) var(--font-sans);
    transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
  }
  .service-jump a:hover, .service-jump a:focus-visible { border-color: var(--color-primary-deep); color: var(--color-primary-deep); }

  .service-block { scroll-margin-top: calc(var(--canggih-header-h) + var(--space-4)); }
  .service-layout { display: grid; gap: var(--space-6); align-items: start; }
  .service-head { display: grid; gap: var(--space-5); align-content: start; }
  .service-head h2 { margin: 0; }
  .service-head .anchor-photo { margin: 0; }
  .service-head .stripe-fill { aspect-ratio: 4 / 3; border-radius: var(--radius-2); }
  .service-copy > p { max-width: var(--content-max); }
  .service-copy .faq { max-width: var(--content-max); border-bottom: 1px solid var(--color-line); }
  .service-copy .faq:first-of-type { border-top: 1px solid var(--color-line); margin-top: var(--space-6); }
  .service-copy .faq > p { color: var(--color-ink-soft); padding-bottom: var(--space-4); margin: 0; }
  .service-copy > .btn { margin-top: var(--space-6); }

  @media (min-width: 768px) {
    .service-layout { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--space-10); }
    .service-head { position: sticky; top: calc(var(--canggih-header-h) + var(--space-6)); }
  }
  @media (min-width: 1024px) {
    .service-layout { gap: var(--space-16); }
  }
  @media (min-width: 880px) {
    .services #programmes-grid { grid-template-columns: repeat(2, 1fr); }
  }
}

/* ── W11 — contact page responsive content layout ─────────────────────────
   Mobile: info → form → media, single column.
   ≥768px: 5/7 grid — info + media stack left, form card right.
   Address/phones/email reuse the home hour-row idiom as icon rows; hours
   render as label/value .hour-row pairs (split in contact.html's script). */
@layer components {
  .contact-layout { display: grid; grid-template-areas: "info" "form" "media" "map"; gap: var(--space-10); }
  .contact-info { grid-area: info; }
  .contact-form-col { grid-area: form; min-width: 0; }
  .contact-media { grid-area: media; display: grid; gap: var(--space-6); align-content: start; }
  .contact-media .yt-embed { margin: 0; }
  .contact-map {
    grid-area: map;
    display: block;
    width: 100%;
    height: 260px;
    border: 0;
    border-radius: var(--radius-2);
  }
  .contact-map[hidden] { display: none; }

  .contact-rows { list-style: none; margin: 0 0 var(--space-2); padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
  .contact-row {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3);
    padding: 12px 16px;
    background: var(--color-cream-soft);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-2);
  }
  .contact-row [data-icon] { color: var(--color-primary-deep); flex: 0 0 auto; width: 1.125rem; height: 1.125rem; }
  .contact-row-label { font-weight: 600; font-size: 0.9375rem; color: var(--color-ink); }
  .contact-row > a { margin-left: auto; text-align: right; overflow-wrap: anywhere; }
  address.contact-row--address { font-style: normal; align-items: flex-start; margin: 0 0 var(--space-2); }
  .contact-row--address [data-icon] { margin-top: 0.25em; }
  .contact-row--address p { margin: 0; color: var(--color-ink-soft); }
  .contact-row--email { margin-bottom: var(--space-8); }

  .contact-hours-label {
    font: 600 var(--type-eyebrow) var(--font-sans);
    letter-spacing: var(--tracking-loose); text-transform: uppercase;
    color: var(--color-ink-muted); margin: 0 0 var(--space-3);
  }
  ul#contact-hours.hours-rows { list-style: none; padding: 0; margin: 0; max-width: none; }

  .contact-form-card h2 { margin-bottom: var(--space-2); font-size: var(--type-h3); }
  .contact-form-card > p { color: var(--color-ink-soft); margin-bottom: var(--space-6); }
  .form-duo { display: grid; gap: 0 var(--space-4); }
  .form-duo .form-field { min-width: 0; }
  .contact-form-card .form-field input,
  .contact-form-card .form-field textarea { width: 100%; min-width: 0; }
  .form-actions { display: flex; align-items: center; gap: var(--space-3) var(--space-5); flex-wrap: wrap; margin-top: var(--space-2); }
  .form-actions small { color: var(--color-ink-muted); flex: 1 1 14rem; line-height: 1.5; }
  .events-copy p:last-child { margin-bottom: 0; }

  @media (min-width: 640px) {
    .form-duo { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  }
  @media (max-width: 639px) {
    .contact-layout { gap: var(--space-8); }
    .contact-form-card { padding: var(--space-6); }
    .form-actions .btn { width: 100%; }
    .events-band { padding: var(--space-8) var(--space-6); }
    .events-band .btn { width: 100%; }
  }
  @media (min-width: 768px) {
    .contact-layout {
      grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
      grid-template-areas: "info form" "media form" "map map";
      align-items: start;
      column-gap: var(--space-10); row-gap: var(--space-8);
    }
    .contact-map { height: 320px; }
  }
  @media (min-width: 1024px) {
    .contact-layout { column-gap: var(--space-16); }
    .contact-map { height: 400px; }
  }
}

/* ── Home "The people" — option 1c conversation list ──────────────────────
   Sticky intro column (left) + one quiet card of staff rows (right):
   headshot · name/role · italic serif quote, with an avatar-stack footer
   row summarising the rest of the team. Stacks to one column below 880px. */
@layer components {
  .people-layout { display: grid; grid-template-columns: 360px 1fr; gap: var(--space-16); align-items: start; }
  .people-intro { position: sticky; top: calc(var(--canggih-header-h) + var(--space-6)); }
  .people-intro h2 { margin: 0 0 var(--space-4); }
  .people-lede { margin: 0 0 var(--space-8); max-width: 34ch; color: var(--color-ink-soft); font-size: 1.0625rem; line-height: 1.6; text-wrap: pretty; }

  .people-list {
    background: var(--color-cream-soft);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-3);
    padding: 0 var(--space-8);
    box-shadow: var(--shadow-1);
  }
  .person-row { display: flex; align-items: center; gap: var(--space-6); padding: var(--space-6) 0; border-bottom: 1px solid var(--color-line); }
  .person-row:last-child { border-bottom: 0; }
  .person-avatar { flex: none; width: 64px; height: 64px; border-radius: 50%; object-fit: cover; object-position: top; box-shadow: 0 0 0 4px var(--color-primary-soft); }
  .people-list .person-row:nth-child(2) .person-avatar { box-shadow: 0 0 0 4px var(--av-b); }
  .people-list .person-row:nth-child(3) .person-avatar { box-shadow: 0 0 0 4px var(--av-c); }
  .person-avatar--initials {
    display: flex; align-items: center; justify-content: center;
    font: 600 1.25rem var(--font-serif); color: var(--color-primary-deep);
    background: linear-gradient(135deg, var(--av-a), var(--color-primary-soft));
  }
  .person-id { width: 220px; flex: none; }
  .person-name { margin: 0 0 2px; font: 600 1.25rem var(--font-serif); color: var(--color-ink); }
  .person-role { margin: 0; font-size: 0.875rem; font-weight: 600; color: var(--color-primary-deep); }
  .person-quote { flex: 1; margin: 0; font-family: var(--font-serif); font-style: italic; font-size: 1.03rem; line-height: 1.55; color: var(--color-ink-soft); text-wrap: pretty; }

  .person-stack { display: flex; flex: none; }
  .person-stack-av {
    width: 44px; height: 44px; border-radius: 50%; border: 2px solid var(--color-cream-soft);
    display: flex; align-items: center; justify-content: center;
    font: 600 0.9375rem var(--font-serif); color: var(--color-primary-deep);
  }
  .person-stack-av + .person-stack-av { margin-left: -12px; }
  .person-stack-av--1 { background: linear-gradient(135deg, var(--av-a), var(--color-primary-soft)); }
  .person-stack-av--2 { background: linear-gradient(135deg, var(--av-b), #E0EAF8); }
  .person-stack-av--3 { background: linear-gradient(135deg, var(--av-c), #E1EFE5); }
  .person-more { margin: 0; color: var(--color-ink-soft); font-size: 1rem; }

  @media (max-width: 879px) {
    .people-layout { grid-template-columns: 1fr; gap: var(--space-8); }
    .people-intro { position: static; }
  }
  @media (max-width: 620px) {
    .people-list { padding: 0 var(--space-6); }
    .person-row { flex-wrap: wrap; gap: var(--space-3) var(--space-4); }
    .person-id { width: auto; }
    .person-quote { flex-basis: 100%; }
  }
}

/* ── Careers culture strip ────────────────────────────────────────────────
   Photo strip on careers.html, rendered from content/careers.json into
   .grid-3 > figure.media-card. The source photos are a mix of landscape and
   portrait crops; normalising them to one 4:3 ratio keeps the row reading as
   a strip instead of a ragged grid. Frame, caption and shadow come from
   .media-card — this block only owns the crop. */
@layer components {
  .culture-strip img { aspect-ratio: 4 / 3; object-fit: cover; }
}

/* ── Enquiry capture and contact channels ─────────────────────────────────
   Design: docs/superpowers/specs/2026-08-09-enquiry-capture-and-channels-design.md
   §6 send flow (success panel) · §5 pre-fill (notice band) · §7 WhatsApp
   placement (chatbot panel footer) · §8 clear-my-data (native <dialog>).
   Markup for these classes is emitted by contact.html, consent.js and
   chatbot.js; this file is their only stylesheet. No new tokens. */
@layer components {

  /* 1 ── Success panel (§6) ───────────────────────────────────────────────
     Replaces the form in place after a valid submit. Carries role="status",
     so the default display must be stated explicitly and paired with an
     explicit [hidden] rule — a role attribute on a non-`display:none`
     element leaves it announced but invisible. Surface mirrors
     .card / .contact-form-card so the swap reads as the same object. */
  .enquiry-success {
    display: block;
    background: var(--color-cream-soft);
    border-radius: var(--radius-3);
    border-left: 3px solid var(--color-primary);
    padding: var(--space-8);
    box-shadow: var(--shadow-1);
  }
  .enquiry-success[hidden] { display: none; }

  .enquiry-success-title { margin: 0 0 var(--space-2); font-size: var(--type-h3); }
  .enquiry-success-body { margin: 0; color: var(--color-ink-soft); text-wrap: pretty; }

  /* Stacked and full-width below 640px, mirroring .form-actions .btn. */
  .enquiry-success-actions {
    display: flex; flex-direction: column; align-items: stretch;
    gap: var(--space-3);
    margin-top: var(--space-6);
  }
  .enquiry-success-actions .btn { width: 100%; }

  /* Tertiary "copy message" control — a button that reads as a text link. */
  .enquiry-copy {
    appearance: none; background: none; border: 0;
    display: inline-flex; align-items: center; justify-content: center;
    min-block-size: 1.5rem; min-inline-size: 1.5rem;   /* WCAG 2.5.8 floor */
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-1);
    font: 600 0.9rem var(--font-sans);
    color: var(--color-primary-deep);
    text-decoration: underline; text-underline-offset: 3px;
    cursor: pointer;
    transition: color var(--dur-1) var(--ease);
  }
  .enquiry-copy:hover { color: var(--color-ink); }

  .enquiry-copy-feedback {
    font-size: var(--type-small); font-weight: 600;
    color: var(--color-success);
  }
  /* Stretched to full width inside the stacked column, so centre it under the
     equally centred .enquiry-copy rather than letting it hang left. */
  .enquiry-success-actions .enquiry-copy-feedback { text-align: center; }

  @media (min-width: 640px) {
    .enquiry-success-actions { flex-direction: row; flex-wrap: wrap; align-items: center; }
    .enquiry-success-actions .btn { width: auto; }
    .enquiry-success-actions .enquiry-copy-feedback { text-align: start; }
  }
  @media (max-width: 639px) {
    .enquiry-success { padding: var(--space-6); }
  }

  /* 2 ── Service select (§5) ──────────────────────────────────────────────
     Extends the base .form-field select rule (font, padding, border, focus
     ring) — those are NOT restated here. This adds only what a <select>
     inside .contact-form-card needs: the same 100% width the sibling input
     and textarea already get, and a caret that survives appearance:none.
     The caret is two 45°/135° gradient wedges rather than an inline SVG so
     it stays on palette tokens with no encoded colour literal. */
  .form-field--select select {
    width: 100%;
    min-width: 0;
    appearance: none;
    padding-inline-end: var(--space-10);
    background-image:
      linear-gradient(45deg, transparent 50%, var(--color-ink-soft) 50%),
      linear-gradient(135deg, var(--color-ink-soft) 50%, transparent 50%);
    background-repeat: no-repeat, no-repeat;
    background-position:
      right calc(var(--space-4) + 6px) center,
      right var(--space-4) center;
    background-size: 6px 6px, 6px 6px;
  }

  /* 3 ── Pre-fill notice (§5) ─────────────────────────────────────────────
     Quiet band above the form card explaining that fields were filled from
     earlier answers, with an inline escape hatch. Starts [hidden]; JS
     unhides it only when something was actually filled from storage. */
  .prefill-notice {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: color-mix(in srgb, var(--color-primary-soft) 45%, var(--color-cream-soft));
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-2);
  }
  .prefill-notice[hidden] { display: none; }

  .prefill-notice-text { font-size: 0.9rem; color: var(--color-ink-soft); flex: 1 1 16rem; }

  .prefill-notice-clear {
    appearance: none; background: none; border: 0;
    display: inline-flex; align-items: center; justify-content: center;
    min-block-size: 1.5rem; min-inline-size: 1.5rem;   /* WCAG 2.5.8 floor */
    margin-inline-start: auto;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-1);
    font: 600 0.9rem var(--font-sans);
    color: var(--color-primary-deep);
    text-decoration: underline; text-underline-offset: 3px;
    cursor: pointer;
    transition: color var(--dur-1) var(--ease);
  }
  .prefill-notice-clear:hover { color: var(--color-ink); }

  /* 4 ── Clear-my-data dialog (§8) ────────────────────────────────────────
     A native <dialog> opened with .showModal(), so it lives in the top
     layer and needs no z-index. Surface, radius and shadow are lifted from
     .consent-modal so the two dialogs read as one system.
     No `display` is declared on the dialog itself — the UA's
     dialog:not([open]) { display: none } must keep working. */
  .clear-data-dialog {
    border: 0;
    padding: var(--space-8);
    max-width: 480px;
    width: calc(100% - var(--space-8));
    max-height: calc(100vh - var(--space-8));
    overflow-y: auto;
    background: var(--color-cream-soft);
    color: var(--color-ink);
    border-radius: var(--radius-3);
    box-shadow: var(--shadow-3);
  }
  /* Literal rgba, matching .consent-modal-backdrop: ::backdrop only inherits
     custom properties from its originating element in browsers newer than
     this project's Chrome 110 / Safari 16 floor, so var() here would fall
     back to a fully transparent scrim on the oldest supported engines. */
  .clear-data-dialog::backdrop { background: rgba(57, 51, 80, 0.45); }

  .clear-data-title {
    font-family: var(--font-serif); font-size: 1.5rem;
    margin: 0 0 var(--space-4); line-height: 1.2;
  }
  .clear-data-body { margin: 0 0 var(--space-4); font-size: 0.9rem; color: var(--color-ink-soft); line-height: 1.5; }
  .clear-data-items {
    margin: 0 0 var(--space-6);
    padding-inline-start: var(--space-5);
    font-size: 0.9rem; color: var(--color-ink-soft);
  }
  .clear-data-items li + li { margin-top: var(--space-1); }
  .clear-data-actions {
    display: flex; gap: var(--space-2); justify-content: flex-end; flex-wrap: wrap;
  }

  @media (max-width: 639px) {
    .clear-data-dialog { padding: var(--space-6); }
    .clear-data-actions .btn { width: 100%; }
  }

  /* 5 ── Chatbot panel footer (§7) ────────────────────────────────────────
     Persistent strip pinned under .chatbot-input, offering the same enquiry
     as a WhatsApp hand-off. Hidden outright when channels() reports no
     WhatsApp row — no number is ever hard-coded. The `background` shorthand
     on .chatbot-wa deliberately clears the base-layer ink-draw underline
     (as .link-arrow does); this is an action, not body copy. */
  .chatbot-footer {
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--color-line);
  }
  .chatbot-wa {
    display: flex; align-items: center; justify-content: center;
    gap: var(--space-2);
    min-block-size: 2.5rem;                            /* matches the 40px control floor */
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--color-primary-soft) 55%, var(--color-cream-soft));
    color: var(--color-primary-deep);
    font: 600 0.875rem var(--font-sans);
    transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
  }
  .chatbot-wa:hover {
    background-color: var(--color-primary-deep);
    color: var(--color-cream-soft);
  }
  .chatbot-wa [data-icon] { flex: 0 0 auto; width: 1.125rem; height: 1.125rem; }

  /* 6 ── Reduced motion ───────────────────────────────────────────────────
     Every transition introduced above is a colour tween on a tertiary
     control; none of them survive a reduce preference. */
  @media (prefers-reduced-motion: reduce) {
    .enquiry-copy,
    .prefill-notice-clear,
    .chatbot-wa { transition: none; }
  }
}
