/* styles-core.css : invariant base layer
 *
 * This file does NOT vary per business. It defines:
 *   - reset + box-sizing
 *   - layout primitives (.container, .row, etc.)
 *   - typography scale (using --font-display, --font-body from variant)
 *   - motion primitives (.reveal, .reveal-mask, animation keyframes)
 *   - accessibility (focus rings, prefers-reduced-motion, skip link)
 *   - shared component skeletons that VARIANT styles override (.nav, .btn, .marquee)
 *
 * Per-build styles live in styles-variant.css (generated by css-builder.js).
 * Variant component styles live in styles-components.css (concatenated from
 * the components that were actually composed into this build).
 */

/* ==== reset ==== */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
body {
  font-family: var(--font-body);
  font-feature-settings: 'kern', 'liga', 'calt', 'ss01';
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  letter-spacing: var(--body-tracking, -0.005em);
  line-height: 1.55;
  overflow-x: hidden;
}
img { max-width: 100%; height: auto; display: block; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul, ol { padding-inline-start: 1.2em; }

/* ==== layout primitives ==== */
.container { width: min(1280px, calc(100% - 48px)); margin-inline: auto; }
@media (max-width: 720px) {
  .container { width: calc(100% - 32px); }
}
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.gap-1 { gap: 8px; } .gap-2 { gap: 16px; } .gap-3 { gap: 24px; } .gap-4 { gap: 32px; } .gap-6 { gap: 48px; }

/* ==== typography scale ==== */
:root {
  --display-1: clamp(48px, 9vw, 124px);
  --display-2: clamp(36px, 6vw, 84px);
  --display-3: clamp(28px, 4.5vw, 60px);
  --h1: clamp(40px, 6vw, 72px);
  --h2: clamp(32px, 4.5vw, 56px);
  --h3: clamp(24px, 3vw, 36px);
  --eyebrow-size: 11px;
  --eyebrow-tracking: 0.22em;
}

.h1, h1 { font-family: var(--font-display); font-weight: 500; font-size: var(--h1); line-height: 1.02; letter-spacing: var(--display-tracking, -0.02em); margin: 0; }
.h2, h2 { font-family: var(--font-display); font-weight: 500; font-size: var(--h2); line-height: 1.04; letter-spacing: var(--display-tracking, -0.02em); margin: 0; }
.h3, h3 { font-family: var(--font-display); font-weight: 500; font-size: var(--h3); line-height: 1.1;  letter-spacing: -0.01em; margin: 0; }
.h1 i, .h2 i, .h3 i, h1 i, h2 i, h3 i { font-style: var(--display-italic-style, italic); font-weight: 400; color: var(--accent); }
p { margin: 0 0 1em; max-width: 60ch; }
.lede { font-size: clamp(17px, 1.4vw, 22px); line-height: 1.5; color: var(--muted); max-width: 60ch; }
.eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--eyebrow-size);
  letter-spacing: var(--eyebrow-tracking);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}

/* ==== reveal primitives ====
 *
 * Everything in a band used to fire at the same instant: `transition-delay`
 * appeared zero times in the whole stylesheet, so a section with nine elements
 * in it simply switched on. The hand-built reference reveals 43 elements in
 * 90ms increments over 900ms, which is what makes a page read as assembling
 * rather than appearing. --i is stamped per element by script.js at the moment
 * its group first intersects, so the stagger is per-band, not per-page. */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity 900ms cubic-bezier(0.22, 1, 0.36, 1) calc(var(--i, 0) * 90ms),
    transform 900ms cubic-bezier(0.22, 1, 0.36, 1) calc(var(--i, 0) * 90ms);
}
.reveal.is-in { opacity: 1; transform: translateY(0); }
/* No JS, no hiding. See the .js latch in the document head. */
html:not(.js) .reveal { opacity: 1; transform: none; }
html:not(.js) .reveal-mask > * { transform: none; }
.reveal-mask { position: relative; overflow: hidden; }
.reveal-mask > * { transform: translateY(110%); transition: transform 1100ms cubic-bezier(0.22, 1, 0.36, 1) calc(var(--i, 0) * 90ms); }
.reveal-mask.is-in > * { transform: translateY(0); }

/* ==== buttons ==== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 16px 26px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Was 999px. A full pill is the single loudest SaaS tell on an otherwise
     editorial page: the hand-built comparison sets its CTA at 4px and the
     editorial-warm references use square corners or none at all. --btn-radius
     is a token so a palette archetype can still take it back up if a brand
     genuinely wants soft corners. */
  border-radius: var(--btn-radius, 4px);
  transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
              background-color 240ms ease,
              color 240ms ease;
  will-change: transform;
}
.btn--primary {
  background: var(--accent);
  color: var(--paper);
}
.btn--primary:hover { transform: translateY(-2px); }
.btn--ghost {
  background: transparent;
  color: var(--ink);
  /* 22% of --ink over --paper measured 1.1:1 : a border nobody could see.
     Non-text UI needs 3:1 (WCAG 1.4.11), so the rim is a real line now. */
  border: 1px solid color-mix(in oklab, var(--ink) 48%, transparent);
}
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }

/* ==== dark-media surface contract ====
 * A section that paints its text over a photograph or a deepwash field marks
 * itself `data-surface="dark"`. Everything inside then resolves against
 * --paper instead of --ink.
 *
 * Without this every component dropped onto a dark hero silently inherits the
 * light-page defaults (--ink text, --ink hairlines) and disappears. That is
 * exactly how the hero's secondary CTA shipped: --ink label + a 22%-alpha
 * --ink border on a photo, measured at 1.4:1 label / 1.1:1 border on all
 * three demo tiers.
 *
 * The contract has two halves. This file supplies the colours; the SECTION is
 * responsible for painting a scrim opaque enough that --paper text clears
 * 4.5:1 over the worst photograph the palette engine might hand it. */
[data-surface="dark"] { color: var(--paper); }
[data-surface="dark"] .lede { color: color-mix(in oklab, var(--paper) 88%, transparent); }
[data-surface="dark"] .h1 i, [data-surface="dark"] .h2 i, [data-surface="dark"] .h3 i,
[data-surface="dark"] h1 i, [data-surface="dark"] h2 i, [data-surface="dark"] h3 i,
[data-surface="dark"] .eyebrow { color: var(--accent-on-dark); }
[data-surface="dark"] .btn--ghost {
  color: var(--paper);
  border-color: color-mix(in oklab, var(--paper) 66%, transparent);
  /* The chip's own fill is part of the contrast guarantee: it darkens the
     label's local background independently of what the photo is doing. */
  background: color-mix(in oklab, var(--deepwash) 46%, transparent);
}
[data-surface="dark"] .btn--ghost:hover {
  background: var(--paper);
  color: var(--deepwash);
  border-color: var(--paper);
}

/* ==== skip link ==== */
.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--paper);
  padding: 12px 16px; z-index: 999;
}
.skip:focus { left: 12px; top: 12px; }

/* ==== focus ==== */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* ==== marquee primitives ==== */
.marquee {
  position: relative;
  overflow: hidden;
  padding: 26px 0;
  background: var(--wash);
  border-block: 1px solid color-mix(in oklab, var(--ink) 8%, transparent);
}
.marquee__track {
  display: flex;
  align-items: center;
  gap: 36px;
  white-space: nowrap;
  animation: marqueeScroll var(--marquee-duration, 64s) linear infinite;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}
.marquee__track > span { display: inline-block; }
.marquee__track .sep { color: var(--accent-2); }
.marquee__track > span:nth-child(4n+1) { color: var(--accent-deep); }
@keyframes marqueeScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ==== nav primitive (variants override .nav__inner layout) ==== */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  transition: background-color 280ms ease, backdrop-filter 280ms ease, border-color 280ms ease;
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled {
  background: color-mix(in oklab, var(--paper) 78%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  border-bottom-color: color-mix(in oklab, var(--ink) 8%, transparent);
}
/* Scrim behind the nav bar.
 *
 * The nav is fixed and transparent until scroll, so at rest it sits directly
 * on whatever the first section is. When that section is a full-bleed photo
 * hero, the nav's --muted links landed on the photograph at 1.2-2.0:1 and were
 * effectively invisible on every build. Recolouring the links alone does not
 * fix it, because the photo underneath is arbitrary: a bright photo kills a
 * light link exactly the way a dark photo kills a dark one.
 *
 * So the nav paints its own guaranteed background. At >=66% --deepwash a
 * PURE WHITE photograph still resolves to ~0.10 relative luminance, which puts
 * --paper text at ~5.9:1 : pass at 4.5:1 with headroom, for ANY image. The
 * gradient releases to nothing by 200px so it reads as a cinematic top
 * vignette rather than a bar. */
.nav::before {
  content: '';
  position: absolute; inset: 0 0 auto 0; height: 200px;
  z-index: -1; pointer-events: none;
  opacity: 0; transition: opacity 280ms ease;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--deepwash) 74%, transparent) 0%,
    color-mix(in oklab, var(--deepwash) 66%, transparent) 40%,
    color-mix(in oklab, var(--deepwash) 26%, transparent) 74%,
    transparent 100%);
}
/* Over-dark nav state. The builder sets data-nav-surface="dark" on <body> for
   any page whose FIRST section is a dark-media hero; subpages with the light
   page-hero never get it. Once scrolled, the nav has its own --paper fill and
   reverts to the light treatment. */
body[data-nav-surface="dark"] .nav:not(.is-scrolled)::before { opacity: 1; }
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .brand { color: var(--paper); }
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .brand__mark,
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .brand__dot {
  color: color-mix(in oklab, var(--paper) 78%, var(--accent) 22%);
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .brand__text i {
  color: color-mix(in oklab, var(--paper) 84%, transparent);
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__links a:not(.nav__cta) { color: var(--paper); }
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__links a:not(.nav__cta):hover {
  color: color-mix(in oklab, var(--paper) 62%, var(--accent) 38%);
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__burger {
  border-color: color-mix(in oklab, var(--paper) 55%, transparent);
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__burger span { background: var(--paper); }
/* The mobile drop panel is a solid --paper card, so its links go back to --ink
   even while the bar above it is still in over-dark mode. */
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__links.is-open a:not(.nav__cta) { color: var(--ink); }
.nav__inner {
  display: flex; align-items: center; gap: 14px;
  width: min(1400px, calc(100% - 48px));
  margin-inline: auto;
  padding-block: 22px;
}
/* Was justify-content: space-between with three children. The click-to-call
   sits between the links and the burger, so the links push right instead. */
.nav__links { margin-left: auto; }
.brand { display: inline-flex; align-items: center; gap: 12px; color: inherit; font-family: var(--font-display); font-size: 18px; letter-spacing: -0.01em; }
.brand__mark { display: inline-flex; color: var(--accent); }
.brand__dot { margin-inline: 6px; color: var(--accent); }
.brand__text i { font-style: italic; color: var(--muted); }
.nav__links { display: flex; align-items: center; gap: 28px; }
.nav__links a {
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: var(--eyebrow-tracking);
  text-transform: uppercase; color: var(--muted);
  transition: color 200ms ease;
}
.nav__links a:hover { color: var(--ink); }
.nav__links a.nav__cta {
  padding: 10px 18px; border-radius: var(--btn-radius, 4px);
  background: var(--accent); color: var(--paper);
}
/* ==== click-to-call in the nav ====
 *
 * Reachable without opening anything, on every page, at every width. On a
 * trades site this is the highest-value interaction there is, so it never
 * lives inside the burger panel. Above 1080px it prints the number; below
 * that the number would fight the brand mark for width, so it says "Call"
 * next to the handset and the number itself is one tap away in the tel: link,
 * the footer, the contact chapter and the sticky bar. */
.nav__call {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 9px 16px;
  border-radius: var(--btn-radius, 4px);
  border: 1px solid color-mix(in oklab, var(--ink) 18%, transparent);
  color: var(--ink);
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: var(--eyebrow-tracking); text-transform: uppercase;
  transition: border-color 200ms ease, color 200ms ease, background-color 200ms ease;
  white-space: nowrap;
}
.nav__call:hover { border-color: var(--accent); color: var(--accent-deep); }
.nav__call svg { flex: none; }
.nav__call-word { display: none; }
@media (max-width: 1080px) {
  .nav__call-num { display: none; }
  .nav__call-word { display: inline; }
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__call {
  color: var(--paper);
  border-color: color-mix(in oklab, var(--paper) 55%, transparent);
}
body[data-nav-surface="dark"] .nav:not(.is-scrolled) .nav__call:hover {
  border-color: var(--paper);
  background: color-mix(in oklab, var(--paper) 14%, transparent);
}

.nav__burger { display: none; }
@media (max-width: 880px) {
  .nav__links { display: none; }
  .nav__burger {
    display: inline-flex; flex-direction: column; gap: 5px;
    padding: 10px; border: 1px solid color-mix(in oklab, var(--ink) 18%, transparent);
    border-radius: 999px;
  }
  .nav__burger span { display: block; width: 18px; height: 1.5px; background: var(--ink); border-radius: 2px; }
  .nav__links.is-open {
    display: flex; position: fixed; inset: 64px 16px auto 16px;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 12px; border-radius: 18px;
    background: var(--paper);
    border: 1px solid color-mix(in oklab, var(--ink) 12%, transparent);
  }
  .nav__links.is-open a { padding: 14px 12px; font-size: 13px; }
}

/* ==== custom cursor (only on hover-capable, fine-pointer devices) ==== */
.cursor {
  position: fixed; top: 0; left: 0;
  width: 24px; height: 24px; margin: -12px 0 0 -12px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--accent) 22%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 60%, transparent);
  pointer-events: none; z-index: 9999;
  transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
              background-color 200ms ease, border-color 200ms ease;
  mix-blend-mode: multiply;
  display: none;
}
@media (hover: hover) and (pointer: fine) {
  .cursor { display: block; }
  body { cursor: none; }
  a, button, [data-magnetic] { cursor: none; }
}
.cursor.is-hover { transform: scale(2.4); background: color-mix(in oklab, var(--accent) 14%, transparent); }

/* ==== footer ==== */
.footer {
  padding: 64px 0 36px;
  background: var(--deepwash);
  color: color-mix(in oklab, var(--paper) 88%, transparent);
}
.footer__inner { display: flex; flex-wrap: wrap; gap: 32px; justify-content: space-between; align-items: end; }
.footer__brand { font-family: var(--font-display); font-size: 28px; }
.footer__meta { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.65; }
.footer__credit { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; }
.footer__disc { margin-top: 18px; padding-top: 18px; border-top: 1px solid color-mix(in oklab, var(--paper) 18%, transparent); font-size: 12px; line-height: 1.5; max-width: 70ch; opacity: 0.62; }
/* Visible image attribution, for licences that require it on the page rather
   than only in assets/CREDITS.md. Collapsed by default: it is a legal
   obligation, not a design element, and it must not compete with the footer. */
.footer__imgcredits { margin-top: 10px; }
.footer__imgcredits summary { cursor: pointer; opacity: 0.72; }
.footer__imgcredits summary:hover { opacity: 1; }
.footer__imgcredits ul {
  list-style: none; margin: 10px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
  letter-spacing: 0.08em; text-transform: none; opacity: 0.72;
}
.footer a { color: inherit; border-bottom: 1px solid color-mix(in oklab, var(--paper) 32%, transparent); }

/* Footer contact block : phone(s), email, address, hours, open state. Present
   on every page of every build and every tier. The footer used to carry the
   address alone, so in multipage mode a visitor on work.html had no phone
   number anywhere below the fold. */
.footer__contact { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin-top: 14px; }
.footer__contact a {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.14em;
  min-height: 30px; display: inline-flex; align-items: center;
}
/* Compound selector deliberately: `.footer__contact a` above is (0,1,1) and
   would otherwise keep the primary number at mono 12px. */
.footer__contact a.footer__phone {
  font-family: var(--font-display); font-size: clamp(19px, 1.6vw, 22px);
  letter-spacing: 0; text-transform: none; min-height: 38px;
}
.footer__addr, .footer__hours {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; opacity: 0.65; font-style: normal;
}
.footer__state { margin-top: 4px; }
.footer__state[data-open="1"] { background: color-mix(in oklab, oklch(72% 0.16 150) 26%, transparent); color: oklch(90% 0.05 150); }
.footer__state[data-open="0"] { background: color-mix(in oklab, var(--paper) 14%, transparent); color: color-mix(in oklab, var(--paper) 76%, transparent); }

/* ==== open / closed badge (shared: contact row + footer) ====
 *
 * Sits directly under the number so "will anyone pick up right now" is
 * answered in the same glance as the number itself. Ships hidden and empty;
 * script.js fills it in the visitor's browser, in the business's timezone.
 * No JS means no badge, which is correct: better silent than stale. */
.row__state {
  display: inline-flex; align-items: center; gap: 7px; width: fit-content;
  margin-top: 8px; padding: 5px 12px 5px 9px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  background: color-mix(in oklab, var(--ink) 7%, transparent); color: var(--muted);
  border-bottom: none;
}
.row__state[hidden] { display: none; }
.row__state::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.row__state[data-open="1"] { background: color-mix(in oklab, oklch(62% 0.15 150) 18%, transparent); color: oklch(42% 0.13 150); }
.row__state[data-open="0"] { background: color-mix(in oklab, var(--ink) 8%, transparent); color: var(--muted); }
.row__state em { font-style: normal; opacity: 0.75; letter-spacing: 0.1em; }

/* ==== mobile sticky call bar ====
 *
 * The design standard's conversion note: sticky header CTA + mobile sticky
 * bottom bar together is +12%, and a third CTA surface adds nothing. This is
 * that second surface, and on a trades site its left half is a phone number,
 * not a form. Phone-width only: on desktop the nav call, the contact chapter
 * and the footer already cover it without eating 64px of viewport. */
.callbar { display: none; }
@media (max-width: 880px) {
  .callbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 190;
    gap: 1px;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
    /* Solid fill, not glass. The blur budget is one purposeful use per site
       and the nav scroll state already spends it. */
    background: var(--paper);
    border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent);
  }
  .callbar { grid-auto-columns: 1.5fr 1fr; }
  .callbar__btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    min-height: 48px; padding: 12px 10px; border-radius: var(--btn-radius, 4px);
    font-family: var(--font-mono); font-size: 11px;
    letter-spacing: 0.08em; text-transform: uppercase;
    white-space: nowrap;
  }
  .callbar__btn--call { background: var(--accent); color: var(--paper); }
  .callbar__btn--alt {
    border: 1px solid color-mix(in oklab, var(--ink) 20%, transparent);
    color: var(--ink);
  }
  .callbar__btn svg { flex: none; }
  /* Nothing may sit under the bar: the footer credit and the chat launcher
     both used to land exactly there. The launcher override needs the
     descendant selector to out-specify the base `.bot` rule, which is
     declared later in this file. */
  body:has(.callbar) { padding-bottom: 80px; }
  body:has(.callbar) .bot { bottom: calc(86px + env(safe-area-inset-bottom, 0px)); }
}
/* Phone-width nav. Below 560px the brand's italic tail, the click-to-call and
   the burger cannot all have their natural width, and the burger was the one
   pushed off the right edge. The tail is the least load-bearing of the three. */
@media (max-width: 560px) {
  .nav__inner { gap: 10px; }
  .brand { font-size: 15px; gap: 8px; min-width: 0; }
  .brand__mark svg { width: 30px; height: 30px; }
  .brand__text i, .brand__dot { display: none; }
  .nav__call { padding: 9px 13px; gap: 6px; letter-spacing: 0.12em; }
}

/* ==== chapter primitive ==== */
.chapter { padding: 120px 0; position: relative; }
/* Chapters that hold absolutely-positioned decorative layers
   (contact backdrop, about watermark) get explicit clipping. */
.chapter--has-backdrop, .chapter--has-watermark { overflow: hidden; isolation: isolate; }
.chapter__hd { margin-bottom: 64px; max-width: 1280px; }
.chapter__num { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; }
.chapter__num .num {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.2em;
  color: var(--accent-2);
  /* Stacked text-shadow for chiseled depth (no background-clip text : banned) */
  text-shadow:
    0 1px 0 color-mix(in oklab, var(--ink) 6%, transparent),
    0 -1px 0 color-mix(in oklab, var(--paper) 32%, transparent);
}
.chapter__num .rule { flex: 1; height: 1px; background: color-mix(in oklab, var(--accent-2) 22%, transparent); }

/* ==== glass primitive (nav scroll state ONLY per design system : 1 purposeful use) ==== */
.glass {
  background: var(--glass);
  border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  backdrop-filter: blur(20px) saturate(140%);
  border-radius: 22px;
}
/* ==== booking form surface (solid : no backdrop-filter to respect glass cap) ==== */
.booking--form {
  background: color-mix(in oklab, var(--paper) 92%, transparent);
  border: 1px solid var(--glass-line);
  border-radius: 22px;
}

/* ==== reviews block (default : variants can override fully) ==== */
.reviews { padding: 96px 0; background: var(--wash); }
.reviews__head { display: flex; align-items: end; justify-content: space-between; flex-wrap: wrap; gap: 24px; margin-bottom: 48px; }
.reviews__rating { display: inline-flex; align-items: center; gap: 14px; }
.reviews__num { font-family: var(--font-display); font-size: 64px; line-height: 1; letter-spacing: -0.03em; }
.reviews__stars { display: inline-flex; gap: 4px; color: var(--accent); overflow: hidden; }
.reviews__stars svg { width: 18px; height: 18px; }
.reviews__count { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.reviews__disc { display: block; margin-top: 6px; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }

/* ==== AI assistant launcher (kept from v1) ==== */
.bot { position: fixed; bottom: 20px; right: 20px; z-index: 200; }
.bot__launch {
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--accent); color: var(--paper);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 26px color-mix(in oklab, var(--ink) 18%, transparent);
}
.bot__panel[hidden] { display: none; }
.bot__panel {
  width: min(360px, calc(100vw - 32px));
  max-height: 70vh;
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid color-mix(in oklab, var(--ink) 12%, transparent);
  border-radius: 18px;
  overflow: hidden;
  position: absolute; right: 0; bottom: 72px;
}
.bot__id strong { color: inherit; }
.bot__hd { padding: 14px 16px; background: var(--accent); color: var(--paper); display: flex; align-items: center; justify-content: space-between; }
.bot__hd strong { font-family: var(--font-display); font-weight: 500; }
.bot__close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; border-radius: 50%;
  color: var(--paper); background: color-mix(in oklab, var(--paper) 12%, transparent);
  cursor: pointer; transition: background-color 200ms ease;
}
.bot__close:hover { background: color-mix(in oklab, var(--paper) 22%, transparent); }
.booking__success { padding: 12px; text-align: left; }
.booking__success h3 { font-family: var(--font-display); font-weight: 500; font-size: clamp(20px, 2vw, 26px); margin: 0 0 10px; color: var(--accent-deep); }
.booking__success p { font-size: 15px; line-height: 1.55; color: var(--muted); margin: 0; }
.bot__log { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
.bot__msg { padding: 10px 14px; border-radius: 14px; font-size: 14px; line-height: 1.45; max-width: 86%; }
.bot__msg--bot { background: var(--wash); align-self: flex-start; }
.bot__msg--me  { background: var(--accent); color: var(--paper); align-self: flex-end; }
.bot__input { display: flex; padding: 12px; gap: 8px; border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent); }
.bot__input input { flex: 1; padding: 10px 12px; border-radius: 10px; border: 1px solid color-mix(in oklab, var(--ink) 14%, transparent); background: var(--paper); color: var(--ink); }
.bot__input button { padding: 8px 14px; border-radius: 10px; background: var(--ink); color: var(--paper); font-size: 13px; }

/* ==== welcome loader ==== */
/* Each loader variant brings its own internal animation : this is the
 * shared chassis + fade-out behavior. */
.loader {
  position: fixed; inset: 0; z-index: 9000;
  display: flex; align-items: center; justify-content: center;
  background: var(--deepwash);
  color: var(--paper);
  transition: opacity 520ms ease, visibility 520ms ease;
}
.loader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
body.loader-locked { overflow: hidden; height: 100vh; }

/* ==== motion budget hooks (variants set these on :root) ==== */
:root {
  --motion-lush: 1;
  --motion-measured: 0.75;
  --motion-reserved: 0.45;
  --marquee-duration: 64s;
}

/* ==== prefers-color-scheme hook (light-mode-first; dark add-on lives in variant CSS if needed) ==== */


/* ==== generated per-build palette + typography ==== */
:root {
  --paper:        oklch(95.29% 0.0280 232.82);
  --wash:         oklch(90.96% 0.0500 232.82);
  --wash-deep:    oklch(84.80% 0.0700 215.41);
  --ink:          oklch(15.00% 0.0220 37.99);
  --muted:        oklch(46.00% 0.0200 37.99);
  --line:         oklch(85.00% 0.0120 37.99);
  --accent:       oklch(51.64% 0.0859 197.99);
  --accent-deep:  oklch(29.64% 0.0987 197.99);
  --accent-on-dark: oklch(78.00% 0.0738 197.99);
  --accent-soft:  oklch(63.32% 0.0708 197.99);
  --accent-light: oklch(86.24% 0.0524 197.99);
  --accent-2:     oklch(49.45% 0.0765 347.99);
  --accent-2-soft:oklch(66.80% 0.0593 347.99);
  --deepwash:     oklch(17.29% 0.0320 197.99);
  --glass:        oklch(92.00% 0.0472 197.99 / 0.55);
  --glass-strong: oklch(92.00% 0.0472 197.99 / 0.78);
  --glass-line:   oklch(78.00% 0.0601 197.99 / 0.42);

  --font-display: 'Fraunces', 'Cormorant Garamond', Georgia, serif;
  --font-body: 'Geist', 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
  --display-tracking: -0.020em;
  --body-tracking: -0.005em;
  --display-italic-style: italic;

  --tilt-strength: 0.75;
  --parallax-strength: 0.7;
  --reveal-strength: 0.85;
  --marquee-duration: 64s;
  --glass-policy: reserved;
}

/* Italic emphasis in display type uses accent-deep for richer feel */
.h1 i, .h2 i, .h3 i, h1 i, h2 i, h3 i { color: var(--accent-deep); }

/* Eyebrows use accent-2 as a sibling-voice so the eyebrow doesn't read the
   same as a CTA. accent stays the CTA color. */
.eyebrow { color: var(--accent-2); }

/* Section background rotation. Chapters can opt into --bg-deep for the
   richest section background : used sparingly by the composer. */
.chapter--deep-bg { background: var(--wash-deep); }
.chapter--paper-bg { background: var(--paper); }
.chapter--wash-bg { background: var(--wash); }

/* Glass policy adjustments */

.glass { background: color-mix(in oklab, var(--paper) 92%, transparent); -webkit-backdrop-filter: none; backdrop-filter: none; border-color: color-mix(in oklab, var(--ink) 10%, transparent); }



/* ==== component styles ==== */

.bgart--sigil {
  position: absolute;
  inset: 0; margin: auto;
  width: min(72vmin, 920px); height: min(72vmin, 920px);
  color: color-mix(in oklab, var(--accent) 28%, transparent);
  pointer-events: none;
  z-index: 0;
}
.bgart-sigil__inner { animation: bgartSigilSpin 220s linear infinite; transform-origin: 100px 100px; }
.bgart-sigil__cross { opacity: 0.4; }
@keyframes bgartSigilSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .bgart-sigil__inner { animation: none; }
}



.loader--sigil { background: var(--deepwash); color: var(--accent); }
.loader--sigil .loader__sigil {
  position: absolute; inset: 0; margin: auto;
  width: min(60vmin, 480px); height: min(60vmin, 480px);
}
.loader--sigil .loader__id {
  position: relative; z-index: 2; text-align: center;
  color: var(--paper); display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.loader--sigil .loader__eyebrow {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.24em; text-transform: uppercase;
  color: color-mix(in oklab, var(--paper) 55%, transparent);
  opacity: 0; animation: ldrFade 480ms ease 700ms forwards;
}
.loader--sigil .loader__name {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(28px, 4.5vw, 48px); margin: 0; letter-spacing: -0.015em;
  opacity: 0; transform: translateY(12px);
  animation: ldrUp 700ms cubic-bezier(0.22, 1, 0.36, 1) 850ms forwards;
}
@keyframes ldrDraw { to { stroke-dashoffset: 0; } }
@keyframes ldrFade { to { opacity: 1; } }
@keyframes ldrUp { to { opacity: 1; transform: translateY(0); } }



.hero--split {
  position: relative;
  min-height: 100vh; min-height: 100svh;
  padding: 140px 0 80px;
  background: var(--paper);
  overflow: hidden;
  isolation: isolate;
}
.hero--split .bgart {
  position: absolute; inset: 0; z-index: 0;
  opacity: 0.55;
}
.hero--split .hero__inner {
  position: relative; z-index: 1;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 56px;
  align-items: stretch;
}
.hero--split .hero__left { display: flex; flex-direction: column; justify-content: center; }
.hero--split .hero__pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 999px;
  background: var(--wash); border: 1px solid color-mix(in oklab, var(--ink) 8%, transparent);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--muted);
  width: fit-content; margin-bottom: 28px;
}
.hero--split .hero__pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.hero--split .hero__title { font-size: clamp(48px, 7vw, 96px); line-height: 0.98; }
.hero--split .hero__sub { margin-top: 22px; }
.hero--split .hero__cta { display: inline-flex; gap: 14px; margin-top: 32px; }
.hero--split .hero__signals {
  margin-top: 48px; padding-top: 28px;
  border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px;
}
.hero--split .hero__signals dt {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.hero--split .hero__signals dd { margin: 0; font-size: 14px; line-height: 1.4; color: var(--ink); }
.hero--split .hero__signals dd strong { font-weight: 600; }

/* The media column is given a DEFINITE height. Without one, the two rows are
   fr units inside a grid whose own height comes from its content, so each row
   resolved to its image's natural rendered height: a portrait 900x1600 source
   at 581px wide painted 1240px tall and pushed the hero to 2251px, which put
   the H1 at 931px, below a 900px fold. A hero must never be sized by the
   aspect ratio of whatever photo the lead happened to have. */
.hero--split .hero__right {
  position: relative; display: grid;
  grid-template-rows: minmax(0, 1.62fr) minmax(0, 1fr);
  gap: 16px;
  height: clamp(440px, 66vh, 640px);
}
.hero--split .hero__photo {
  position: relative; margin: 0;
  border-radius: 8px;
  overflow: hidden;
  background: var(--wash);
}
/* height:100% alone is not enough: the global img rule sets height:auto, and
   an auto height re-introduces the natural-ratio blow-out. min-height:0 lets
   the figure actually shrink inside its row. */
.hero--split .hero__photo { min-height: 0; }
.hero--split .hero__photo img { width: 100%; height: 100%; min-height: 0; object-fit: cover; }
.hero--split .hero__photo--sub figcaption {
  position: absolute; left: 16px; bottom: 16px;
  padding: 8px 14px; border-radius: 999px;
  /* Same defect as the cinematic hero's pill: --paper label on the LIGHT
     --glass fill, floated over an arbitrary photograph. Dark chip instead, so
     the caption holds 4.5:1 whatever the photo behind it is doing. */
  background: color-mix(in oklab, var(--deepwash) 70%, transparent);
  border: 1px solid color-mix(in oklab, var(--paper) 38%, transparent);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--paper);
  display: inline-flex; gap: 10px; align-items: center;
}
@media (max-width: 980px) {
  .hero--split .hero__inner { grid-template-columns: 1fr; gap: 36px; }
  .hero--split .hero__signals { grid-template-columns: 1fr 1fr; }
  .hero--split .hero__right { height: clamp(320px, 42vh, 440px); }
}



.chapter--mosaic { padding: 120px 0; background: var(--paper); }
/* A "mosaic" that was two equal columns is a card grid with a different name,
   and it landed on the same 628/628 tracks as the reviews block directly above
   it. Six tracks with spans gives four tiles of three different widths. */
.mosaic { display: grid; grid-template-columns: repeat(6, 1fr); gap: 24px; }
.mosaic-tile:nth-child(4n+1) { grid-column: span 4; }
.mosaic-tile:nth-child(4n+2) { grid-column: span 2; }
.mosaic-tile:nth-child(4n+3) { grid-column: span 2; }
.mosaic-tile:nth-child(4n+4) { grid-column: span 4; }
.mosaic-tile {
  position: relative;
  aspect-ratio: 4/5;
  border-radius: 6px;
  overflow: hidden;
  background: var(--ink);
  color: var(--paper);
  transition: transform 480ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mosaic-tile:nth-child(4n+1) { aspect-ratio: 16/9; }
.mosaic-tile:nth-child(4n+2) { aspect-ratio: 4/5; align-self: end; }
.mosaic-tile:nth-child(4n+3) { aspect-ratio: 4/5; }
.mosaic-tile:nth-child(4n+4) { aspect-ratio: 2/1; align-self: end; }
.mosaic-tile:hover { transform: translateY(-4px); }
.mosaic-tile img { width: 100%; height: 100%; object-fit: cover; transition: transform 800ms cubic-bezier(0.22, 1, 0.36, 1); }
.mosaic-tile:hover img { transform: scale(1.04); }
.mosaic-tile__veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 38%, color-mix(in oklab, var(--ink) 86%, transparent) 100%);
}
/* Photoless tile: the ground carries the palette and a fine hatch instead of a
   picture, so the grid keeps its rhythm and the label keeps its contrast. */
.mosaic-tile--plate { background: color-mix(in oklab, var(--accent) 30%, var(--ink)); }
.mosaic-tile--plate::before {
  content: '';
  position: absolute; inset: -30%;
  background: repeating-linear-gradient(
    var(--plate-angle, 26deg),
    color-mix(in oklab, var(--paper) 12%, transparent) 0 1px,
    transparent 1px 12px
  );
}
.mosaic-tile--plate .mosaic-tile__veil {
  background: linear-gradient(180deg, transparent 30%, color-mix(in oklab, var(--ink) 70%, transparent) 100%);
}
.mosaic-tile__body {
  position: absolute; inset: auto 24px 24px;
  display: flex; flex-direction: column; gap: 6px;
}
.mosaic-tile__num { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em; color: var(--accent); text-transform: uppercase; }
.mosaic-tile__h { font-family: var(--font-display); font-weight: 500; font-size: clamp(22px, 2.4vw, 32px); margin: 0; line-height: 1.05; }
.mosaic-tile__body p { margin: 8px 0 0; font-size: 14px; color: color-mix(in oklab, var(--paper) 88%, transparent); max-width: 36ch; }
@media (max-width: 720px) {
  .mosaic { grid-template-columns: 1fr; }
  .mosaic-tile:nth-child(4n+1), .mosaic-tile:nth-child(4n+2),
  .mosaic-tile:nth-child(4n+3), .mosaic-tile:nth-child(4n+4) { grid-column: span 1; }
  .mosaic-tile, .mosaic-tile:nth-child(4n+1), .mosaic-tile:nth-child(4n+2),
  .mosaic-tile:nth-child(4n+3), .mosaic-tile:nth-child(4n+4) { aspect-ratio: 3/2; align-self: auto; }
}



.chapter--standard { padding: 120px 0; background: var(--wash); }
.standard__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: start; }
.standard__copy { display: flex; flex-direction: column; gap: 14px; }
.standard__lead { font-family: var(--font-display); font-size: clamp(22px, 2.4vw, 30px); line-height: 1.3; color: var(--ink); max-width: 56ch; }
.standard__lead i { color: var(--accent); font-style: italic; }
.standard__copy strong { font-weight: 600; color: var(--ink); }
.creed {
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px 24px;
  margin: 32px 0 0; padding-top: 28px;
  border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent);
}
.creed dt { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); margin-bottom: 6px; }
.creed dd { margin: 0; font-size: 14px; line-height: 1.45; color: var(--ink); }

.study { position: relative; }
.study__plate { position: relative; margin: 0; }
.study__frame {
  position: relative;
  aspect-ratio: 4/5; border-radius: 8px; overflow: hidden;
  background: var(--ink);
}
.study__frame img { width: 100%; height: 100%; object-fit: cover; }
.study__gloss {
  position: absolute; inset: 0;
  background: linear-gradient(115deg, transparent 30%, color-mix(in oklab, var(--paper) 22%, transparent) 45%, transparent 55%);
  mix-blend-mode: screen; pointer-events: none;
}
.study__cap {
  position: absolute; left: 18px; bottom: 18px;
  padding: 10px 14px; border-radius: 10px;
  background: var(--glass); border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  display: flex; flex-direction: column; gap: 2px;
}
.study__cap span { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); }
.study__cap strong { font-family: var(--font-display); font-weight: 500; font-size: 16px; color: var(--paper); }
.study__chips {
  position: absolute; right: -8px; top: 24px;
  display: flex; flex-direction: column; gap: 8px;
}
.study__chip {
  padding: 6px 12px; border-radius: 999px;
  background: var(--paper); border: 1px solid color-mix(in oklab, var(--ink) 12%, transparent);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent);
}
.study__specs { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; padding-top: 20px; border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent); }
.study__spec { display: flex; align-items: baseline; gap: 14px; }
.study__spec span { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em; color: var(--accent); min-width: 64px; }
.study__spec em { font-style: normal; font-size: 14px; color: var(--ink); }
@media (max-width: 880px) {
  .standard__grid { grid-template-columns: 1fr; gap: 40px; }
  .creed { grid-template-columns: 1fr; }
  .study__chips { position: relative; right: 0; top: 0; flex-direction: row; flex-wrap: wrap; margin-top: 16px; }
}



/* Deliberately short. 56px of padding, not 120px : this band is a rule with
   facts on it, not a chapter. */
.chapter--signals { padding: 54px 0; background: var(--deepwash); color: var(--paper); }
.signals {
  display: grid;
  /* Not an even split: cells carrying a sentence get twice the measure of
     cells carrying a number. Uniform columns here would be another identical
     grid on a page that already has too many. */
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: baseline;
  gap: 0;
}
.signal {
  display: flex; flex-direction: column; gap: 7px;
  padding: 2px 30px;
  border-left: 1px solid color-mix(in oklab, var(--paper) 20%, transparent);
}
.signal:first-child { border-left: 0; padding-left: 0; }
.signal--wide { grid-column: span 2; }
.signal__label {
  font-family: var(--font-mono); font-size: 10px;
  /* Positive tracking on micro-labels: the benchmark sets 9-13px mono at
     +0.15 to +0.19em. */
  letter-spacing: 0.18em; text-transform: uppercase;
  color: color-mix(in oklab, var(--paper) 62%, transparent);
}
.signal__value {
  font-family: var(--font-display); font-size: clamp(30px, 3.2vw, 46px);
  line-height: 1; letter-spacing: -0.025em;
  color: var(--accent-on-dark);
}
.signal__note { font-size: 14px; line-height: 1.4; color: color-mix(in oklab, var(--paper) 86%, transparent); }
@media (max-width: 880px) {
  .signals { grid-auto-flow: row; grid-template-columns: 1fr 1fr; gap: 26px 0; }
  .signal--wide { grid-column: span 2; }
  .signal { padding: 0 20px; border-left: 1px solid color-mix(in oklab, var(--paper) 18%, transparent); }
  .signal:nth-child(odd) { border-left: 0; padding-left: 0; }
  .signal--wide { border-left: 0; padding-left: 0; }
}



.chapter--answers { padding: 120px 0; background: var(--paper); }
/* Asymmetric: the header sits in a narrow left rail and the questions run in
   a wider right column, so this band does not share a shape with the two-up
   grids either side of it. */
.answers { display: grid; grid-template-columns: 0.62fr 1.38fr; gap: 72px; align-items: start; }
.answers__hd { position: sticky; top: 108px; }
.answers__hd .lede { margin-top: 18px; font-size: 16px; }
.answers__list { border-top: 1px solid color-mix(in oklab, var(--ink) 16%, transparent); }
.answer { border-bottom: 1px solid color-mix(in oklab, var(--ink) 12%, transparent); }
.answer__q {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 26px 0; cursor: pointer; list-style: none;
  font-family: var(--font-display); font-size: clamp(19px, 1.8vw, 25px);
  line-height: 1.25; letter-spacing: -0.012em;
}
.answer__q::-webkit-details-marker { display: none; }
.answer__mark {
  position: relative; flex: none; width: 15px; height: 15px;
}
.answer__mark::before, .answer__mark::after {
  content: ''; position: absolute; background: var(--accent);
  transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1), opacity 260ms;
}
.answer__mark::before { left: 0; top: 7px; width: 15px; height: 1px; }
.answer__mark::after { left: 7px; top: 0; width: 1px; height: 15px; }
.answer[open] .answer__mark::after { transform: rotate(90deg); opacity: 0; }
.answer__a { padding: 0 0 28px; max-width: 62ch; }
.answer__a p { margin: 0; color: var(--muted); font-size: 16px; line-height: 1.66; }
.answer[open] .answer__a p { animation: answerIn 520ms cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes answerIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (max-width: 880px) {
  .answers { grid-template-columns: 1fr; gap: 34px; }
  .answers__hd { position: static; }
  .answer__q { padding: 22px 0; }
}
@media (prefers-reduced-motion: reduce) {
  .answer[open] .answer__a p { animation: none; }
}



.chapter--area {
  position: relative; isolation: isolate; overflow: hidden;
  padding: 128px 0; color: var(--paper);
  background: var(--deepwash);
}
.area__media { position: absolute; inset: 0; z-index: -1; }
.area__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.area__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(94deg, color-mix(in oklab, var(--deepwash) 88%, transparent) 0%, color-mix(in oklab, var(--deepwash) 58%, transparent) 52%, color-mix(in oklab, var(--deepwash) 30%, transparent) 100%);
}
.area__inner { display: grid; grid-template-columns: 1.32fr 0.68fr; gap: 64px; align-items: end; }
.chapter--area .eyebrow { color: var(--accent-on-dark); }
.chapter--area .h2 { max-width: 15ch; }
.chapter--area .h2 i { color: var(--accent-on-dark); }
.chapter--area .lede { color: color-mix(in oklab, var(--paper) 84%, transparent); margin-top: 22px; max-width: 46ch; }
.area__map {
  display: inline-flex; align-items: center; gap: 10px; margin-top: 30px;
  padding-bottom: 6px;
  border-bottom: 1px solid color-mix(in oklab, var(--paper) 44%, transparent);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
}
.area__map:hover { border-bottom-color: var(--accent-on-dark); color: var(--accent-on-dark); }
.area__facts { margin: 0; display: flex; flex-direction: column; gap: 22px; }
.area__facts dt {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.18em;
  text-transform: uppercase; color: color-mix(in oklab, var(--paper) 60%, transparent); margin-bottom: 6px;
}
.area__facts dd { margin: 0; font-size: 15px; line-height: 1.45; }
@media (max-width: 880px) {
  .area__inner { grid-template-columns: 1fr; gap: 40px; align-items: start; }
  .area__scrim { background: linear-gradient(180deg, color-mix(in oklab, var(--deepwash) 82%, transparent), color-mix(in oklab, var(--deepwash) 62%, transparent)); }
}



.chapter--strip { padding: 120px 0; background: var(--wash); }
.strip { overflow-x: auto; padding-bottom: 8px; scrollbar-width: thin; }
.strip__inner { display: flex; gap: 20px; padding: 0 max(24px, calc((100vw - 1280px) / 2)); }
.strip__cell {
  flex: 0 0 clamp(280px, 38vw, 480px);
  aspect-ratio: 3/4; margin: 0;
  border-radius: 6px; overflow: hidden;
  background: var(--paper);
  position: relative;
}
.strip__cell img { width: 100%; height: 100%; object-fit: cover; }
.strip__cell figcaption {
  position: absolute; left: 16px; bottom: 16px;
  padding: 6px 12px; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--paper);
  display: inline-flex; gap: 8px;
}
.strip__cell figcaption .num { color: var(--accent); }



.chapter--about { padding: 120px 0; background: var(--paper); }
/* Was 1fr 1fr, the same shape as the standard chapter that usually sits two
   bands above it and the same 608/608 tracks the measurement kept reporting as
   an adjacent repeat. Off-centre split with the photo on the narrower side. */
.about__grid { display: grid; grid-template-columns: 0.78fr 1.22fr; gap: 72px; align-items: center; }
.about__photo {
  position: relative; margin: 0;
  aspect-ratio: 4/5; border-radius: 8px; overflow: hidden;
  background: var(--wash);
}
.about__photo img { width: 100%; height: 100%; object-fit: cover; }
.about__since {
  position: absolute; left: 16px; bottom: 16px;
  padding: 8px 14px; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--paper);
}
.about__copy p { font-size: 16px; line-height: 1.6; color: var(--ink); }
.about__pitch { font-family: var(--font-display); font-style: italic; font-weight: 400; font-size: clamp(20px, 1.8vw, 26px); line-height: 1.4; color: var(--ink); }
.team { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 32px; padding-top: 28px; border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent); }
.team__person { display: flex; flex-direction: column; gap: 4px; }
.team__role { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); }
.team__name { font-family: var(--font-display); font-size: 18px; color: var(--ink); }
@media (max-width: 880px) { .about__grid { grid-template-columns: 1fr; gap: 32px; } }



.reviews--glass { position: relative; padding: 120px 0; color: var(--paper); overflow: hidden; isolation: isolate; }
.reviews--glass .reviews__bg { position: absolute; inset: 0; z-index: -2; }
.reviews--glass .reviews__bg img { width: 100%; height: 100%; object-fit: cover; }
/* The variant brain sends a photoless build to quote-wall instead, but the
   weight floor in weightedPick means "never" is not a thing this codebase can
   express. So the glass still has something to sit on if it lands here. */
.reviews--glass .reviews__bg--photoless {
  background:
    radial-gradient(80% 60% at 20% 0%, color-mix(in oklab, var(--accent) 38%, transparent) 0%, transparent 62%),
    linear-gradient(160deg, color-mix(in oklab, var(--accent-deep, var(--accent)) 40%, var(--deepwash)) 0%, var(--deepwash) 70%);
}
.reviews--glass .reviews__veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, color-mix(in oklab, var(--deepwash) 60%, transparent), color-mix(in oklab, var(--deepwash) 92%, transparent));
}
.reviews--glass .reviews__head { display: flex; align-items: end; justify-content: space-between; flex-wrap: wrap; gap: 24px; margin-bottom: 56px; color: var(--paper); }
.reviews--glass .reviews__head .eyebrow { color: var(--accent); }
.reviews--glass .reviews__num { color: var(--paper); }
.reviews--glass .reviews__count { color: color-mix(in oklab, var(--paper) 65%, transparent); }
.reviews--glass .reviews__disc { color: color-mix(in oklab, var(--paper) 55%, transparent); }
/* Not repeat(2, 1fr): that is the same two-up as half the page. A 5-up run
   over 6 tracks gives the first quote real width and keeps the rest tight. */
.reviews--glass .reviews__grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 24px; }
.reviews--glass .reviews__grid > * { grid-column: span 3; }
.reviews--glass .reviews__grid > *:first-child { grid-column: span 6; }
.reviews--glass .reviews__grid > *:nth-child(4), .reviews--glass .reviews__grid > *:nth-child(5) { grid-column: span 3; }
.reviews--glass .rv {
  margin: 0; padding: 32px;
  background: color-mix(in oklab, var(--paper) 14%, transparent);
  border: 1px solid color-mix(in oklab, var(--paper) 22%, transparent);
  border-radius: 18px;
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  backdrop-filter: blur(22px) saturate(140%);
  color: var(--paper);
}
.reviews--glass .rv p { color: var(--paper); font-family: var(--font-display); font-size: 20px; line-height: 1.45; font-style: italic; font-weight: 400; margin: 0 0 16px; }
.reviews--glass .rv footer { display: flex; align-items: center; gap: 12px; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }
.reviews--glass .rv cite { font-style: normal; color: var(--paper); }
.reviews--glass .rv span { color: var(--accent); }
@media (max-width: 880px) {
  .reviews--glass .reviews__grid { grid-template-columns: 1fr; }
  .reviews--glass .reviews__grid > *, .reviews--glass .reviews__grid > *:first-child { grid-column: span 1; }
}



.chapter--contact { padding: 120px 0; background: var(--wash); }
.contact__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; }
/* No-booking layout: the lede holds the left column and the contact rows
   move to the right. The previous markup left the whole right column empty
   apart from one sentence, so the closing section of the site read half
   built. Left column is narrower so the display italic sets at poster scale. */
.contact__grid--direct { grid-template-columns: 0.85fr 1fr; gap: 72px; align-items: start; }
.contact__grid--direct .contact__lede { font-size: clamp(26px, 3vw, 40px); margin-bottom: 0; max-width: 18ch; }
.contact__direct { display: block; }
.contact__direct .contact__row:first-child { border-top: none; padding-top: 0; }
.contact__lede { font-family: var(--font-display); font-style: italic; font-size: clamp(22px, 2vw, 28px); line-height: 1.4; color: var(--ink); margin-bottom: 28px; }
.row__link { display: inline-block; margin-top: 8px; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent); }
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Hover effect uses transform (not padding) so we don't animate layout — design-system rule. */
.contact__row { display: grid; grid-template-rows: auto auto auto; padding: 18px 0; border-top: 1px solid color-mix(in oklab, var(--ink) 10%, transparent); transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1); }
.contact__row:hover { transform: translateX(8px); }
.row__label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); margin-bottom: 4px; }
.row__val { font-family: var(--font-display); font-size: clamp(20px, 2vw, 26px); color: var(--ink); }
.contact__row--call .row__val { font-size: clamp(24px, 2.4vw, 32px); }
.row__sub { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; color: var(--muted); margin-top: 4px; }
/* The open/closed badge (.row__state) is a shared primitive: the contact row
   and the footer both carry one. It lives in styles-core.css. */

.booking { padding: 32px; border-radius: 18px; }
.booking__head { margin-bottom: 24px; }
.booking__eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); }
.booking__h { font-family: var(--font-display); font-weight: 500; font-size: clamp(22px, 2vw, 28px); margin: 8px 0 18px; }
.booking__steps { list-style: none; padding: 0; margin: 0; display: flex; gap: 16px; }
.booking__steps li { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.booking__steps li span { width: 22px; height: 22px; border-radius: 50%; border: 1px solid color-mix(in oklab, var(--ink) 18%, transparent); display: inline-flex; align-items: center; justify-content: center; }
.booking__steps li.is-current { color: var(--ink); }
.booking__steps li.is-current span { background: var(--accent); color: var(--paper); border-color: var(--accent); }
.booking__panel { display: flex; flex-direction: column; gap: 14px; }

.field { display: flex; flex-direction: column; gap: 6px; }
.field span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted); }
.field input, .field select, .field textarea {
  padding: 12px 14px; border-radius: 10px;
  border: 1px solid color-mix(in oklab, var(--ink) 14%, transparent);
  background: var(--paper); color: var(--ink);
  font: inherit;
  transition: border-color 200ms ease, box-shadow 200ms ease;
  min-height: 44px;
}
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 22%, transparent);
}
/* Browser-native validation styles — only kick in after a submit attempt
   (when the form has the :invalid class). Pre-submit fields shouldn't be
   highlighted red just because they're empty. */
form.was-validated .field input:invalid,
form.was-validated .field select:invalid,
form.was-validated .field textarea:invalid {
  border-color: color-mix(in oklab, oklch(50% 0.18 25) 80%, transparent);
}
.booking__submit { margin-top: 8px; align-self: start; }
@media (max-width: 880px) {
  .contact__grid, .contact__grid--direct { grid-template-columns: 1fr; gap: 36px; }
  .contact__grid--direct .contact__lede { max-width: none; }
  .field-pair { grid-template-columns: 1fr; }
}



.chapter--hours { padding: 120px 0; background: var(--wash); }
.hours { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; max-width: 920px; margin-inline: auto; align-items: start; }
.hours__table { width: 100%; border-collapse: collapse; }
.hours__row { border-bottom: 1px solid color-mix(in oklab, var(--ink) 10%, transparent); }
.hours__row td { padding: 14px 4px; vertical-align: baseline; }
.hours__day { font-family: var(--font-display); font-size: 18px; color: var(--ink); }
.hours__time { font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.06em; color: var(--muted); text-align: right; }
.hours__row--today { background: color-mix(in oklab, var(--accent-light) 60%, transparent); }
.hours__row--today td { color: var(--ink); }
.hours__row--today .hours__time { color: var(--accent-deep); }
.hours__row--closed .hours__time { color: var(--muted); font-style: italic; }
.hours__today-pill { display: inline-block; margin-left: 10px; padding: 2px 8px; border-radius: 999px; background: var(--accent); color: var(--paper); font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.2em; text-transform: uppercase; vertical-align: middle; }

.hours__addr { display: flex; flex-direction: column; gap: 8px; padding: 24px; background: var(--paper); border-radius: 14px; border: 1px solid color-mix(in oklab, var(--ink) 8%, transparent); }
.hours__addr-lbl { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent-2); }
.hours__addr-val { font-family: var(--font-display); font-size: 18px; color: var(--ink); line-height: 1.4; }
.hours__map { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent); margin-top: 6px; }
@media (max-width: 720px) { .hours { grid-template-columns: 1fr; gap: 24px; } }



.chapter--giftcards { padding: 96px 0; background: var(--paper); }
.giftcards { max-width: 720px; margin: 0 auto; text-align: center; }
.giftcards__buttons { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-top: 24px; }
.giftcards__btn {
  display: inline-flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 18px 22px; background: var(--brand); color: var(--paper);
  text-decoration: none; border-radius: 8px; font: inherit; font-weight: 500;
  transition: transform 200ms ease, opacity 200ms ease;
}
.giftcards__btn:hover { transform: translateY(-2px); opacity: 0.92; }
.giftcards__btn-label { font-size: 15px; }
.giftcards__phone { margin-top: 18px; color: var(--muted); font-size: 14px; }
.giftcards__phone a { color: var(--accent-deep, var(--accent)); text-decoration: none; font-family: var(--font-mono); }


/* ==== ornaments (chapter dividers + contact backdrop) ==== */
.orn--vine { padding: 14px 0; color: var(--accent-2); display: flex; justify-content: center; background: var(--wash); }
.orn--vine svg { width: min(560px, 74%); height: 26px; }
.orn--compass {
  padding: 12px 0;
  background: var(--wash);
  color: var(--accent);
  display: flex; align-items: center; justify-content: center; gap: 18px;
}
.orn--compass svg { width: 30px; height: 30px; }
.orn--compass .orn__rule { width: 120px; height: 1px; background: color-mix(in oklab, var(--ink) 14%, transparent); }
.orn--compass .orn__label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.3em; color: var(--accent-2); }
.orn--wave {
  padding: 14px 0;
  background: var(--wash);
  color: var(--accent-deep);
  display: flex; justify-content: center;
}
.orn--wave svg { width: 90%; height: 24px; }

/* styles-responsive.css : the LAST layer in styles.css.
 *
 * Component stylesheets are concatenated after styles-core.css, so a mobile
 * correction written in core loses every specificity tie to a component's own
 * `padding: 120px 0`. This file is appended after all of them, which is the
 * only place a generator-wide responsive rule can actually win.
 *
 * Everything here is traced to a measurement against the hand-built demos:
 *   - desktop section padding 104px uniform, mobile 74px (0.71x)
 *   - mobile tap targets never below 44px (the 23px one in trueline is a
 *     defect, not a pattern to copy)
 *   - no horizontal scroll at 390px
 */

/* ==== background tone rotation ====
 * Stamped by the composer in assembleChapters(). These have to live in the
 * last layer: a component's own `.chapter--editorial { background: var(--paper) }`
 * is a single-class selector too, so an earlier rule of equal specificity
 * loses the tie. Ornament dividers already sit on --wash so a divider landing
 * between two paper bands still reads as a boundary. */
.chapter--tone-paper { background: var(--paper); }
.chapter--tone-wash { background: var(--wash); }

/* ==== mobile vertical rhythm ====
 * Every generated chapter shipped 120px top + 120px bottom unchanged to a
 * 844px viewport: 28% of the fold spent on air before a word is read. */
@media (max-width: 880px) {
  .chapter,
  section[class*="chapter--"],
  .reviews,
  .hours,
  .menu,
  .teasers {
    padding-top: 82px;
    padding-bottom: 82px;
  }
  .chapter__hd { margin-bottom: 42px; }
  /* Stacked columns need their own breathing room back, not the desktop gap. */
  .disciplines { gap: 56px; }
}
@media (max-width: 560px) {
  .chapter,
  section[class*="chapter--"],
  .reviews {
    padding-top: 68px;
    padding-bottom: 68px;
  }
}

/* ==== tap targets ====
 * Measured floor across the four generated demos was 15px, with 6-7 controls
 * per page under 44px. Inline links inside running prose are deliberately
 * excluded : a 44px box around a word mid-sentence breaks the line box. */
@media (pointer: coarse), (max-width: 880px) {
  .nav a,
  .nav button,
  footer a,
  .footer a,
  .hours a,
  .chapter--contact a,
  .contact a,
  [class*="__list"] a,
  [class*="__links"] a,
  [class*="__nav"] a,
  .footer__phone,
  .footer__contact a,
  .btn,
  button:not(.bot__launch) {
    min-height: 44px;
  }
  .footer__phone,
  .footer__contact a { display: inline-flex; align-items: center; }
  /* A phone number inside running prose cannot be given a 44px box without
     wrecking the line, so it gets an invisible overlay instead. This is the
     one control whose measured element height stays below 44px on purpose;
     the TOUCH target is 44px. */
  p a[href^="tel:"], li a[href^="tel:"] { position: relative; }
  p a[href^="tel:"]::after, li a[href^="tel:"]::after {
    content: ''; position: absolute; left: -6px; right: -6px;
    top: 50%; height: 44px; transform: translateY(-50%);
  }
  .nav a,
  .nav button,
  footer a,
  .footer a,
  [class*="__links"] a,
  [class*="__nav"] a {
    display: inline-flex;
    align-items: center;
  }
  /* Icon-only controls need width as well as height. */
  a[aria-label]:not(:has(span)),
  button[aria-label] {
    min-width: 44px;
    justify-content: center;
  }
}

/* ==== no horizontal scroll ==== */
@media (max-width: 880px) {
  .container > * { min-width: 0; }
  table, pre { max-width: 100%; overflow-x: auto; display: block; }
}
