/* ==========================================================================
   Woodside Memorial Funeral Home, LLC - design system.

   Hand-written, self-contained. No external font requests: validate.mjs fails
   the build on any @import or src:url pointing off-origin, so the display face
   is a system serif stack and the body face a system sans stack. Both stacks
   have Cyrillic coverage, because DEMO-HOUSE-RULES.md section 7 says a display
   font without it is one of the expensive traps to bake in on the English side.

   THIS HEADER IS REWRITTEN EVERY TIME THE PALETTE IS. It arrived describing a
   dance school's walnut and stage gold, and that build had inherited a header
   describing an auto electrician's graphite, which had itself inherited one
   describing a nail salon's aubergine. Three generations of a comment that was
   wrong about the tokens directly beneath it. Nothing checks a comment, which
   is exactly why it is worth saying so at the top of the file that keeps
   getting it wrong.

   Palette: slate, moss and stone.

   Chosen against this trade's stock moves, of which there are three and almost
   every funeral home makes one. The first is BLACK, GOLD AND A SERIF WITH
   SWASHES, which is a nineteen-eighties memorial card and reads as cheap - the
   brief says so in as many words. The second is A PHOTOGRAPH OF A SUNSET WITH
   A DOVE, which is a greetings card and says nothing at all. The third is
   HOSPITAL BLUE AND SANS-SERIF, which is a clinic and is the one thing a
   family does not want this to feel like.

   SLATE is a deep blue-grey with no black in it. It is the colour of a
   bluestone step, of a winter sky over Queens and of a good coat, and every
   photograph on this site was rendered in cool daylight or warm lamplight
   against pale plaster, so the dark bands and the pictures belong to each
   other rather than fighting.

   MOSS is the first accent and it is THE CARE: what this business actually
   does, every service page, every telephone number and every primary action.
   It is the green of the chairs in the chapel and of the hedging outside, and
   it is a living colour rather than a solemn one, which is the right note for
   the half of this site that is practical help.

   STONE is the second accent and it is THE BOUNDARY: the nine pages about what
   this funeral home does not do, the policies, and every demonstration notice.
   The two accents MEAN different things rather than alternating, which is the
   rule the hotel build settled - an accent that means nothing is decoration,
   and decoration is the first thing a visitor stops seeing.

   --iris is for dark grounds and --iris-dk for light ones. That split is not
   decoration: a sibling build shipped a single accent used on both and the
   audit found it failing contrast on white. Every accent below is stated for
   BOTH grounds. Contrast is checked for real by _tools/audit.mjs, not asserted
   here.

   AND THE HARDCODED rgba() VALUES ARE PART OF THE PALETTE. Changing the tokens
   below is not the swap. _tools/swap-palette.mjs did both halves when this
   build forked - the tokens, the renames from --brass/--sage to --iris/--amber,
   and eighteen literals that encoded a previous build's colours, two of them
   two generations stale - and then GREPPED FOR THE OLD VALUES AFTERWARDS
   rather than trusting the page to look right. Run it with --check any time.
   DEMO-HOUSE-RULES.md section 12.

   THE HERO'S TWO CHILDREN ARE BOTH POSITIONED - see .hero-media img, video
   below. Six builds in this portfolio left the poster and the video in normal
   flow, so the video was laid out beneath the clipped hero box and nobody ever
   saw it while every gate reported it playing. DEMO-HOUSE-RULES.md section 16.
   ========================================================================== */

:root {
  --ink:        #232b3a;   /* deep indigo-slate, taken from the wall in the hero photograph */
  --char:       #2e394b;
  --char-2:     #3d4a60;
  --char-3:     #69748a;
  --iris:       #a9b8e8;   /* periwinkle. THE EXAMINATION: the clinical half — services, appointments, findings, the booking flow. Dark grounds only. */
  --iris-dk:    #3a4a99;   /* its light-ground twin, for links and icons on --bone */
  --iris-lt:    #eef0fa;
  --amber:      #e0b877;   /* warm brass. THE FRAME ROOM: the retail half — the catalogue, the collections, the lenses, every price. Dark grounds only. */
  --amber-dk:   #8a6420;   /* its light-ground twin */
  --amber-lt:   #faf2e3;
  --signal:     #a8443a;   /* muted brick — the emergency copy and form validation, nothing else */
  --signal-lt:  #f9ece9;
  --bone:       #f7f7f9;
  --bone-2:     #edeef2;
  --paper:      #ffffff;
  --line:       #e0e1e8;
  --line-2:     #c4c6d1;
  --body:       #2b3342;   /* contrast measured by audit.mjs, not asserted here */
  --muted:      #586070;
  --muted-lt:   #656d7d;   /* small labels only */
  --warm-line:  #d3d9f2;   /* border of the --warm advice note, which is the IRIS one here */
  --warm-ink:   #2a3573;   /* its text */
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --wrap: 1200px;
  --r: 4px;
  --r-lg: 8px;
  --shadow: 0 1px 2px rgba(35, 43, 58, .05), 0 8px 24px -12px rgba(35, 43, 58, .20);
  --shadow-lg: 0 2px 4px rgba(35, 43, 58, .06), 0 24px 48px -20px rgba(35, 43, 58, .30);
}

/* ------------------------------------------------------------------ base */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font-family: var(--sans);
  /* 17/16 AS A REM, NOT 17px. Every sibling in this portfolio sets a pixel
     body size, and the whole stylesheet above and below is in rem — so the
     rem tree was anchored to a value a reader could not change. A browser's
     own "default font size" setting did nothing at all; only page zoom
     worked. On a site read by people in their seventies and eighties, at
     four in the morning, that is not a nicety. */
  font-size: 1.0625rem;
  line-height: 1.68;
  -webkit-font-smoothing: antialiased;
}

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

a { color: var(--char-2); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--iris-dk); }

h1, h2, h3, h4 {
  font-family: var(--serif);
  color: var(--ink);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: -.012em;
  margin: 0 0 .55em;
}
h1 { font-size: clamp(2.15rem, 4.4vw, 3.4rem); letter-spacing: -.02em; }
h2 { font-size: clamp(1.6rem, 2.9vw, 2.35rem); }
h3 { font-size: 1.24rem; }
h4 { font-size: 1.02rem; }
p  { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

ul, ol { margin: 0 0 1.1em; padding-left: 1.15em; }
li { margin-bottom: .4em; }

/* `strong` darkens body copy on the light surfaces that make up most of the
   site, but a hard-coded ink here repaints it INVISIBLE on every dark surface —
   that is exactly how the topbar's "24-hour emergency line" ended up at a
   contrast ratio of 1.00, ink on ink. Dark components set --strong instead of
   each needing its own override, so a new one inherits the right behaviour. */
strong { color: var(--strong, var(--ink)); font-weight: 650; }
hr { border: 0; border-top: 1px solid var(--line); margin: 3rem 0; }

::selection { background: var(--iris-lt); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--iris-dk);
  outline-offset: 3px;
  border-radius: 2px;
}

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 1.5rem; }
@media (max-width: 640px) { .wrap { padding-inline: 1.15rem; } }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  --strong: #fff; background: var(--char); color: #fff; padding: .8rem 1.2rem; border-radius: 0 0 var(--r) 0;
}
.skip:focus { left: 0; }

.eyebrow {
  font-family: var(--sans);
  font-size: .745rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--iris-dk);
  margin: 0 0 .85rem;
}
.lead {
  font-size: clamp(1.06rem, 1.5vw, 1.22rem);
  line-height: 1.62;
  color: var(--body);
}
.muted { color: var(--muted); }
.center { text-align: center; }

/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font-family: var(--sans); font-size: .935rem; font-weight: 600; letter-spacing: .005em;
  padding: .78rem 1.4rem;
  border-radius: var(--r);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .18s, color .18s, border-color .18s, transform .18s;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }

.btn--primary { --strong: #fff; background: var(--char); color: #fff; border-color: var(--char); }
.btn--primary:hover { background: var(--char-2); border-color: var(--char-2); color: #fff; }

.btn--ghost { background: transparent; color: var(--char); border-color: var(--line-2); }
.btn--ghost:hover { background: var(--bone-2); color: var(--char); border-color: var(--char-3); }

.btn--iris { background: var(--iris); color: var(--ink); border-color: var(--iris); }
.btn--iris:hover { background: var(--iris-lt); border-color: var(--iris-lt); color: var(--ink); }

.btn--light { background: rgba(255,255,255,.1); color: #fff; border-color: rgba(255,255,255,.34); }
.btn--light:hover { background: rgba(255,255,255,.2); color: #fff; border-color: rgba(255,255,255,.6); }

/* THE PRIMARY BUTTON ON A DARK GROUND IS COPPER, NOT INK.

   .btn--primary is a dark fill with white text: the strongest thing on a
   light section, and close to invisible on the hero, the ink band and the CTA
   band, where it is a dark rectangle on a dark rectangle separated only by
   its own edge. It MEASURED fine — white on #291f18 is sixteen to one — and
   it looked wrong, which is exactly the gap between a legibility check and a
   design one, and the reason the house rules say to look at the hero rather
   than trust that the image 200s.

   Stage gold on walnut is 9.1:1 and gold is the accent this stylesheet has already
   decided means THE WORK. .btn--iris has existed unused in this file for
   several forks; this is the same declaration, applied where it was needed. */
.hero .btn--primary,
.section--ink .btn--primary,
.cta-band .btn--primary {
  --strong: var(--ink);
  background: var(--iris); color: var(--ink); border-color: var(--iris);
}
.hero .btn--primary:hover,
.section--ink .btn--primary:hover,
.cta-band .btn--primary:hover {
  background: var(--iris-lt); border-color: var(--iris-lt); color: var(--ink);
}

.btn-row { display: flex; flex-wrap: wrap; gap: .7rem; }

.link-more {
  display: inline-flex; align-items: center; gap: .42rem;
  font-weight: 600; font-size: .93rem;
  color: var(--char-2); text-decoration: none;
}
.link-more:hover { color: var(--iris-dk); }
.link-more svg { transition: transform .18s; }
.link-more:hover svg { transform: translateX(3px); }

/* ---------------------------------------------------------------- topbar */

.topbar {
  --strong: #fff;
  background: var(--ink);
  color: rgba(255,255,255,.72);
  font-size: .795rem;
  border-bottom: 1px solid rgba(255,255,255,.07);
}
.topbar .wrap { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 40px; }
.topbar-left { display: flex; gap: 1.6rem; list-style: none; margin: 0; padding: 0; }
.topbar-left li { display: flex; align-items: center; gap: .42rem; margin: 0; }
.topbar-left svg { color: var(--iris); flex: none; }
.topbar-note { display: flex; align-items: center; gap: .42rem; white-space: nowrap; }
.topbar-note svg { color: var(--iris); }
.topbar a { color: #fff; text-decoration: none; font-weight: 600; }
.topbar a:hover { color: var(--iris-lt); }

@media (max-width: 900px) { .topbar-hide-sm { display: none; } }
/* 640, NOT 560. The inherited threshold was tuned to that build's topbar
   sentence. This one says "Closed Mondays · the bar takes no bookings" plus a
   phone number, which is longer, and the sweep found it overflowing by 21px
   across a 568-584px band on all 40 pages — just above where the wrap used to
   kick in. The lesson is that this breakpoint is a function of the SENTENCE,
   so it moves when the sentence does. */
@media (max-width: 640px) {
  .topbar-left li span { display: none; }
  .topbar .wrap { justify-content: center; }
  .topbar-left { display: none; }

  /* The note is nowrap on desktop so the icon, the promise and the phone
     number stay on one line. On a 390px phone that same string is 416px wide
     and pushes the whole document 13px sideways — a horizontal scroll on every
     page of the site, caused entirely by the header strip. Let it wrap and
     centre instead of trimming the copy to fit: the next edit to this sentence
     would otherwise silently reintroduce the overflow. */
  .topbar-note {
    white-space: normal;
    flex-wrap: wrap;
    justify-content: center;
    text-align: center;
    row-gap: .15rem;
  }
}

/* ---------------------------------------------------------------- header */

.site-header {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255,255,255,.96);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap { display: flex; align-items: center; gap: 1.5rem; min-height: 78px; }

/* A BRAND NAME IS A LAYOUT INPUT, NOT JUST CONTENT.
   `flex: none` here was sized around a two-word name. This build's name is
   five words, and on a 375px viewport it pushed the burger 60px past the
   right edge — every page of the site scrolled sideways on a phone, and the
   drawer inherited the inflated document width on top of that. The mark
   still refuses to shrink; the text is now allowed to. */
.brand { display: flex; align-items: center; gap: .72rem; text-decoration: none; flex: 0 1 auto; min-width: 0; }
.brand-mark { border-radius: 9px; flex: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1.16; min-width: 0; }
.brand-name {
  font-family: var(--serif); font-size: 1.16rem; font-weight: 600;
  color: var(--ink); letter-spacing: -.01em;
}
.brand-sub {
  font-size: .705rem; letter-spacing: .085em; text-transform: uppercase;
  color: var(--muted); margin-top: .22rem;
}

.nav { display: flex; align-items: center; gap: .2rem; margin-left: auto; }
.nav > a, .nav-drop > button {
  display: inline-flex; align-items: center; gap: .3rem;
  font-family: var(--sans); font-size: .935rem; font-weight: 550;
  color: var(--body); text-decoration: none;
  padding: .62rem .7rem; border-radius: var(--r);
  background: none; border: 0; cursor: pointer; line-height: 1;
  /* the brand lockup is wide enough here that a two-word label wraps mid-item
     at 1440 without this — the nav must shrink before a label breaks */
  white-space: nowrap;
}
.nav > a:hover, .nav-drop > button:hover { color: var(--char); background: var(--bone-2); }
.nav > a[aria-current="page"] { color: var(--char); font-weight: 650; }

/* THE PANEL IS POSITIONED AGAINST THE NAV BAR, NOT AGAINST ITS BUTTON.

   .nav-panel is centred with left:50% + translateX(-50%). With .nav-drop as
   the containing block that centres an 860px panel on whichever button opened
   it — fine for a single centred dropdown, which is what every sibling build
   has, and wrong the moment there are two. The "Visit" button sits well right
   of centre, so its panel ran 68px past the viewport on every page in a
   1024-1328px band. Centring both panels on the nav bar instead keeps them
   inside at every width and looks deliberate rather than accidental. */
.nav { position: relative; }
.nav-drop { position: static; }
.nav-drop > button svg { transition: transform .2s; }
.nav-drop[data-open="true"] > button { color: var(--char); background: var(--bone-2); }
.nav-drop[data-open="true"] > button svg { transform: rotate(180deg); }

.nav-panel {
  /* ANCHORED TO THE NAV'S RIGHT EDGE, NOT CENTRED ON IT.
     It was `left: 50%; translateX(-50%)`, which is house rules §12's fix
     ("centre it on .nav, not on its own button") — and §12's fix is not
     enough here, because `.nav` has `margin-left: auto` and therefore sits at
     the RIGHT of the header. An 860px panel centred on a right-aligned nav
     runs off the right of the page: measured at 443..1303 in a 1200px
     viewport, 103px past the edge, on all 49 pages, in a band the sweep found
     from 1024 to 1404.

     `visibility: hidden` does not remove it from the document's scroll extent
     — the same property §15 records for the closed mobile drawer, here on the
     desktop dropdown. Anchoring the right edge to the nav's own right edge
     makes the panel grow leftward into the page, where there is always room,
     and needs no clipping ancestor that would cut the panel off when open. */
  position: absolute; top: calc(100% + 10px); right: 0; transform: translateY(-6px);
  width: min(860px, calc(100vw - 3rem));
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.6rem 1.6rem 0;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s, transform .18s, visibility .18s;
}
.nav-drop[data-open="true"] .nav-panel {
  opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0);
}
.nav-panel-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem 2rem; }
.nav-col-h {
  font-size: .715rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--iris-dk); margin: 0 0 .7rem; padding-bottom: .55rem;
  border-bottom: 1px solid var(--line);
}
.nav-col a {
  display: block; font-size: .9rem; color: var(--body); text-decoration: none;
  padding: .34rem 0; border-radius: 2px;
}
.nav-col a:hover { color: var(--char); }
.nav-col a[aria-current="page"] { color: var(--char); font-weight: 650; }
.nav-panel-foot {
  display: flex; flex-wrap: wrap; gap: 1.4rem;
  margin: 1.4rem -1.6rem 0; padding: 1rem 1.6rem;
  background: var(--bone); border-top: 1px solid var(--line);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

.header-cta { display: flex; align-items: center; gap: .55rem; flex: none; }
.header-cta .btn { padding: .68rem 1.1rem; font-size: .89rem; }
.burger {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  background: none; border: 1px solid var(--line-2); border-radius: var(--r);
  color: var(--char); cursor: pointer;
}

/* THE DESKTOP BAR MUST FIT FROM 1024px WITH NOTHING WRAPPING — the standard
   the main site's CLAUDE.md sets, after a single extra nav link tipped its
   bar over and the fix turned out to be two dropdowns rather than a narrower
   breakpoint. The inherited value here was 1120px, because the build this was
   forked from carried one dropdown and four links. This one carries two
   dropdowns and two links, which is 392px of nav against 124px of slack at
   1121 — so 1024 is reachable and was measured rather than assumed. */
@media (max-width: 1023px) { .nav { display: none; } .burger { display: inline-flex; } }
@media (max-width: 780px) {
  .header-cta .btn--ghost, .header-cta .btn--primary { display: none; }
  .site-header .wrap { min-height: 66px; }
  .brand-sub { display: none; }
  .brand-name { font-size: 1.02rem; line-height: 1.14; }
}
@media (max-width: 430px) { .brand-name { font-size: .93rem; } }

/* ---------------------------------------------------------------- drawer */

/* `overflow: hidden` IS THE LOAD-BEARING DECLARATION HERE, not `visibility`.
   The panel inside is 380px wide at `translateX(100%)`, which puts it from the
   right edge of the viewport to 380px past it. `visibility: hidden` hides it
   and does NOT remove it from the document's scroll extent, so every page
   scrolled horizontally. House rules §15 found this on ONE page of forty-six
   on a sibling build and said every sibling has the same markup; here it was
   all forty-nine pages, in a band from 1024px to 1404px — which is 1024 plus
   the panel's own width, and is what named the culprit when the sweep could
   not. Below 1024 the drawer is the actual navigation and something else sets
   the scroll extent; above 1404 the wrap does.

   Clipping the fixed container costs nothing: when the drawer is open the
   panel is at translateX(0) and entirely inside the box. */
.drawer { position: fixed; inset: 0; z-index: 100; visibility: hidden; overflow: hidden; }
.drawer[data-open="true"] { visibility: visible; }
.drawer-veil {
  position: absolute; inset: 0; background: rgba(35, 43, 58,.5);
  opacity: 0; transition: opacity .25s;
}
.drawer[data-open="true"] .drawer-veil { opacity: 1; }
.drawer-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(380px, 88vw);
  background: var(--paper);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform .28s cubic-bezier(.4,0,.2,1);
  overflow-y: auto;
}
.drawer[data-open="true"] .drawer-panel { transform: none; }
.drawer-top {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--line); flex: none;
}
.drawer-close {
  width: 42px; height: 42px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line); border-radius: var(--r);
  color: var(--char); cursor: pointer;
}
.drawer nav { padding: 1rem 1.25rem; flex: 1; }
.drawer nav a {
  display: block; padding: .58rem 0; text-decoration: none;
  font-size: 1.02rem; font-weight: 600; color: var(--ink);
  border-bottom: 1px solid var(--bone-2);
}
.drawer nav a.sub { font-size: .93rem; font-weight: 500; color: var(--body); padding-left: .1rem; }
.drawer-sub {
  font-size: .7rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--iris-dk); margin: 1.25rem 0 .35rem;
}
.drawer-foot { padding: 1.25rem; border-top: 1px solid var(--line); background: var(--bone); flex: none; }
.drawer-foot p { display: flex; align-items: center; gap: .4rem; margin: 0; }
.drawer-foot svg { color: var(--iris-dk); }

/* ------------------------------------------------------------------ hero */

.hero { --strong: #fff; position: relative; background: var(--ink); color: #fff; overflow: hidden; }
.hero-media { position: absolute; inset: 0; }
/* BOTH CHILDREN ARE POSITIONED, AND THAT IS THE WHOLE FIX FOR A DEFECT THAT
   SHIPPED ON SIX SITES. .hero-media is `position: absolute; inset: 0`, and
   this rule used to set only width and height — so with BOTH a poster <img>
   and the <video> present, two block children at 100% stacked: the image
   filled the box and the video was laid out immediately below it, where
   `.hero { overflow: hidden }` clipped it away entirely. No gap, no
   scrollbar, no console error, and every gate green, because the <video>
   genuinely loaded and genuinely played — readyState 4, paused false,
   currentTime advancing. The question nobody put was "is it IN the box",
   which is a layout question that only getBoundingClientRect or a screenshot
   can answer. DEMO-HOUSE-RULES.md §16.

   The poster stays the LCP element, which is why it exists; the video follows
   it in the markup and paints over it once it starts. */
.hero-media img, .hero-media video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(35, 43, 58,.80) 0%, rgba(35, 43, 58,.60) 38%, rgba(35, 43, 58,.20) 72%, rgba(35, 43, 58,.06) 100%),
    linear-gradient(to top, rgba(35, 43, 58,.66), transparent 45%);
}
.hero-inner {
  position: relative; z-index: 2;
  padding: clamp(4rem, 9vw, 7.5rem) 0 clamp(3rem, 6vw, 5rem);
  max-width: 660px;
}
.hero h1 { color: #fff; margin-bottom: .5em; }
.hero .eyebrow { color: var(--iris); }
.hero .lead { color: rgba(255,255,255,.84); margin-bottom: 2rem; }
.hero .btn-row { margin-bottom: 2.6rem; }
/* THE HERO IS A DARK GROUND AND .btn--ghost IS NOT.
   .btn--ghost defaults to `color: var(--char)` on a light section, which
   on the hero put dark brown text on the near-black ground at about 1.1:1 —
   the telephone button beside "Book a consultation" was effectively invisible.
   .cta-band already carried this override and the hero never did; the same gap
   exists on the sibling builds, where the dark ground happened to be navy
   rather than espresso and the button was equally unreadable. */
.hero .btn--ghost { color: #fff; border-color: rgba(255,255,255,.4); }
.hero .btn--ghost:hover { background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.65); }

/* Default to an OPAQUE dark cell so the block is legible wherever it is used.
   The hero overrides to a translucent cell so the video shows through — the
   translucency only composites to something readable over a dark surface, and
   a stat block dropped onto a light section inherited it and went to 1.14:1
   before this was split. Any new usage now gets the safe version. */
.hero-stats {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px; background: var(--line-2);
  border-top: 1px solid var(--line-2);
}
.hero-stats div { --strong: #fff; background: var(--ink); padding: 1.4rem 1.35rem; }
.hero .hero-stats { background: rgba(255,255,255,.14); border-top-color: rgba(255,255,255,.14); }
.hero .hero-stats div { background: rgba(35, 43, 58,.55); }
.hero-stats dt {
  font-family: var(--serif); font-size: 1.75rem; color: var(--iris);
  line-height: 1; margin-bottom: .38rem;
}
.hero-stats dd {
  margin: 0; font-size: .82rem; color: rgba(255,255,255,.72); line-height: 1.42;
}
@media (max-width: 800px) { .hero-stats { grid-template-columns: repeat(2, 1fr); } }

/* -------------------------------------------------------------- pagehead */

.pagehead {
  --strong: #fff; position: relative; background: var(--ink); color: #fff; overflow: hidden;
  padding: clamp(2.6rem, 5.5vw, 4.4rem) 0 clamp(2.2rem, 4.5vw, 3.4rem);
}
.pagehead-media { position: absolute; inset: 0; }
.pagehead-media img { width: 100%; height: 100%; object-fit: cover; }
.pagehead-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(95deg, rgba(35, 43, 58,.95) 0%, rgba(35, 43, 58,.88) 42%, rgba(35, 43, 58,.55) 100%);
}
.pagehead .wrap { position: relative; z-index: 2; }
.pagehead h1 { color: #fff; max-width: 20ch; }
.pagehead .lead { color: rgba(255,255,255,.82); max-width: 62ch; }
.pagehead .eyebrow { color: var(--iris); }
.pagehead--plain { background: var(--char); }

.crumbs { margin-bottom: 1.5rem; }
.crumbs ol { display: flex; flex-wrap: wrap; gap: .4rem; list-style: none; margin: 0; padding: 0; font-size: .8rem; }
.crumbs li { margin: 0; color: rgba(255,255,255,.6); }
.crumbs li + li::before { content: "/"; margin-right: .4rem; color: rgba(255,255,255,.34); }
.crumbs a { color: rgba(255,255,255,.78); text-decoration: none; }
.crumbs a:hover { color: var(--iris-lt); }

.pagehead-meta {
  display: flex; flex-wrap: wrap; gap: .55rem 1.5rem;
  list-style: none; margin: 1.6rem 0 0; padding: 0;
}
.pagehead-meta li {
  display: flex; align-items: center; gap: .45rem; margin: 0;
  font-size: .875rem; color: rgba(255,255,255,.84);
}
.pagehead-meta svg { color: var(--iris); flex: none; }

/* The same inline icon-and-label list, on the hero's dark ground. The hero
   markup uses <ul><li>icon + <span>label</span></li></ul>, so the list marker
   has to be cleared explicitly — a bare <ul> inside .hero-inner otherwise
   inherits the prose bullet and stacks the icon above the text. */
.hero-meta {
  position: relative; z-index: 2;
  display: flex; flex-wrap: wrap; gap: .6rem 1.9rem;
  list-style: none; margin: 2rem 0 0; padding: 0;
}
.hero-meta li {
  display: flex; align-items: center; gap: .5rem; margin: 0;
  font-size: .9rem; color: rgba(255,255,255,.86);
}
.hero-meta svg { color: var(--iris); flex: none; }

/* -------------------------------------------------------------- sections */

.section { padding: clamp(3.2rem, 6.5vw, 5.5rem) 0; }
.section--alt { background: var(--bone); }
.section--tight { padding: clamp(2.4rem, 4.5vw, 3.6rem) 0; }
.section--ink { --strong: #fff; background: var(--ink); color: rgba(255,255,255,.78); }
.section--ink h2, .section--ink h3 { color: #fff; }
/* A DARK SECTION DOES NOT MAKE EVERYTHING INSIDE IT DARK-GROUND.
   .svc-card carries its own --paper background, so the blanket rule above
   painted white headings onto a white card: six invisible headings on the
   home page, found by audit.mjs at a contrast ratio of 1.00. This is the same
   family as the --gold-on-white 404 defect every sibling shipped — a value
   that is correct for one ground applied on the other — except that here it
   arrives through a DESCENDANT SELECTOR rather than through a token, which is
   why looking at the page does not catch it: there is nothing to see. Any
   component that sets its own light background has to restate its own text
   colours. */
.section--ink .svc-card { color: var(--body); }
.section--ink .svc-card h3 { color: var(--ink); }
.section--ink .svc-card p { color: var(--muted); }
/* A link-more sitting DIRECTLY on the dark ground inherits the light-ground
   accent from the base rule and lands at 1.34:1 — the same defect family the
   sibling build documented, arriving this time through inheritance rather
   than a descendant selector. The card rule below is more specific and keeps
   --iris-dk, because inside .svc-card the ground is the card's own paper. */
.section--ink .link-more { color: var(--iris); }
.section--ink .svc-card .link-more { color: var(--iris-dk); }
.section--ink .svc-card-tag { color: var(--iris-dk); }
.section--ink .eyebrow { color: var(--iris); }
.section--ink .lead { color: rgba(255,255,255,.8); }

.section-head { max-width: 68ch; margin-bottom: 2.6rem; }
.section-head.center { margin-inline: auto; }
.section-head p:last-child { margin-bottom: 0; }

.grid { display: grid; gap: 1.5rem; }
/* minmax(0, 1fr), NOT 1fr.

   A 1fr track has an automatic MINIMUM of min-content, so a child that
   refuses to shrink pushes the track wider than the container and the whole
   page scrolls sideways. This build has exactly such a child: .table-wrap >
   table carries min-width: 22rem so a menu or hours table stays readable, and
   it scrolls inside its own box — but only if the grid track is allowed to be
   narrower than it. With a bare 1fr the track grew to 352px instead, which
   the overflow sweep found as two separate bands: 320-368px on /contact.html
   and 648-744px on /hours-and-directions.html, where TWO such tables sit side
   by side. The carservice mirror settled that minmax(0,1fr) is the fix and
   that overflow-wrap is its partner for long words. */
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 980px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); } }

/* ----------------------------------------------------------------- cards */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.7rem;
}
.section--alt .card { background: var(--paper); }

.svc-card {
  display: flex; flex-direction: column;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  overflow: hidden; text-decoration: none; color: inherit;
  transition: border-color .2s, box-shadow .2s, transform .2s;
}
.svc-card:hover { border-color: var(--line-2); box-shadow: var(--shadow); transform: translateY(-2px); color: inherit; }
.svc-card img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
.svc-card-body { padding: 1.4rem 1.5rem 1.6rem; display: flex; flex-direction: column; flex: 1; }
.svc-card h3 { margin-bottom: .45rem; font-size: 1.14rem; }
.svc-card p { font-size: .92rem; color: var(--muted); margin-bottom: 1.1rem; }
.svc-card .link-more { margin-top: auto; }
.svc-card-tag {
  font-size: .68rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
  color: var(--iris-dk); margin-bottom: .55rem;
}

.promise {
  display: flex; flex-direction: column; gap: .85rem;
}
.promise-ic {
  width: 46px; height: 46px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--bone-2); color: var(--char); flex: none;
  border: 1px solid var(--line);
}
.section--ink .promise-ic { background: rgba(157, 192, 168,.16); border-color: rgba(157, 192, 168,.34); color: var(--iris); }
.promise h3 { font-size: 1.06rem; margin-bottom: .3rem; }
.promise p { font-size: .92rem; color: var(--muted); margin: 0; }
.section--ink .promise p { color: rgba(255,255,255,.68); }

.ind-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; background: var(--paper);
}
.ind-card img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.ind-card-body { padding: 1.3rem 1.4rem 1.5rem; }
.ind-card h3 { font-size: 1.08rem; margin-bottom: .4rem; }
.ind-card p { font-size: .9rem; color: var(--muted); margin: 0; }

/* ------------------------------------------------------------------ team */

.team-card {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden;
}
.team-card img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.team-card-body { padding: 1.35rem 1.45rem 1.6rem; }
.team-card h3 { font-size: 1.1rem; margin-bottom: .18rem; }
.team-role { font-size: .875rem; color: var(--char-2); font-weight: 600; margin: 0 0 .1rem; }
.team-creds {
  font-size: .715rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--iris-dk); font-weight: 700; margin: 0 0 .9rem;
}
.team-card p.bio { font-size: .91rem; color: var(--muted); margin-bottom: 1rem; }
.team-focus { list-style: none; margin: 0 0 .9rem; padding: 0; }
.team-focus li {
  display: flex; align-items: flex-start; gap: .45rem;
  font-size: .855rem; color: var(--body); margin-bottom: .3rem;
}
.team-focus svg { color: var(--iris-dk); flex: none; margin-top: .28em; }
.team-langs {
  font-size: .8rem; color: var(--muted); padding-top: .85rem; border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: .42rem; margin: 0;
}
.team-langs svg { color: var(--iris-dk); flex: none; }

/* ----------------------------------------------------------------- split */

.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
/* CENTRING IS RIGHT FOR IMAGE + PROSE AND WRONG FOR TWO COLUMNS OF PROSE.
   A photograph beside a paragraph wants its centre on the paragraph's centre;
   two blocks of text of different lengths centred against each other read as
   misaligned, because the eye takes the top edge as the baseline. This build
   uses .split for both, so the two-column case opts into top alignment — the
   same fix the law firm build made to its contact and attorney pages, carried
   here as a modifier rather than re-derived. It is used on 102 blocks and was
   emitted for every one of them before it existed: the class-coverage
   assertion in validate.mjs is what found that, and no screenshot would have,
   because "centred" and "top-aligned" both look deliberate. */
.split--top { align-items: start; }
.split--rev .split-media { order: 2; }
.split-media img { width: 100%; height: auto; border-radius: var(--r-lg); }
.split-copy h2 { margin-bottom: .6em; }
@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; gap: 2rem; }
  .split--rev .split-media { order: 0; }
}

/* ----------------------------------------------------------------- lists */

.checklist { list-style: none; margin: 0 0 1.2em; padding: 0; }
.checklist li {
  display: flex; align-items: flex-start; gap: .6rem;
  margin-bottom: .6rem; font-size: .96rem;
}
.checklist svg { color: var(--iris-dk); flex: none; margin-top: .3em; }
.section--ink .checklist svg { color: var(--iris); }

.deflist { list-style: none; margin: 0; padding: 0; }
.deflist > li {
  padding: 1.15rem 0; border-bottom: 1px solid var(--line); margin: 0;
}
.deflist > li:first-child { padding-top: 0; }
.deflist > li:last-child { border-bottom: 0; padding-bottom: 0; }
.deflist h3 {
  font-family: var(--sans); font-size: 1rem; font-weight: 650;
  color: var(--ink); margin-bottom: .3rem;
}
.deflist p { font-size: .93rem; color: var(--muted); margin: 0; }

.numbered { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.numbered > li {
  counter-increment: step;
  position: relative; padding-left: 3.4rem; margin-bottom: 1.7rem;
}
.numbered > li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: -.1rem;
  font-family: var(--serif); font-size: 1.35rem; color: var(--iris-dk);
  border-right: 1px solid var(--line); padding-right: .85rem; width: 2.55rem; text-align: right;
}
.numbered h3 { font-size: 1.08rem; margin-bottom: .3rem; }
.numbered p { font-size: .93rem; color: var(--muted); margin: 0; }

/* ------------------------------------------------------------------ menu */

.menu { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--paper); overflow: hidden; }
.menu-head { padding: 1.6rem 1.8rem; background: var(--bone); border-bottom: 1px solid var(--line); }
.menu-head h2 { font-size: 1.4rem; margin-bottom: .3rem; }
.menu-head p { font-size: .875rem; color: var(--muted); margin: 0; }
.menu-list { list-style: none; margin: 0; padding: 0 1.8rem; }
.menu-list li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--bone-2); margin: 0;
}
.menu-list li:last-child { border-bottom: 0; }
.menu-item h3 { font-family: var(--sans); font-size: .99rem; font-weight: 650; margin-bottom: .18rem; }
.menu-item p { font-size: .875rem; color: var(--muted); margin: 0; }
.menu-price {
  font-family: var(--serif); font-size: 1.06rem; color: var(--char); font-weight: 600;
  white-space: nowrap; flex: none;
}
@media (max-width: 560px) {
  .menu-head, .menu-list { padding-inline: 1.25rem; }
  .menu-list li { flex-direction: column; gap: .3rem; }
}

/* ------------------------------------------------------------------- faq */

.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5rem;
  padding: 1.25rem 0; cursor: pointer; list-style: none;
  font-family: var(--serif); font-size: 1.1rem; font-weight: 600; color: var(--ink);
  line-height: 1.34;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; flex: none; width: 11px; height: 11px; margin-top: .42em;
  border-right: 1.5px solid var(--iris-dk); border-bottom: 1.5px solid var(--iris-dk);
  transform: rotate(45deg); transition: transform .2s;
}
.faq details[open] summary::after { transform: rotate(-135deg); }
.faq summary:hover { color: var(--char-2); }
.faq-body { padding: 0 0 1.35rem; max-width: 78ch; }
.faq-body p { font-size: .96rem; color: var(--muted); margin: 0; }

/* --------------------------------------------------------------- voices */

.voice {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 1.9rem 2rem;
  display: flex; flex-direction: column;
}
.voice-ic { color: var(--iris); margin-bottom: .85rem; }
/* the takeaway line, then the quote itself set off by a brass rule — a heading
   here competed with the quote for the same job */
.voice-what {
  font-family: var(--serif); font-size: 1.17rem; font-weight: 600; line-height: 1.3;
  color: var(--ink); margin: 0 0 1rem;
}
.voice blockquote { margin: 0 0 1.4rem; padding-left: 1.15rem; border-left: 2px solid var(--iris-lt); }
.voice blockquote p { font-size: .965rem; color: var(--muted); margin: 0; }
.voice figcaption { margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--line); }
.voice cite {
  font-style: normal; font-size: .81rem; font-weight: 650;
  letter-spacing: .045em; color: var(--char-2);
}
@media (max-width: 560px) { .voice { padding: 1.5rem 1.4rem; } }

/* -------------------------------------------------------------- calendar */

.cal { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.cal li {
  display: grid; grid-template-columns: 190px 1fr; gap: 1.5rem;
  padding: 1.25rem 0; border-bottom: 1px solid var(--line); margin: 0;
}
.cal .cal-when {
  font-family: var(--serif); font-size: 1.1rem; color: var(--char); font-weight: 600;
}
.cal p { font-size: .95rem; color: var(--muted); margin: 0; }
@media (max-width: 700px) { .cal li { grid-template-columns: 1fr; gap: .3rem; } }

/* --------------------------------------------------------------- gallery */

.gallery { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; }
.gallery img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--r); }
.gallery figure { margin: 0; }
.gallery figcaption { font-size: .8rem; color: var(--muted); margin-top: .5rem; }
@media (max-width: 900px) { .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---------------------------------------------------------------- areas */

.chips { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 0; padding: 0; }
.chips li {
  margin: 0; font-size: .85rem; color: var(--body);
  padding: .38rem .8rem; border: 1px solid var(--line-2); border-radius: 999px; background: var(--paper);
}
.section--ink .chips li { border-color: rgba(255,255,255,.22); color: rgba(255,255,255,.82); background: rgba(255,255,255,.05); }

/* ------------------------------------------------------------------ cta */

.cta-band { --strong: #fff; background: var(--char); color: rgba(255,255,255,.82); padding: clamp(2.8rem, 5vw, 4rem) 0; }
.cta-inner {
  display: grid; grid-template-columns: 1fr auto; gap: 2rem; align-items: center;
}
.cta-band h2 { color: #fff; margin-bottom: .5em; }
.cta-band .eyebrow { color: var(--iris); }
.cta-band .lead { color: rgba(255,255,255,.8); max-width: 62ch; margin: 0; }
.cta-band .btn--primary { background: var(--iris); color: var(--ink); border-color: var(--iris); }
.cta-band .btn--primary:hover { background: var(--iris-lt); border-color: var(--iris-lt); }
.cta-band .btn--ghost { color: #fff; border-color: rgba(255,255,255,.36); }
.cta-band .btn--ghost:hover { background: rgba(255,255,255,.1); color: #fff; border-color: rgba(255,255,255,.6); }
@media (max-width: 860px) { .cta-inner { grid-template-columns: 1fr; } }

/* --------------------------------------------------------- advice note */

.advice-note {
  display: flex; align-items: flex-start; gap: .65rem;
  background: var(--bone); border: 1px solid var(--line); border-left: 3px solid var(--iris);
  border-radius: var(--r); padding: 1rem 1.2rem;
  font-size: .855rem; color: var(--muted); line-height: 1.58;
  margin: 2.4rem 0 0;
}
.advice-note svg { color: var(--iris-dk); flex: none; margin-top: .2em; }
/* The handling and safety note. Same shape, warmer ground — it is a promise
   rather than a caveat, and it should not read like small print. */
.advice-note--warm { background: var(--iris-lt); border-color: var(--warm-line); border-left-color: var(--iris-dk); color: var(--warm-ink); }
.advice-note--warm strong { color: var(--ink); }
.advice-note + .advice-note { margin-top: 1rem; }

/* ----------------------------------------------------------- contact */

/* minmax(0, ...) RATHER THAN A BARE fr, and .info-card carries the partner
   rule. DEMO-HOUSE-RULES.md section 12: an fr track has an automatic
   min-content minimum, so a child whose longest unbreakable run is wider than
   the column grows the TRACK instead of wrapping — and the sweep found exactly
   that here, an .info-card 18px wider than the viewport in the 320-336px band
   and nowhere else. Two viewports would never have seen it. */
.contact-grid { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
@media (max-width: 900px) { .contact-grid { grid-template-columns: minmax(0, 1fr); } }

.form { display: grid; gap: 1.1rem; }
.form .row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
@media (max-width: 560px) { .form .row { grid-template-columns: 1fr; } }
.form label { display: block; font-size: .85rem; font-weight: 650; color: var(--ink); margin-bottom: .38rem; }
.form input, .form select, .form textarea {
  width: 100%; font-family: var(--sans); font-size: .96rem; color: var(--body);
  padding: .72rem .85rem;
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r);
}
.form textarea { min-height: 140px; resize: vertical; }
.form input:focus, .form select:focus, .form textarea:focus {
  outline: none; border-color: var(--char-3); box-shadow: 0 0 0 3px rgba(58, 74, 153,.14);
}
.form .check { display: flex; align-items: flex-start; gap: .6rem; font-size: .87rem; color: var(--muted); }
.form .check input { width: auto; margin-top: .25em; flex: none; }
.form .hint { font-size: .81rem; color: var(--muted); margin: 0; }

.info-card {
  background: var(--bone); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.7rem;
  min-width: 0; overflow-wrap: anywhere;
}
@media (max-width: 380px) { .info-card { padding: 1.15rem; } }
.info-card + .info-card { margin-top: 1.25rem; }
.info-card h3 { font-size: 1.1rem; margin-bottom: .9rem; }
.info-list { list-style: none; margin: 0; padding: 0; }
.info-list li {
  display: flex; align-items: flex-start; gap: .65rem; margin-bottom: .8rem; font-size: .93rem;
}
.info-list li:last-child { margin-bottom: 0; }
.info-list svg { color: var(--iris-dk); flex: none; margin-top: .22em; }
.info-list a { color: var(--char-2); }

.hours-table { width: 100%; border-collapse: collapse; font-size: .91rem; }
.hours-table th, .hours-table td { text-align: left; padding: .45rem 0; border-bottom: 1px solid var(--line); }
.hours-table th { font-weight: 600; color: var(--ink); }
.hours-table td { color: var(--muted); text-align: right; }
.hours-table tr:last-child th, .hours-table tr:last-child td { border-bottom: 0; }

/* The two-column opening-hours table right-aligns its single value column, which
   is right for "Monday .... 5:00 am - 10:00 pm" and wrong for the five-column
   class timetable, where it pushed every class name up against the next column.
   A multi-column table reads as a grid, so everything goes left and only the
   trailing numeric column stays right. */
/* THE TRAILING COLUMN IS NOT A PRICE HERE, SO IT IS NOT RIGHT-ALIGNED.
   The rule above was written for a table whose last column was a number.
   On this site the three .timetable uses end in "12–14 minutes", "11:00am –
   10:00pm" and a whole sentence of prose about where an allergen turns up —
   and a ragged-left paragraph is simply harder to read. Same family of
   mistake as the nowrap on .emerg a: a rule that is correct for a number is
   not a rule about last columns. */
.timetable td, .timetable th { text-align: left; padding-right: 1.2rem; vertical-align: top; }
.timetable td:last-child, .timetable th:last-child { padding-right: 0; }
.timetable thead th {
  font-size: .74rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; border-bottom-color: var(--line-2);
}
.timetable tbody th { white-space: nowrap; font-variant-numeric: tabular-nums; }
.timetable tbody td { color: var(--body); }
.timetable tbody td:nth-child(3), .timetable tbody td:nth-child(4) { color: var(--muted); }
@media (max-width: 640px) {
  .timetable { font-size: .84rem; }
  .timetable td, .timetable th { padding-right: .7rem; }
  /* NO column is hidden on mobile. The sibling dropped its fourth column
     because that one was decorative; here column four is the oven a pizza
     is baked in, on the page whose entire purpose is comparing that. Every
     .timetable on this site sits inside an overflow-x:auto wrapper, so a
     narrow screen scrolls the table rather than losing a column of it. */
}

/* ----------------------------------------------------------- prose page */

.prose { max-width: 74ch; }
.prose h2 { margin-top: 2.6rem; font-size: 1.5rem; }
.prose h3 { margin-top: 1.9rem; font-size: 1.13rem; font-family: var(--sans); font-weight: 650; }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }
.prose ul { padding-left: 1.2em; }
.prose li { margin-bottom: .5em; }

/* ---------------------------------------------------------------- error */

.err { text-align: center; padding: clamp(4rem, 10vw, 8rem) 0; }
.err-code {
  font-family: var(--serif); font-size: clamp(4rem, 12vw, 8rem); line-height: 1;
  /* --iris is the accent FOR DARK GROUNDS: 9.5:1 on --ink and 1.95:1 on
     white, which is what this numeral sat at until audit.mjs measured it.
     Large text still needs 3:1, and "decorative" is not a category a screen
     reader knows about — the 404 is the first thing on the page and it says
     what went wrong. --iris-dk is the same hue at 5.5:1. */
  color: var(--iris-dk); margin-bottom: .3rem;
}

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

.site-footer { --strong: #fff; background: var(--ink); color: rgba(255,255,255,.68); padding: clamp(3rem, 6vw, 4.5rem) 0 2rem; font-size: .92rem; }
.site-footer .brand-name { color: #fff; }
.site-footer .brand-sub { color: rgba(255,255,255,.5); }
.footer-grid {
  display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: 2.5rem;
  padding-bottom: 2.6rem; border-bottom: 1px solid rgba(255,255,255,.12);
}
@media (max-width: 980px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; } }
@media (max-width: 600px) { .footer-grid { grid-template-columns: 1fr; } }

.footer-brand .brand { margin-bottom: 1.1rem; }
.footer-brand p { font-size: .89rem; color: rgba(255,255,255,.6); max-width: 42ch; }
.footer-langs { display: flex; flex-wrap: wrap; gap: .4rem; list-style: none; margin: 1.1rem 0 0; padding: 0; }
.footer-langs li {
  margin: 0; font-size: .74rem; letter-spacing: .04em;
  padding: .22rem .6rem; border: 1px solid rgba(255,255,255,.16); border-radius: 999px;
  color: rgba(255,255,255,.62);
}
.site-footer h4 {
  color: #fff; font-family: var(--sans); font-size: .74rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; margin-bottom: 1rem;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: .48rem; font-size: .875rem; }
.site-footer a { color: rgba(255,255,255,.68); text-decoration: none; }
.site-footer a:hover { color: var(--iris); }
.footer-contact li { display: flex; align-items: flex-start; gap: .55rem; }
.footer-contact svg { color: var(--iris); flex: none; margin-top: .2em; }

.footer-bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.6rem 0 1.2rem; font-size: .8rem; color: rgba(255,255,255,.5);
}
.footer-bar ul { display: flex; flex-wrap: wrap; gap: 1.1rem; }
.footer-bar li { margin: 0; font-size: .8rem; }

.footer-disclaimer {
  font-size: .785rem; line-height: 1.6; color: rgba(255,255,255,.44);
  border-top: 1px solid rgba(255,255,255,.1); padding-top: 1.2rem; margin: 0; max-width: 100ch;
}
.footer-disclaimer strong { color: rgba(255,255,255,.72); }

/* ----------------------------------------------- maintenance plan cards */

.plans {
  display: grid; gap: 1.5rem; align-items: start;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 980px) { .plans { grid-template-columns: 1fr; max-width: 34rem; margin-inline: auto; } }

.plan {
  position: relative; display: flex; flex-direction: column; height: 100%;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 2rem 1.7rem 1.8rem; box-shadow: var(--shadow);
}
.plan--featured {
  /* the flag floats above the top edge, so no extra padding is needed here —
     matching padding keeps the three price baselines on one line */
  border-color: var(--char); box-shadow: var(--shadow-lg);
}
.plan-flag {
  position: absolute; top: -.75rem; left: 1.7rem;
  background: var(--iris); color: var(--ink);
  font-family: var(--sans); font-size: .7rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  padding: .3rem .7rem; border-radius: 2px; margin: 0;
}
.plan h3 { font-size: 1.42rem; margin-bottom: .35rem; }

.plan-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; margin: 0 0 .9rem; }
.plan-price strong {
  font-family: var(--serif); font-size: 2.5rem; font-weight: 600;
  color: var(--ink); letter-spacing: -.02em; line-height: 1;
}
.plan-price span { font-size: .84rem; color: var(--muted); }

.plan-blurb { font-size: .93rem; color: var(--body); margin: 0 0 1.3rem; }

.plan-list { list-style: none; margin: 0 0 1.3rem; padding: 0; border-top: 1px solid var(--line); }
.plan-list li {
  display: flex; gap: .6rem; align-items: flex-start; margin: 0;
  padding: .62rem 0; border-bottom: 1px solid var(--line); font-size: .89rem; line-height: 1.5;
}
.plan-list svg { color: var(--iris-dk); flex: none; margin-top: .18em; }

.plan-best { font-size: .82rem; color: var(--muted); margin: 0 0 1.3rem; }
.plan-best strong { color: var(--body); }
.plan .btn { margin-top: auto; width: 100%; justify-content: center; }

/* ------------------------------------------------------- emergency strip */

/* THE FIRST-CALL STRIP WEARS --iris, NOT --signal, AND THAT IS THE PALETTE'S
   OWN RULE APPLIED. This stylesheet's header says --iris is THE CARE (what
   this business does, every primary action, the telephone number) and
   --signal is THE DEADLINE AND THE REFUSAL. The most important, most caring
   sentence on the site is not a refusal.

   It arrived in the signal family from the parent build, with a --iris-dk
   telephone icon sitting inside a pink box: two accents disagreeing inside
   one component. It also read as a validation error, which on a page somebody
   reaches at three in the morning is the wrong note entirely. Only a
   screenshot showed it; every contrast check passed on both versions. */
.emerg {
  display: flex; gap: .8rem; align-items: flex-start;
  background: var(--iris-lt); border: 1px solid #d3d9f2; border-left: 3px solid var(--iris-dk);
  border-radius: var(--r); padding: .95rem 1.15rem; margin: 0 0 2rem;
}
.emerg svg { color: var(--iris-dk); flex: none; margin-top: .18em; }
/* min-width:0 because a flex item defaults to min-width:auto, which refuses
   to shrink below its own min-content width. Combined with the nowrap below
   that produced a 436px-wide paragraph inside a 338px strip on a 375px
   phone — 115px of horizontal overflow on the home page, and the only
   layout fault in 116 live page loads. */
.emerg p { margin: 0; min-width: 0; font-size: .93rem; line-height: 1.55; }
.emerg strong { color: var(--ink); }
/* NO nowrap. The sibling build put one here because the only link in its
   emergency strip was a telephone number, where keeping "000-000-0000"
   on one line is right. The link in this strip is a whole sentence, and
   nowrap made it an unbreakable 436px word. A rule that is correct for a
   phone number is not a rule about links. */
.emerg a { font-weight: 600; }

/* ------------------------------------------------------------- printing */

@media print {
  .topbar, .site-header, .drawer, .cta-band, .site-footer, .hero-media { display: none !important; }
  body { font-size: 11pt; color: #000; }
  a { text-decoration: none; color: #000; }
}


/* ==========================================================================
   PIZZERIA COMPONENTS
   The blocks this build adds on top of the shared system. Everything above
   is inherited; everything below exists because a menu has shapes a clinic
   does not — a price ladder per item, a dietary marker that has to be
   readable at a glance, and a captioned figure for a photograph of food.
   ========================================================================== */

/* ------------------------------------------------- sizes and prices ---
   Every menu page carries one. A restaurant site that makes you telephone
   to discover a price is hiding the price, so the ladder is on the page. */

.sizes {
  background: var(--bone);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.7rem 1.8rem 1.5rem;
}
.sizes h2 { font-size: 1.28rem; margin-bottom: 1.1rem; }
.sizes ul { list-style: none; margin: 0 0 1.1rem; padding: 0; }
.sizes li {
  display: flex; align-items: baseline; gap: .9rem;
  padding: .72rem 0;
  border-bottom: 1px dashed var(--line-2);
  margin: 0;
}
.sizes li:last-child { border-bottom: 0; }
.sizes li > span:first-child { flex: 1 1 auto; color: var(--body); }
.sizes-price {
  flex: none;
  font-family: var(--serif);
  font-size: 1.14rem;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sizes-note { font-size: .84rem; color: var(--muted); margin: 0; line-height: 1.55; }

@media (max-width: 480px) {
  .sizes { padding: 1.3rem 1.2rem 1.2rem; }
}

/* --------------------------------------------------- dietary markers ---
   Vegetarian and spicy, as small type rather than as an emoji or a coloured
   dot: a marker nobody can read is decoration, and on this site the dietary
   markers link to the page that explains what they do NOT mean. */

.badges { display: flex; flex-wrap: wrap; gap: .45rem; list-style: none; margin: 0 0 1.1rem; padding: 0; }
.badges li { margin: 0; }
.badge {
  display: inline-flex; align-items: center; gap: .34rem;
  font-family: var(--sans); font-size: .735rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  padding: .28rem .6rem;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  color: var(--muted);
  background: var(--paper);
  text-decoration: none;
  white-space: nowrap;
}
a.badge:hover { border-color: var(--iris-dk); color: var(--iris-dk); }
.badge svg { flex: none; }
.badge--veg { border-color: #a9b8e8; color: #3a4a99; background: #eef0fa; }
a.badge--veg:hover { border-color: #3a4a99; color: #26307a; }
.badge--heat { border-color: #edd2ce; color: var(--signal); background: var(--signal-lt); }
a.badge--heat:hover { border-color: var(--signal); color: #7a2d24; }

/* --------------------------------------------------- captioned figure ---
   A photograph of food with a line under it. Used wherever the caption is
   part of the honesty rather than a nicety — the slice counter, the
   gluten-free base, the delivery photographs. */

.figcap { margin: 0; }
.figcap img { border-radius: var(--r-lg); display: block; width: 100%; height: auto; }
.figcap figcaption {
  margin-top: .85rem; padding-left: .9rem;
  border-left: 2px solid var(--iris-dk);
  font-size: .83rem; line-height: 1.5; color: var(--muted);
}
.figcap figcaption strong { color: var(--body); font-weight: 600; }
.figcap figcaption a { color: var(--iris-dk); }

/* ------------------------------------------------------- spec strip ---
   The four numbers that define a pizza style: hydration, ferment, oven,
   bake. Reads as a spec sheet, which is what it is. */

.spec { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; margin: 0 0 1.6rem; }
.spec div { background: var(--bone); padding: .9rem 1rem; }
.spec dt { font-size: .705rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--muted-lt); margin-bottom: .3rem; }
.spec dd { margin: 0; font-family: var(--serif); font-size: 1.02rem; color: var(--ink); line-height: 1.3; }
@media (max-width: 700px) { .spec { grid-template-columns: repeat(2, 1fr); } }

/* -------------------------------------------------------- menu index ---
   /menu.html — the whole card, section by section, with prices inline.
   Deliberately dense: this is the page people actually read. */

.mcard {
  display: grid; grid-template-columns: 96px 1fr auto; gap: 1.15rem;
  align-items: start;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
}
.mcard:last-child { border-bottom: 0; }
.mcard:hover { color: inherit; }
.mcard:hover h3 { color: var(--iris-dk); }
.mcard img { width: 96px; height: 96px; border-radius: var(--r); object-fit: cover; }
.mcard h3 { font-size: 1.1rem; margin: 0 0 .3rem; transition: color .18s; }
.mcard p { font-size: .9rem; color: var(--muted); margin: 0 0 .45rem; line-height: 1.55; }
.mcard-from {
  flex: none; text-align: right;
  font-family: var(--serif); font-size: 1.08rem; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mcard-from small { display: block; font-family: var(--sans); font-size: .7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-lt); }
@media (max-width: 620px) {
  .mcard { grid-template-columns: 68px 1fr; gap: .9rem; }
  .mcard img { width: 68px; height: 68px; }
  .mcard-from { grid-column: 2; text-align: left; }
  .mcard-from small { display: inline; margin-left: .4rem; }
}

/* ------------------------------------------------------ hours table ---
   The opening hours, with today's row marked by site.js. */

.hours-table td[data-today], .hours-table th[data-today] { color: var(--ink); font-weight: 650; }
.hours-table tr[data-today] { background: var(--iris-lt); }

/* ------------------------------------------------------------- people
   The team page. Restoration used .team-card with a body wrapper; this
   build's markup is flatter (image, name, role, bio) because the bios are
   longer and a card border around six paragraphs reads as a form. */
.person { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; padding-bottom: 1.5rem; }
.person img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; display: block; margin-bottom: 1.2rem; }
.person h3 { font-size: 1.1rem; margin: 0 1.45rem .18rem; }
.person-role { margin: 0 1.45rem .8rem; font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; color: var(--iris-dk); font-weight: 600; }
.person p:not(.person-role) { margin: 0 1.45rem; font-size: .91rem; color: var(--muted); }

/* ------------------------------------------------------------- areas
   One block per neighbourhood on /service-areas.html. Not a card: these are
   paragraphs of argument and a border would make twelve of them look like a
   price list. */
.area { border-left: 3px solid var(--iris-dk); padding: .15rem 0 .15rem 1.4rem; }
.area h2 { font-size: 1.12rem; margin-bottom: .5rem; }
.area p { font-size: .93rem; color: var(--muted); margin: 0; }

/* A service card with no photograph — used for the "read next" links on the
   honesty pages, where an image would imply the linked page is about a thing
   rather than about an argument. */
.svc-card--plain { display: block; }
.svc-card--plain .svc-card-body { padding: 1.5rem 1.45rem 1.6rem; }
.svc-card--plain h3 { margin-bottom: .55rem; }

/* ==========================================================================
   THE BOOKING APPLICATION
   /appointments.html — provider picker, month calendar, slot grid, form.
   Everything here is progressive: the page ships a real noscript fallback
   pointing at the telephone number, and the calendar only appears once the
   availability API has answered.
   ========================================================================== */

.booking { margin: 3rem 0 0; }

.booking-steps {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; margin-bottom: 2.2rem;
}
.booking-step {
  background: var(--paper); padding: .95rem 1.1rem;
  display: flex; gap: .7rem; align-items: center;
}
.booking-step[data-on="true"] { background: var(--ink); }
.booking-step[data-on="true"] .booking-step-n { background: var(--iris); color: var(--ink); }
.booking-step[data-on="true"] .booking-step-t { color: #fff; }
.booking-step-n {
  flex: 0 0 auto; width: 1.55rem; height: 1.55rem; border-radius: 50%;
  background: var(--bone-2); color: var(--muted);
  font: 700 .78rem/1.55rem var(--sans); text-align: center;
}
.booking-step-t { font: 600 .86rem/1.3 var(--sans); color: var(--body); letter-spacing: -.01em; }
@media (max-width: 780px) {
  .booking-steps { grid-template-columns: repeat(2, 1fr); }
}

.booking-panel {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.9rem; box-shadow: var(--shadow);
}
.booking-panel + .booking-panel { margin-top: 1.6rem; }
.booking-panel > h2 {
  font: 600 1.32rem/1.25 var(--serif); color: var(--ink);
  margin: 0 0 .4rem; letter-spacing: -.01em;
}
.booking-panel > p.hint { color: var(--muted); font-size: .95rem; margin: 0 0 1.4rem; line-height: 1.65; }

/* ------------------------------------------------------------- providers */

.docs { display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); gap: .9rem; }
.doc {
  display: flex; gap: .85rem; text-align: left; align-items: flex-start;
  background: var(--paper); border: 1.5px solid var(--line);
  border-radius: var(--r-lg); padding: .95rem; cursor: pointer;
  font: inherit; color: inherit; transition: border-color .14s, box-shadow .14s, background .14s;
}
.doc:hover { border-color: var(--line-2); }
.doc[aria-pressed="true"] {
  border-color: var(--iris-dk); background: var(--iris-lt);
  box-shadow: 0 0 0 1px var(--iris-dk);
}
.doc img {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: 50%;
  object-fit: cover; background: var(--bone-2);
}
.doc-b { min-width: 0; }
.doc-n { display: block; font: 600 .96rem/1.25 var(--sans); color: var(--ink); }
.doc-s { display: block; font-size: .8rem; color: var(--muted); margin-top: .15rem; line-height: 1.45; }
.doc-r { display: block; font-size: .74rem; color: var(--muted-lt); margin-top: .3rem;
         text-transform: uppercase; letter-spacing: .08em; }
.doc-mark {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: 50%;
  background: var(--ink); color: var(--iris); display: grid; place-items: center;
}

/* -------------------------------------------------------------- calendar */

.cal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 1rem;
}
.cal-title { font: 600 1.02rem/1 var(--sans); color: var(--ink); }
.cal-nav { display: flex; gap: .4rem; }
.cal-nav button {
  width: 2.1rem; height: 2.1rem; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--paper); color: var(--body);
  display: grid; place-items: center; cursor: pointer;
}
.cal-nav button:hover:not(:disabled) { border-color: var(--line-2); background: var(--bone); }
.cal-nav button:disabled { opacity: .35; cursor: not-allowed; }
.cal-nav button.prev svg { transform: rotate(90deg); }
.cal-nav button.next svg { transform: rotate(-90deg); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .35rem; }
.cal-dow {
  text-align: center; font: 700 .68rem/2 var(--sans); color: var(--muted-lt);
  text-transform: uppercase; letter-spacing: .09em;
}
.cal-day {
  /* Square, but capped: at 1440px a seven-column grid inside the panel makes a
     1:1 cell about 120px tall, which turns a month into a wall. The cap keeps
     the calendar compact on desktop and lets it stay square on a phone, where
     the column is narrow enough that the ratio wins. */
  aspect-ratio: 1 / 1; max-height: 4.4rem;
  border-radius: var(--r); border: 1px solid transparent;
  background: var(--bone); color: var(--muted-lt); font: 500 .92rem/1 var(--sans);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .18rem; cursor: default; padding: 0;
}
.cal-day[data-free="1"] {
  background: var(--paper); border-color: var(--line-2); color: var(--ink);
  cursor: pointer; font-weight: 600;
}
.cal-day[data-free="1"]:hover { border-color: var(--iris-dk); background: var(--iris-lt); }
.cal-day[aria-pressed="true"] {
  background: var(--ink); border-color: var(--ink); color: #fff;
}
.cal-day[aria-pressed="true"] .cal-dot { background: var(--iris); }
.cal-day.empty { background: transparent; }
.cal-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--iris-dk); }
.cal-day[data-free="0"] .cal-dot { visibility: hidden; }

.cal-key {
  display: flex; flex-wrap: wrap; gap: 1.1rem; margin-top: 1rem;
  font-size: .8rem; color: var(--muted);
}
.cal-key span { display: inline-flex; align-items: center; gap: .4rem; }
.cal-key i { width: .8rem; height: .8rem; border-radius: 3px; display: inline-block; }
.cal-key .k-free { background: var(--paper); border: 1px solid var(--line-2); }
.cal-key .k-none { background: var(--bone); }
.cal-key .k-sel { background: var(--ink); }

/* ----------------------------------------------------------------- slots */

.slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(122px, 1fr)); gap: .55rem; }
.slot {
  border: 1.5px solid var(--line); background: var(--paper); border-radius: var(--r);
  padding: .72rem .5rem; cursor: pointer; text-align: center;
  font: 600 .95rem/1.2 var(--sans); color: var(--ink); transition: border-color .14s, background .14s;
}
.slot small { display: block; font: 500 .72rem/1.4 var(--sans); color: var(--muted); margin-top: .18rem; }
.slot:hover { border-color: var(--iris-dk); background: var(--iris-lt); }
.slot[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.slot[aria-pressed="true"] small { color: var(--iris); }

.slots-empty {
  border: 1px dashed var(--line-2); border-radius: var(--r-lg);
  padding: 1.6rem; text-align: center; color: var(--muted); font-size: .95rem;
}

/* ------------------------------------------------------------------ form */

.bk-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
@media (max-width: 700px) { .bk-grid { grid-template-columns: 1fr; } }
.bk-field { display: flex; flex-direction: column; gap: .38rem; }
.bk-field.full { grid-column: 1 / -1; }
.bk-field label { font: 600 .84rem/1.3 var(--sans); color: var(--ink); }
.bk-field label .req { color: var(--signal); }
.bk-field input, .bk-field select, .bk-field textarea {
  font: 400 .97rem/1.45 var(--sans); color: var(--body);
  border: 1.5px solid var(--line); border-radius: var(--r);
  padding: .68rem .8rem; background: var(--paper); width: 100%;
}
.bk-field textarea { min-height: 6.5rem; resize: vertical; }
.bk-field input:focus, .bk-field select:focus, .bk-field textarea:focus {
  outline: none; border-color: var(--iris-dk); box-shadow: 0 0 0 3px var(--iris-lt);
}
.bk-field[data-bad="1"] input, .bk-field[data-bad="1"] select, .bk-field[data-bad="1"] textarea {
  border-color: var(--signal);
}
.bk-err { font-size: .8rem; color: var(--signal); }
.bk-check { display: flex; gap: .6rem; align-items: flex-start; font-size: .9rem; line-height: 1.6; color: var(--body); }
.bk-check input { margin-top: .25rem; width: 1rem; height: 1rem; flex: 0 0 auto; accent-color: var(--iris-dk); }
.bk-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.bk-summary {
  background: var(--bone); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 1.1rem 1.3rem; margin-bottom: 1.5rem;
}
.bk-summary dl { display: grid; grid-template-columns: auto 1fr; gap: .45rem 1.2rem; margin: 0; }
.bk-summary dt {
  font: 700 .7rem/1.6 var(--sans); color: var(--muted-lt);
  text-transform: uppercase; letter-spacing: .09em;
}
.bk-summary dd { margin: 0; font: 600 .96rem/1.6 var(--sans); color: var(--ink); }

.bk-note { font-size: .86rem; color: var(--muted); line-height: 1.7; margin: 1.2rem 0 0; }
.bk-actions { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; margin-top: 1.6rem; }

/* ------------------------------------------------------------ done / err */

.bk-done { text-align: center; padding: 1rem 0 .4rem; }
.bk-done .tick {
  width: 3.4rem; height: 3.4rem; border-radius: 50%; margin: 0 auto 1.2rem;
  background: var(--iris-lt); color: var(--iris-dk); display: grid; place-items: center;
}
.bk-done h2 { font: 600 1.5rem/1.25 var(--serif); color: var(--ink); margin: 0 0 .6rem; }
.bk-done p { color: var(--muted); line-height: 1.7; max-width: 46ch; margin: 0 auto 1rem; }
.bk-ref {
  display: inline-block; font: 700 .82rem/1 var(--sans); letter-spacing: .1em;
  background: var(--bone-2); color: var(--ink); padding: .55rem .9rem; border-radius: var(--r);
}
.bk-alert {
  border-left: 3px solid var(--signal); background: var(--signal-lt);
  padding: .9rem 1.1rem; border-radius: 0 var(--r) var(--r) 0;
  font-size: .92rem; line-height: 1.65; color: #7a2d24; margin-bottom: 1.3rem;
}
.bk-spin { text-align: center; padding: 2.4rem 0; color: var(--muted); font-size: .93rem; }
.bk-spin::before {
  content: ""; display: block; width: 1.6rem; height: 1.6rem; margin: 0 auto .9rem;
  border: 2px solid var(--line-2); border-top-color: var(--iris-dk);
  border-radius: 50%; animation: bkspin .7s linear infinite;
}
@keyframes bkspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .bk-spin::before { animation: none; } }

.is-hidden { display: none !important; }

/* ------------------------------------------- confirm.php landing page --- */

.result-card {
  max-width: 640px; margin: 0 auto; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 2.6rem 2.2rem; box-shadow: var(--shadow); text-align: center;
}
.result-card .tick, .result-card .cross {
  width: 3.6rem; height: 3.6rem; border-radius: 50%; margin: 0 auto 1.4rem;
  display: grid; place-items: center;
}
.result-card .tick { background: var(--iris-lt); color: var(--iris-dk); }
.result-card .cross { background: var(--signal-lt); color: var(--signal); }
.result-card h1 { font: 600 1.65rem/1.25 var(--serif); color: var(--ink); margin: 0 0 .7rem; }
.result-card > p { color: var(--muted); line-height: 1.75; margin: 0 auto 1.6rem; max-width: 48ch; }
.result-card .bk-summary { text-align: left; margin: 1.8rem 0 0; }

/* ------------------------------------------- illustrative condition images
   A generated image of a skin condition must never read as a record of a
   patient, so the caption is part of the component rather than an optional
   extra: the figure and its label ship together or not at all. */

/* ==========================================================================
   RESTAURANT COMPONENTS
   Added for this build. Everything above this line is the shared system the
   sibling demos audited; everything below is new and is what the menu, the
   dish list and the party picker need.
   ========================================================================== */

.wrap--narrow { max-width: 760px; }
.wrap--menu   { max-width: 880px; }

.btn--lg { padding: .95rem 1.7rem; font-size: .95rem; }

.section-foot { margin: 2.2rem 0 0; }

/* A "we do not" list. The cross is the SIGNAL colour and the tick is brass,
   so the two lists differ by more than an icon shape — colour is never the
   only cue, but it must not contradict the shape either. */
.checklist--no svg { color: var(--signal); }

/* Wide content scrolls inside its own box rather than the page. A table that
   makes the body scroll sideways is the overflow defect sweep-overflow.mjs
   exists to catch, and a wrapper is cheaper than finding out on a phone. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 0 1.4rem; }
.table-wrap > table { min-width: 22rem; }
.hours-table caption {
  caption-side: top; text-align: left; font-size: .82rem; color: var(--muted);
  padding-bottom: .6rem;
}
.hours-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.hours-table th.num { text-align: right; }

.svc-card-media { display: block; }
.card-price {
  font: 600 1rem/1 var(--serif); color: var(--iris-dk);
  margin: 0 0 .5rem; font-variant-numeric: tabular-nums;
}
.card-when { font-size: .82rem; color: var(--muted); margin: 0 0 .55rem; display: flex; gap: .4rem; align-items: center; }
.card-when svg { color: var(--iris-dk); flex: none; }

/* --------------------------------------------------------- menu tiles --- */

.menu-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: .9rem; }
.menu-tile {
  display: flex; flex-direction: column; gap: .2rem;
  padding: 1.15rem 1.3rem; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  text-decoration: none; color: inherit; position: relative;
  transition: border-color .2s, box-shadow .2s, transform .2s;
}
.menu-tile:hover { border-color: var(--iris-dk); box-shadow: var(--shadow); transform: translateY(-2px); color: inherit; }
.menu-tile-name { font: 600 1.06rem/1.3 var(--serif); color: var(--ink); }
.menu-tile-when { font-size: .82rem; color: var(--muted); padding-right: 1.8rem; }
.menu-tile svg { position: absolute; right: 1.1rem; bottom: 1.1rem; color: var(--iris-dk); }

/* ============================================================ the menu ===
   A dish is a name, a leader, a price, a description and its allergen
   components. The leader is a repeating background rather than a row of full
   stops in the markup, so a screen reader announces "Wood-roasted carrots,
   fourteen dollars" and not fifty periods. */

.menu-jump {
  position: sticky; top: 0; z-index: 20;
  background: rgba(248, 247, 244, .94);
  border-bottom: 1px solid var(--line);
  margin: 0 0 2.4rem; padding: .7rem 0;
}
@supports (backdrop-filter: blur(8px)) {
  .menu-jump { backdrop-filter: blur(8px); }
}
.menu-jump ul {
  display: flex; gap: .3rem; list-style: none; margin: 0; padding: 0;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.menu-jump ul::-webkit-scrollbar { display: none; }
.menu-jump a {
  display: block; white-space: nowrap; text-decoration: none;
  font: 600 .82rem/1 var(--sans); color: var(--muted);
  padding: .5rem .85rem; border-radius: 999px;
}
.menu-jump a:hover { background: var(--bone-2); color: var(--ink); }

.menu-section + .menu-section { margin-top: 3rem; }
.menu-section-h {
  font: 600 .78rem/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
  color: var(--iris-dk); margin: 0 0 1.4rem; padding-bottom: .7rem;
  border-bottom: 1px solid var(--line);
}

.dish-list { list-style: none; margin: 0; padding: 0; }
.dish + .dish { margin-top: 1.7rem; }
.dish-head { display: flex; align-items: baseline; gap: .5rem; }
.dish-name { font: 600 1.06rem/1.35 var(--serif); color: var(--ink); margin: 0; flex: none; max-width: 78%; }
.dish-dots {
  flex: 1 1 auto; min-width: 1.5rem; height: 1px; align-self: center;
  background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1px);
  background-size: 5px 1px; background-repeat: repeat-x; background-position: 0 100%;
}
.dish-price {
  font: 600 1.02rem/1.35 var(--serif); color: var(--ink); flex: none;
  font-variant-numeric: tabular-nums;
}
.dish-desc { font-size: .93rem; line-height: 1.7; color: var(--muted); margin: .3rem 0 0; max-width: 62ch; }
.dish-note {
  display: flex; gap: .45rem; align-items: flex-start;
  font-size: .84rem; line-height: 1.6; color: var(--warm-ink);
  background: var(--iris-lt); border-radius: var(--r);
  padding: .5rem .7rem; margin: .55rem 0 0; max-width: 62ch;
}
.dish-note svg { color: var(--iris-dk); flex: none; margin-top: .2em; }
.dish-marks { margin: .5rem 0 0; display: flex; flex-wrap: wrap; gap: .4rem .8rem; }
.dish-allergens, .dish-without {
  font: 500 .77rem/1.5 var(--sans); color: var(--muted-lt);
}
.dish-without { color: var(--amber-dk); font-weight: 600; }

@media (max-width: 560px) {
  .dish-name { max-width: 100%; }
  .dish-head { flex-wrap: wrap; }
  .dish-dots { display: none; }
  .dish-price { margin-left: auto; }
}

.menu-foot { margin-top: 3.5rem; padding-top: 2rem; border-top: 1px solid var(--line); }
.allergen-legend { margin-bottom: 2rem; }
.allergen-legend h2 { font-size: 1.12rem; margin-bottom: .7rem; }
.legend-list {
  list-style: none; margin: .9rem 0 1.2rem; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .5rem 1.2rem;
}
.legend-list li { display: flex; gap: .5rem; align-items: center; font-size: .87rem; color: var(--body); }
.legend-list svg { color: var(--iris-dk); flex: none; }
.menu-links { margin-top: 2.6rem; }
.menu-links h2 { font-size: 1.05rem; margin-bottom: .8rem; }

/* ------------------------------------------------------- the party grid ---
   The first question the booking form asks. Eight numbers and one wide
   button, because party size decides the turn time and the inventory that
   can be searched — see pages-visit.cjs. */

.party-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: .6rem; margin: 1.4rem 0 0;
}
.party {
  font: 600 1.15rem/1 var(--serif); color: var(--ink);
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r);
  padding: 1rem .5rem; cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.party:hover { border-color: var(--iris-dk); background: var(--iris-lt); }
.party[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.party--wide {
  grid-column: 1 / -1; font-family: var(--sans); font-size: .92rem; font-weight: 600;
}
@media (max-width: 480px) { .party-grid { grid-template-columns: repeat(3, 1fr); } }

/* ----------------------------------------------------------- calendar key */

.cal-key {
  display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; align-items: center;
  font-size: .78rem; color: var(--muted); margin: 1rem 0 0;
}
.key { width: .62rem; height: .62rem; border-radius: 50%; display: inline-block; margin-right: .3rem; }
.key--free { background: var(--iris-dk); }
.key--full { background: var(--line-2); }
.key--shut { background: transparent; border: 1px solid var(--line-2); }

/* The service-period heading inside the time step. */
.slot-group + .slot-group { margin-top: 1.8rem; }
.slot-group h3 {
  font: 600 .78rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase;
  color: var(--iris-dk); margin: 0 0 .9rem;
}

/* ------------------------------------------------------------ area cards */

.area h3 { font-size: 1.08rem; margin-bottom: .45rem; }

/* ---------------------------------------------------------- team extras */

.team-langs { font-size: .82rem; color: var(--muted-lt); display: flex; gap: .4rem; align-items: center; margin: .8rem 0 0; }
.team-langs svg { color: var(--iris-dk); flex: none; }

/* --------------------------------------------------------------- the LCP
   The hero image is the LCP element. It must never animate its own opacity —
   the main site's CLAUDE.md records that rule and it costs a measurable
   amount of LCP every time somebody forgets it. */
.hero-img { width: 100%; height: 100%; object-fit: cover; }

/* ------------------------------------------------- gaps the class-coverage
   check found. Two of these were real: .advice-note--warn was a typo for the
   stylesheet's existing --warm and would have rendered the ALLERGEN notice as
   a plain paragraph on every page that carries it, and .small was a class
   this build invented that the shared stylesheet had never needed. Neither is
   visible in a screenshot — the text is still there, just unstyled — which is
   precisely why the check reads the built HTML rather than trusting the eye. */

/* The caution variant: used only where the note is a limitation rather than
   an aside. --warm is the amber "worth knowing"; --warn is the red "this one
   matters", and the allergen notice is the reason it exists. */
.advice-note--warn {
  border-left-color: var(--signal);
  background: var(--signal-lt);
  color: #7a2d24;
}
.advice-note--warn svg { color: var(--signal); }
.advice-note--warn strong { color: #5c1f18; }

.small { font-size: .86rem; line-height: 1.7; color: var(--muted); }
.small a { color: var(--iris-dk); }

.menu-body { margin: 0; }

/* The Visit dropdown carries two short columns rather than the Menus panel's
   wider pair, so it is narrower and right-aligned to its trigger. */
.nav-panel--single { min-width: 30rem; }
@media (max-width: 1080px) { .nav-panel--single { min-width: 0; } }

.team-photo { display: block; width: 100%; height: auto; }

/* The two class names carried by every inline <svg>. They exist as hooks
   rather than as styles — the icons are sized by their width/height
   attributes, which is what stops an unsized SVG rendering at the spec
   default of 300x150. Declared here so the class-coverage check in
   validate.mjs is comparing like with like instead of reporting two
   permanent false positives that teach everyone to ignore it. */
.ic { flex: none; }
.mark { display: block; }

/* ==========================================================================
   AN INLINE ICON MUST BE INLINE, AND THE RESET AT THE TOP OF THIS FILE SAYS
   IT IS NOT.

   `img, svg, video { display: block }` is right for a photograph and wrong for
   a 13-pixel glyph sitting at the start of a sentence: the glyph becomes a
   block, takes a line of its own, and the sentence starts underneath it.

   Almost everywhere that does not show, because the icon's container is a flex
   row (.svc-spec li, .plan-list li, .hero-meta li, .footer-contact li) and a
   flex item's display is blockified anyway. It shows wherever an icon sits in
   ORDINARY FLOW inside a paragraph or a heading — .svc-card-tag, .advice-note
   and the boundary card's h3 — and there it looks like a stray mark floating
   above the text.

   THE FIX THAT WAS ALREADY HERE COULD NOT WORK. `.svc-card-tag svg {
   vertical-align: -.15em }` was written for this exact component, and
   vertical-align has no effect on a block box: the property was correct, the
   display it assumed was not. So the rule has been present and inert on this
   component through several forks, which is a comment-describing-a-check
   problem in CSS form.

   Fixed at the class rather than per component, because `.i` is only ever put
   on an icon and an icon is only ever inline. `.mark` above stays a block —
   it is the logo. */
.i { display: inline-block; vertical-align: -.15em; }


/* ==========================================================================
   THE INN'S OWN COMPONENTS

   Everything below is new for this build: the room card's spec strip, the rate
   table, the per-night price breakdown, and the availability widget. They are
   at the end of the file rather than woven in, so the next fork can see at a
   glance what belongs to a hotel and what belongs to the shared system.

   EVERY TABLE IS INSIDE .table-scroll. A rate table has five columns and a
   night table has six; on a 320px phone neither fits, and a table that does not
   fit widens the page instead of scrolling inside itself. The .grid-N
   minmax(0,1fr) fix upstream is what makes this work when the table sits in a
   grid cell — without it the track grows to the table's min-content width and
   .table-scroll never scrolls. DEMO-HOUSE-RULES.md §12.
   ========================================================================== */

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 0 1.4rem; }
.table-scroll table { min-width: 34rem; }
.table-scroll caption {
  caption-side: top; text-align: left; font-size: .86rem; color: var(--muted);
  padding-bottom: .7rem; line-height: 1.5;
}

.rate-table, .night-table { width: 100%; border-collapse: collapse; font-size: .93rem; }
.rate-table th, .rate-table td, .night-table th, .night-table td {
  text-align: left; padding: .62rem .8rem; border-bottom: 1px solid var(--line);
}
.rate-table thead th, .night-table thead th {
  font-size: .74rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted-lt); font-weight: 700; border-bottom-color: var(--line-2);
}
.rate-table tbody th, .night-table tbody th { font-weight: 620; color: var(--ink); }
.rate-table td, .night-table td { color: var(--body); font-variant-numeric: tabular-nums; }
.rate-table tbody tr:last-child th, .rate-table tbody tr:last-child td,
.night-table tbody tr:last-child th, .night-table tbody tr:last-child td { border-bottom: 0; }
.night-table tfoot th, .night-table tfoot td {
  border-top: 2px solid var(--line-2); border-bottom: 0; font-weight: 650; color: var(--ink);
}
.night-table .num, .rate-table .num { text-align: right; }
.night-table .zero { color: var(--muted-lt); }

/* The spec strip inside a room card: four facts, two up, icons at the same
   weight as the icon set. */
.room-spec {
  list-style: none; margin: 0 0 1rem; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .38rem .9rem;
  font-size: .85rem; color: var(--muted);
}
.room-spec li { display: flex; align-items: flex-start; gap: .4rem; margin: 0; }
.room-spec svg { color: var(--iris-dk); flex: none; margin-top: .22rem; }
.room-spec span { min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 420px) { .room-spec { grid-template-columns: minmax(0, 1fr); } }

/* The two-column "what is and is not included" block. The SECOND column is the
   one that sells the room, which is the whole argument of this build: a guest
   who reads what a room does not have and books it anyway does not complain
   about it afterwards. */
.inc-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.6rem 2.2rem; }
@media (max-width: 780px) { .inc-grid { grid-template-columns: minmax(0, 1fr); } }
.inc-list { list-style: none; margin: 0; padding: 0; }
.inc-list li { display: flex; gap: .55rem; margin: 0 0 .62rem; font-size: .94rem; }
.inc-list svg { flex: none; margin-top: .28rem; }
.inc-list span { min-width: 0; }
.inc-list--yes svg { color: var(--amber-dk); }
.inc-list--no svg { color: var(--signal); }

/* ------------------------------------------------------- availability app */

.avail { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--paper); overflow: hidden; }
.avail-form { padding: 1.5rem 1.5rem 1.7rem; background: var(--bone); border-bottom: 1px solid var(--line); }
.avail-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; }
@media (max-width: 880px) { .avail-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 440px) { .avail-row { grid-template-columns: minmax(0, 1fr); } }
.avail-out { padding: 1.5rem; }
.avail-empty { color: var(--muted); font-size: .95rem; margin: 0; }

.avail-card {
  display: grid; grid-template-columns: 190px minmax(0, 1fr) auto; gap: 1.2rem;
  align-items: start; padding: 1.2rem 0; border-bottom: 1px solid var(--line);
}
.avail-card:last-child { border-bottom: 0; }
.avail-card img { border-radius: var(--r); width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
.avail-card h3 { font-size: 1.08rem; margin: 0 0 .3rem; }
.avail-card p { font-size: .9rem; color: var(--muted); margin: 0 0 .6rem; }
.avail-price { text-align: right; min-width: 9.5rem; }
.avail-price .big { font-family: var(--serif); font-size: 1.55rem; color: var(--ink); display: block; line-height: 1.2; }
.avail-price .sub { font-size: .8rem; color: var(--muted-lt); display: block; margin-bottom: .7rem; }
@media (max-width: 780px) {
  .avail-card { grid-template-columns: minmax(0, 1fr); }
  .avail-price { text-align: left; }
}

/* THREE STATES, NOT TWO. "No availability", "closed to arrivals" and "minimum
   stay not met" are different sentences and get different strips. A guest told
   the second tries again tomorrow for the same night; a guest told the third
   shortens or lengthens the stay. Collapsing all three into one "sorry,
   nothing" is the most common lie on a hotel booking page. */
.avail-state {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .95rem 1.1rem; border-radius: var(--r); font-size: .92rem;
  border: 1px solid var(--line); background: var(--bone-2); margin: 0 0 1rem;
}
.avail-state svg { flex: none; margin-top: .2rem; }
.avail-state--full { background: var(--signal-lt); border-color: #edd2ce; color: #7a2d24; }
.avail-state--full svg { color: var(--signal); }
.avail-state--arrivals { background: var(--iris-lt); border-color: var(--warm-line); color: var(--warm-ink); }
.avail-state--arrivals svg { color: var(--iris-dk); }
.avail-state--minstay { background: var(--amber-lt); border-color: #e6d2a8; color: #5c4310; }
.avail-state--minstay svg { color: var(--amber-dk); }
.avail-state strong { color: inherit; }

/* The alternatives offered when a range does not work. A bare "no" is not an
   answer; these are real ranges the engine has actually checked. */
.avail-alts { list-style: none; margin: .2rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .5rem; }
.avail-alts li { margin: 0; }
.avail-alts button {
  display: inline-block; padding: .38rem .75rem; border: 1px solid var(--line-2);
  border-radius: 999px; font-size: .85rem; color: var(--body);
  background: var(--paper); cursor: pointer; font-family: inherit;
}
.avail-alts button:hover { border-color: var(--iris-dk); color: var(--iris-dk); }

.book-summary { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.4rem 1.5rem; background: var(--bone); }
.book-summary h3 { font-size: 1rem; margin: 0 0 .8rem; }
.book-steps { counter-reset: s; list-style: none; margin: 0; padding: 0; }
.book-steps li { position: relative; padding-left: 2.4rem; margin-bottom: 1.15rem; }
.book-steps li::before {
  counter-increment: s; content: counter(s);
  position: absolute; left: 0; top: .05rem;
  width: 1.7rem; height: 1.7rem; border-radius: 50%;
  background: var(--ink); color: var(--iris);
  display: grid; place-items: center; font-size: .82rem; font-weight: 700;
}
.book-steps h3 { font-size: 1rem; margin: 0 0 .25rem; }
.book-steps p { margin: 0; font-size: .93rem; color: var(--muted); }


/* ==========================================================================
   THE SALON COMPONENTS.

   Everything below is new in this build rather than inherited, and every class
   here is emitted by site.cjs or a page module - validate.mjs reads the built
   HTML and fails on a class the stylesheet has never heard of, which is how
   the dermatologist build found that its allergen notice had been rendering as
   an unstyled paragraph on every page that carried it.
   ========================================================================== */

/* The third advice-note variant. --warm is the amber "worth knowing", --warn
   the red "this one matters", and --cool is the jade one: the notes about
   hygiene, aftercare and children, which are reassurances rather than
   warnings and should not be wearing a caution colour. */
.advice-note--cool {
  border-left-color: var(--amber-dk);
  background: var(--amber-lt);
  color: #4a3612;
}
.advice-note--cool svg { color: var(--amber-dk); }
.advice-note--cool strong { color: #38290c; }

/* The spec strip on a service group card. Same shape as .room-spec, which it
   replaces; kept as its own name because a nail service's facts are a count,
   a length and whether it needs a chair, not a bed and a view. */
.svc-spec {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .35rem .8rem; list-style: none; margin: 0 0 .8rem; padding: 0;
  font-size: .84rem; color: var(--muted);
}
.svc-spec li { display: flex; align-items: flex-start; gap: .4rem; margin: 0; }
.svc-spec svg { color: var(--iris-dk); flex: none; margin-top: .22rem; }
.svc-spec span { min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 420px) { .svc-spec { grid-template-columns: minmax(0, 1fr); } }

/* A pill in a table cell. Only one exists: the jade "chair" marker on every
   service that needs one of the three pedicure stations as well as a
   technician, which is the constraint this build's booking engine is about. */
.pill {
  display: inline-block; vertical-align: baseline;
  padding: .06rem .42rem; border-radius: 999px;
  font-family: var(--sans); font-size: .68rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  border: 1px solid var(--line-2); color: var(--muted);
}
.pill--amber { border-color: var(--amber-dk); color: var(--amber-dk); background: var(--amber-lt); }

/* ------------------------------------------------------ the arithmetic box
   The single most useful component on this site, and the one thing the
   booking engine does that a fixed-slot one cannot: it shows the minutes
   being added up. Rendered by site.cjs sumExample(), which reads the real
   durations out of data.cjs - so this box cannot show a sum the engine would
   not actually reserve. */
.sum {
  border: 1px solid var(--line); border-left: 3px solid var(--iris-dk);
  border-radius: var(--r); background: var(--bone);
  padding: 1.1rem 1.25rem; margin: 1.6rem 0;
}
.sum-h {
  display: flex; align-items: center; gap: .45rem;
  margin: 0 0 .7rem; font-weight: 650; color: var(--ink);
  font-size: .92rem; letter-spacing: .01em;
}
.sum-h svg { color: var(--iris-dk); flex: none; }
.sum-rows { list-style: none; margin: 0; padding: 0; font-size: .93rem; }
.sum-rows li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: .5rem 1rem; align-items: baseline;
  padding: .34rem 0; border-bottom: 1px dotted var(--line);
  margin: 0; color: var(--muted);
}
.sum-rows li span:first-child { min-width: 0; overflow-wrap: anywhere; }
.sum-rows .num { font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; min-width: 5.5rem; }
.sum-total {
  border-bottom: 0; border-top: 1px solid var(--line-2);
  margin-top: .3rem; padding-top: .55rem;
  color: var(--ink); font-weight: 650;
}
.sum-note { margin: .8rem 0 0; font-size: .85rem; color: var(--muted); }
@media (max-width: 520px) {
  .sum-rows li { grid-template-columns: minmax(0, 1fr) auto; }
  .sum-rows li span:nth-child(2) { grid-column: 2; }
  .sum-rows li span:nth-child(3) { grid-column: 2; }
  .sum-rows .num { min-width: 0; }
}

/* ------------------------------------------------------------- the lookbook
   Every tile is a swatch painted on loose tips rather than on a hand - see
   the header of _tools/shots.cjs for why that is a decision and not a
   shortcut. The caption carries the finish name and how long it wears, which
   is the question the picture cannot answer. */
.look { margin: 0; }
.look img {
  width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: var(--r); background: var(--bone-2);
}
.look figcaption {
  display: flex; flex-wrap: wrap; gap: .2rem .6rem; align-items: baseline;
  margin-top: .55rem; font-size: .86rem; color: var(--muted);
}
.look figcaption strong { color: var(--ink); }
.look figcaption span { font-size: .8rem; }

/* The one line on a technician card that says what they actually do all day,
   as opposed to their job title. It is deliberately NOT a credential. */
.person-focus {
  display: flex; align-items: center; gap: .4rem;
  margin: 0 1.45rem .7rem; font-size: .86rem; color: var(--amber-dk); font-weight: 600;
}
.person-focus svg { flex: none; }

/* A paragraph of biography inside a .person card. It sits in the same gutter
   as the name and the role above it, which .bio on its own does not know
   about — .bio is defined for a card that has its own padding. */
.person .bio { margin: 0 1.45rem; font-size: .93rem; color: var(--muted); line-height: 1.65; }

/* ==========================================================================
   THE ASSESSMENT WIZARD

   Built on components this stylesheet already had rather than on a new
   vocabulary: the option buttons are `.doc` (the selectable card the sibling
   build used to pick a member of staff), the lead capture is `.bk-grid` and
   `.bk-field`, the engagement suggestion inside a result is `.sum`, and the
   sent screen is `.result-card`. Everything below is what those did not
   already cover.

   The progress bar is a bar and not a number out of ten, and the count under
   it says out loud that the denominator can move. That is not decoration
   either: two of the twelve questions are chosen from the answers above them,
   so a fixed "question 7 of 12" would be a small lie told twelve times.
   ========================================================================== */

.wiz { margin-top: 2rem; }

.wiz-bar {
  height: 4px; border-radius: 2px; background: var(--bone-2);
  overflow: hidden; margin-bottom: .7rem;
}
.wiz-bar-fill {
  height: 100%; background: var(--iris-dk); border-radius: 2px;
  transition: width .28s ease;
}
@media (prefers-reduced-motion: reduce) { .wiz-bar-fill { transition: none; } }

.wiz-count {
  margin: 0 0 1.9rem; font-size: .84rem; color: var(--muted);
  letter-spacing: .01em;
}
.wiz-count .muted { color: var(--muted-lt); }

.wiz-q { border: 0; padding: 0; margin: 0 0 1.9rem; min-width: 0; }
.wiz-q legend {
  padding: 0; margin: 0 0 .5rem;
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(1.28rem, 2.4vw, 1.7rem); line-height: 1.22;
  color: var(--ink); letter-spacing: -.012em;
  overflow-wrap: anywhere;
}
.wiz-help { margin: 0 0 1.1rem; font-size: .92rem; color: var(--muted); max-width: 62ch; }

/* The option cards. `.doc` carries the frame, the pressed state and the
   hover; the only thing wanted here is that a wizard option has no photograph
   in it, so its mark is a small empty disc that fills with a tick. */
.wiz-q .doc-mark { width: 34px; height: 34px; background: var(--bone-2); color: var(--muted); }
.wiz-q .doc[aria-pressed="true"] .doc-mark { background: var(--ink); color: var(--iris); }
.wiz-q .doc-n { font-weight: 550; }

.wiz-actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin: 1.6rem 0 1.9rem; flex-wrap: wrap;
}
.wiz-actions .btn[disabled] { opacity: .45; pointer-events: none; }

.wiz-result-head { margin-bottom: 1.6rem; }
.wiz-result-head h2 { margin-bottom: .5rem; }

.wiz-results { margin-bottom: 1.9rem; }
.wiz-result .sum { margin-bottom: 1rem; }
.wiz-result .eyebrow { margin-bottom: .35rem; }

.wiz-lead {
  background: var(--bone); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.9rem 1.8rem 1.7rem; margin: 2rem 0 1.4rem;
}
.wiz-lead h3 { margin-bottom: .45rem; }
.wiz-lead > p { color: var(--muted); font-size: .93rem; max-width: 64ch; margin: 0 0 1.4rem; }

.wiz-restart { display: flex; gap: .7rem; flex-wrap: wrap; margin: 0 0 1rem; }

/* The plain form people without JavaScript get. Every question, no
   branching, posted once to the same endpoint the wizard uses. */
.wiz-fallback { margin-top: 2rem; }
.wiz-fallback fieldset {
  border: 0; padding: 0; margin: 0 0 2rem; min-width: 0;
  border-bottom: 1px solid var(--line); padding-bottom: 1.6rem;
}
.wiz-fallback legend {
  padding: 0; margin: 0 0 .5rem;
  font-family: var(--serif); font-weight: 600; font-size: 1.2rem; line-height: 1.25;
  color: var(--ink); overflow-wrap: anywhere;
}
.wiz-fallback .wiz-opts { display: grid; gap: .5rem; margin: .9rem 0 0; }
.wiz-fallback .wiz-opts label {
  display: flex; gap: .6rem; align-items: flex-start;
  font-size: .95rem; color: var(--body);
  border: 1.5px solid var(--line); border-radius: var(--r-lg);
  padding: .7rem .85rem; cursor: pointer;
}
.wiz-fallback .wiz-opts label:hover { border-color: var(--line-2); }
.wiz-fallback .wiz-opts input { margin-top: .28rem; flex: none; accent-color: var(--iris-dk); }

.grid-1 { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }

/* ==========================================================================
   THE DARK GROUND, PART TWO — .info-card

   The sibling build found this defect on .svc-card and fixed it there, with a
   comment (above, at .section--ink) explaining that a dark section does not
   make everything inside it dark-ground: a component carrying its own light
   background paints white headings onto white paper, and there is nothing to
   see because there is nothing there.

   .info-card has exactly the same shape — `background: var(--bone)` — and it
   was never given the same treatment, because no sibling had ever put one
   inside .section--ink. This build's home page does, and audit.mjs found six
   elements at 1.05:1 on the assessment band. Same family, same cause, a
   different component, one generation later.

   So rather than restating the text colours over a white card, the card
   becomes a real dark-ground card: a translucent well with a hairline. That
   is also what it should have looked like.
   ========================================================================== */
.section--ink .info-card {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .14);
  color: rgba(255, 255, 255, .78);
}
.section--ink .info-card h3 { color: #fff; }
.section--ink .info-card p { color: rgba(255, 255, 255, .78); }
.section--ink .info-card .link-more { color: var(--iris); }
.section--ink .info-card svg { color: var(--iris); }
.section--ink .info-card .info-list a { color: var(--iris); }

/* ==========================================================================
   .plan ON A DARK GROUND — THE SENTENCE THAT USED TO BE HERE CAME TRUE.

   What this comment said, verbatim, when this stylesheet was forked:

     "And the same question asked of every other component that carries its
      own light background: .ind-card, .person, .plan and .sum are all
      `--paper` or `--bone`. None of them is used inside .section--ink today.
      If one ever is, it needs a block here, and audit.mjs is what will say so."

   This build's home page puts S.rateCards() — which is .plans/.plan — inside
   a .section--ink band, because a dark band with the four class lengths
   floating in it is the strongest thing on the page. `.section--ink` sets
   `color: #fff` on its descendants; `.plan h3` and `.plan-price` inherit it;
   the card's own background is `--paper`. White on white, eighteen elements at
   1:1, and NOTHING IS VISIBLE — there is nothing to see because there is
   nothing there, which is why a screenshot review walks past it and only a
   contrast audit finds it.

   audit.mjs did say so, exactly as predicted, on the first run against the
   built site. The fix is the same one .info-card got a generation earlier:
   the card becomes a real dark-ground card rather than a light card with its
   text colours restated.

   .ind-card, .person and .sum are still light-ground only. If one of them ever
   lands in a dark band, it needs a block here — and audit.mjs is still what
   will say so.
   ========================================================================== */
.section--ink .plan {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .14);
  box-shadow: none;
}
.section--ink .plan--featured {
  background: rgba(255, 255, 255, .09);
  border-color: var(--iris);
}
.section--ink .plan h3 { color: #fff; }
.section--ink .plan-price { color: #fff; }
.section--ink .plan-price strong { color: var(--iris); }
.section--ink .plan-price span { color: rgba(255, 255, 255, .72); }
.section--ink .plan-blurb { color: rgba(255, 255, 255, .78); }
.section--ink .plan-list { border-top-color: rgba(255, 255, 255, .14); }
.section--ink .plan-list li { color: rgba(255, 255, 255, .78); }
.section--ink .plan-list svg { color: var(--iris); }
.section--ink .plan-best { color: rgba(255, 255, 255, .72); }
.section--ink .plan-best strong { color: #fff; }
.section--ink .plan-flag { background: var(--iris); color: var(--ink); }

/* --------------------------------------------------------------------------
   .svc-card-tag CARRIES A SENTENCE ON THIS SITE, NOT A LABEL.

   Inherited as an uppercase letter-spaced micro-label, which is right for
   "CHAIR REQUIRED" and wrong for "We are busy and we are not getting
   anywhere" — a full quoted sentence set in caps across three lines, which is
   what the practice-area cards put in it. The screenshot is what found that;
   nothing was broken and it simply read badly.
   -------------------------------------------------------------------------- */
.svc-card-tag {
  font-size: .88rem; font-weight: 500; letter-spacing: 0; text-transform: none;
  font-style: italic; color: var(--muted); line-height: 1.5;
}
.svc-card-tag svg { color: var(--iris-dk); vertical-align: -.15em; }

/* ==========================================================================
   .split NEEDS minmax(0, 1fr) TOO — the fourth time in this repo.

   DEMO-HOUSE-RULES §12 established the rule for .grid-N: a bare `1fr` track
   has an automatic min-content MINIMUM, so a child that refuses to shrink
   grows the track rather than scrolling inside its own box. `.grid` was
   fixed. `.split` was not, because on every sibling build its two children
   were a paragraph and a photograph, and neither has a min-width.

   This build put a `.table-scroll` inside a `.split-copy` on
   /hours-and-directions.html. `.table-scroll table` carries `min-width: 34rem`
   so a wide rate table stays readable and scrolls — but with a bare `1fr`
   the track grew to 544px instead, and the whole page scrolled sideways by
   172px on a 390px phone. check-live.mjs found it; nothing at 1440 shows it,
   and it is invisible at 390 too unless you happen to swipe.

   Both halves are needed. minmax(0, 1fr) lets the track be narrower than its
   content; min-width: 0 on the children stops a flex/grid item's own
   automatic minimum doing the same thing one level down.
   ========================================================================== */
.split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.split > * { min-width: 0; }
@media (max-width: 860px) {
  .split { grid-template-columns: minmax(0, 1fr); }
}

/* AND THE HOURS TABLE SHOULD NEVER HAVE HAD A SCROLL BOX.
   `.table-scroll table { min-width: 34rem }` is right for a five-column rate
   table and wrong for two columns of "Monday to Thursday / 8:30am - 6:00pm",
   which reads perfectly at 320px. Forcing a horizontal scroll onto it is
   worse than the problem the rule was written for. */
.table-scroll .hours-table { min-width: 0; }

/* ==========================================================================
   .advice-note--warn: RED IS THE WRONG REGISTER FOR THIS TRADE.

   Inherited from a food build, where --warn is the ALLERGEN notice and red is
   exactly right: it is a safety warning and it should stop somebody.

   On this site --warn carries the not-professional-advice line, the
   no-valuation line, the no-benchmark line, the composite-case-study line and
   the demonstration notice — five qualifications that appear on nearly every
   page. None of them is a hazard. A pink box repeated forty-five times reads
   as an error state, and the specific damage is that the assessment's most
   important sentence starts to look like a form validation failure.

   So on this build --warn is the QUIET, DEFINITE one: a neutral ground with
   an ink rule down the side. It is still the most emphatic of the three
   variants — brass is "worth knowing", sage is "this belongs to somebody
   else", ink is "read this properly" — and it stops shouting.

   The red --signal token stays where it belongs: form validation, and the 404
   code. Contrast for all of it is checked by audit.mjs rather than asserted.
   ========================================================================== */
.advice-note--warn {
  border-color: var(--line-2);
  border-left-color: var(--ink);
  background: var(--bone-2);
  color: var(--body);
}
.advice-note--warn svg { color: var(--ink); }
.advice-note--warn strong { color: var(--ink); }

/* ==========================================================================
   A BUTTON MAY WRAP ON A NARROW PHONE.

   `.btn` is `white-space: nowrap`, which is right almost everywhere: a call
   to action broken across two lines mid-phrase looks like a mistake. It stops
   being right when the label is longer than the screen. The live sweep found
   a 356px button on a 320px viewport — a 54px horizontal scroll on the home
   page, in a band no two-viewport audit looks at, caused by nothing worse
   than a long label.

   The label was shortened, which is the real fix. This is the guard, so the
   next long one is ugly rather than broken: below 420px a button wraps and
   centres instead of pushing the document sideways.
   ========================================================================== */
/* THE BREAKPOINT WAS 420px AND IT IS NOT A CONSTANT.

   It is a fact about the LONGEST BUTTON LABEL in the build that wrote it, in
   the same way §12 records that the topbar's wrap point is a function of the
   sentence. This build's longest is "The whole price list, and four more
   worked examples" — 417px of nowrap text in a 391px column, so it overflowed
   in a band from 424 to 432px, four pixels above the guard.

   Raised to 520px, which clears every label this build has with room; and
   `max-width: 100%` is added as the part that is NOT a breakpoint, so a longer
   label added later is ugly rather than a horizontal scrollbar. The label was
   shortened as well — §15 asks for both, because a guard that only ever
   triggers is a design nobody looked at. */
@media (max-width: 520px) {
  .btn { white-space: normal; text-align: center; }
}
.btn { max-width: 100%; }

/* ==========================================================================
   THE CLOSED DRAWER WAS WIDENING THE DOCUMENT.

   `.drawer` is a fixed, full-viewport overlay whose panel sits at
   `translateX(100%)` when closed — 380px hanging off the right edge — and it
   was `overflow: visible`. `visibility: hidden` hides it and does NOT take it
   out of the scroll extent, so the panel's own box still counted: the live
   sweep found a 50px horizontal scroll on /assessment.html in a 644-740px
   band, and the culprit turned out to be the brand mark inside the closed
   drawer, 46px past the right edge.

   Two things about it are worth remembering.

   First, it only showed up on ONE page of forty-six, which is why it survived
   at 1440 and at 390 and why a two-viewport audit never saw it. Whether the
   overhang wins depends on what else is setting the document's scroll extent,
   so the same markup is fine on forty-five pages and broken on the forty-
   sixth. That is the argument for sweeping every width rather than two.

   Second, this drawer markup is shared with every sibling build in the
   portfolio, so the same latent overhang is in all of them.

   Clipping the overlay is the fix: closed, the panel is outside the box and
   is cut off; open, `transform: none` puts it inside and nothing changes.
   ========================================================================== */
.drawer { overflow: hidden; }

/* ==========================================================================
   A MEDIA QUERY MEASURES THE VIEWPORT, AND THIS BOX IS NOT THE VIEWPORT.

   `.sum-rows li` is a three-column grid whose two numeric columns carry
   `min-width: 5.5rem` and `white-space: nowrap`, and there is a
   `@media (max-width: 520px)` rule that folds it to two columns on a phone.
   That covers the case where the SCREEN is narrow. It does nothing for the
   case where the screen is wide and the BOX is narrow — which is what the six
   assessment cards are: a `.sum` inside an `.info-card` inside a two-column
   grid, so at a 644px viewport each one is about 290px wide while the media
   query is still sitting in its desktop branch.

   The live sweep found it as a 50px horizontal scroll on /assessment.html in
   the 644-740px band, and it took a subtree bisection to find, because
   nothing has a bounding box outside the viewport: the row is simply wider
   than its own column and the grid track cannot shrink below it.

   Inside a result card the rows stack. Three short lines in a 290px card read
   better than a squeezed table anyway, and it is the layout the runtime
   wizard already uses at its own narrow widths.
   ========================================================================== */
.wiz-result .sum-rows li {
  grid-template-columns: minmax(0, 1fr);
  gap: .1rem;
}
.wiz-result .sum-rows .num {
  text-align: left; min-width: 0; white-space: normal;
}

/* ==========================================================================
   ADDED FOR THIS BUILD — the timetable, the enrolment form and four layout
   helpers the forked stylesheet did not define.

   EVERY CLASS BELOW WAS ADDED BECAUSE THE MARKUP USES IT, not the other way
   round. The rule this build is obeying is house rules section 13: score a
   forked stylesheet against your own markup, by ELEMENT selector as well as
   by class, before you assume the panel is styled. `.field`, `.field-row`,
   `.form-actions` and `.form-status` were in the first draft of the enrolment
   form and none of them existed here; the form was rewritten onto the classes
   the stylesheet already defines (.form .row, .form .check, .form .hint)
   rather than the stylesheet being grown to meet it. What is below is only
   what genuinely had no equivalent.
   ========================================================================== */

/* --------------------------------------------------------- layout helpers */

/* A two-column band. minmax(0,1fr) rather than 1fr, because a 1fr track takes
   an automatic min-content minimum and a child with its own overflow-x box
   grows the track instead of scrolling inside it. Restaurant build, section 12. */
.two-col { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(2rem, 4vw, 3.4rem); align-items: start; }
.two-col > div > h2 { margin-bottom: .7rem; }
.two-col > div > p { color: var(--muted); }
.two-col img { border-radius: var(--r-lg); width: 100%; height: auto; }
@media (max-width: 860px) { .two-col { grid-template-columns: minmax(0, 1fr); } }

/* The statistic strip under the hero. */
.strip { background: var(--char); color: #fff; padding: clamp(1.6rem, 3vw, 2.4rem) 0; }
.strip .hero-stats { margin: 0; }

/* A list of links with a trailing gloss. */
.link-list { list-style: none; margin: 0; padding: 0; }
.link-list li { padding: .7rem 0; border-bottom: 1px solid var(--line); font-size: .95rem; color: var(--muted); }
.link-list li:last-child { border-bottom: 0; }
.link-list a { font-weight: 650; }

/* A tighter numbered list, for a four-step explainer inside prose. */
.numbered--tight > li { margin-bottom: 1.1rem; }

/* The honeypot. Off-screen rather than display:none, which some clients strip. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --------------------------------------------------- the enrolment form */

/* .form itself, .form .row, .form .check and .form .hint are defined above and
   are reused unchanged. A fieldset is not, and without these three rules the
   browser's default 2px groove border reads as a bug on a page that is
   otherwise borderless. */
.form fieldset { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.4rem 1.5rem 1.6rem; margin: 0; background: var(--paper); }
.form legend { font-family: var(--serif); font-size: 1.08rem; color: var(--ink); padding: 0 .5rem; margin-left: -.5rem; }
.form fieldset + fieldset { margin-top: .3rem; }
.form fieldset > label { margin-top: 1rem; }
.form fieldset > label:first-of-type { margin-top: .4rem; }
.form .hint + .check { margin-top: .9rem; }

/* ------------------------------------------------------------ timetable */

.tt-filters { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--bone); padding: 1.1rem 1.3rem 1.2rem; margin-bottom: 1.6rem; }
.tt-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; padding: .45rem 0; }
.tt-filter-row + .tt-filter-row { border-top: 1px solid var(--line); }
.tt-filter-h { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); width: 8.5rem; flex: none; }
.tt-filter-h svg { color: var(--iris-dk); }
.tt-chip {
  display: inline-block; padding: .32rem .72rem; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--paper);
  font-size: .84rem; font-weight: 600; color: var(--body); text-decoration: none;
  transition: border-color .15s, background .15s, color .15s;
}
.tt-chip:hover { border-color: var(--iris-dk); color: var(--iris-dk); }
.tt-chip.is-on { background: var(--ink); border-color: var(--ink); color: #fff; }
.tt-chip.is-on:hover { color: #fff; }
.tt-filter-note { font-size: .84rem; color: var(--muted); margin: .7rem 0 0; }
.tt-live-note { color: var(--muted); margin-bottom: 1.2rem; }
.tt-empty { padding: 2rem 1.4rem; text-align: center; color: var(--muted); background: var(--bone); border-radius: var(--r-lg); }

/* The class cell carries three lines: the discipline as a link, the level, and
   the age band. They are block-level inside the <th> rather than three cells,
   because seven columns is already the most a phone can carry. */
.tt-level { display: block; font-weight: 500; color: var(--body); }
.tt-ages { display: block; font-size: .8rem; font-weight: 500; color: var(--muted); margin-top: .15rem; }
.tt-table th[scope="row"] { min-width: 12rem; }
.tt-places { font-variant-numeric: tabular-nums; }
.tt-places[data-full="1"] { color: var(--signal); font-weight: 700; }

@media (max-width: 520px) {
  .tt-filter-h { width: 100%; }
}

/* --------------------------------------------------- the private-lesson diary */

.lesson-diary { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--paper); padding: clamp(1.2rem, 2.5vw, 1.8rem); }
.lesson-pick { margin-bottom: 1.4rem; }
.lesson-day { border-top: 1px solid var(--line); padding: 1rem 0; }
.lesson-day:first-child { border-top: 0; padding-top: 0; }
.lesson-day h3 { font-size: 1rem; margin-bottom: .6rem; }
.lesson-times { display: flex; flex-wrap: wrap; gap: .45rem; margin: 0; padding: 0; list-style: none; }
.lesson-time {
  display: inline-block; padding: .38rem .8rem; border-radius: var(--r-sm);
  border: 1px solid var(--line-2); background: var(--bone);
  font-size: .88rem; font-weight: 650; color: var(--body); cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.lesson-time:hover { border-color: var(--iris-dk); color: var(--iris-dk); background: var(--iris-lt); }
.lesson-time[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.lesson-none { font-size: .9rem; color: var(--muted); margin: 0; }

/* The three tones a form status line can take. `.hint` alone is the neutral
   one and is already defined above; these only change the colour, because a
   status that also moves or resizes makes the page jump under the reader. */
.hint--good { color: var(--amber-dk); font-weight: 600; }
.hint--cool { color: var(--amber-dk); }
.hint--warn { color: var(--signal); font-weight: 600; }

/* Two tokens the forked stylesheet never declared but this build's markup
   asks for. validate.mjs's class-coverage and custom-property checks found
   both — they were emitting an unstyled container and an invalid
   border-radius respectively, neither of which is visible enough to notice. */
:root { --r-sm: 5px; }
.lesson-slots { margin-top: .4rem; }

/* ==========================================================================
   THE GUEST HOUSE ADDITIONS.

   Six classes this build introduces. They are added here rather than left to
   be discovered, because house rules §18 records a build where the wrong
   class name went into a page brief and three agents used it seventy-nine
   times: every alternating band rendered plain white until the class-coverage
   check said so. Score the names against the stylesheet BEFORE writing pages.
   ========================================================================== */

/* A bare top-level link in the nav bar, beside the dropdown buttons. It has to
   match the buttons' metrics exactly or the bar looks misaligned at every
   width — which is why it borrows their padding rather than inventing its
   own. */
.nav-link {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .55rem .7rem; border-radius: var(--r);
  font: inherit; font-weight: 600; font-size: .95rem;
  color: var(--body); text-decoration: none; white-space: nowrap;
}
.nav-link:hover, .nav-link:focus-visible { background: var(--bone-2); color: var(--ink); }
.nav-link[aria-current="page"] { color: var(--iris-dk); }

/* THE NESTING FIGURE — the one drawing that carries the site's whole argument.
   The SVG is drawn in user units and scaled to its container, so the type
   inside it must be sized in those units too; `font-size` on the <text> would
   otherwise be scaled twice. */
.nesting { margin: 1.75rem 0 0; }
.nesting svg { display: block; width: 100%; height: auto; }
.nf-h { font-family: var(--serif); font-size: 17px; font-weight: 600; fill: var(--ink); }
.nf-n { font-family: var(--serif); font-size: 15px; font-weight: 600; fill: var(--ink); }
.nf-s { font-size: 11.5px; fill: var(--muted); }

/* The whole-house row in the rate table. It is the row that contains the
   other three, so it is marked rather than merely last. */
.rate-row--house { background: var(--iris-lt); }
.rate-row--house th[scope="row"] a { color: var(--warm-ink); }

/* The featured unit card on the home page. */
.svc-card--feature { border-color: var(--line-2); box-shadow: var(--shadow-lg); }

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

/* ------------------------------------------------- the availability result

   Four more classes, added because `booking.js` emits them and a class the
   stylesheet has never heard of renders as unstyled markup that no validator
   complains about. The `.avail-card` grid itself already exists; these are the
   parts inside it that the guest-house result needs and the hotel's did not —
   it drew a room list, and this draws four LETTINGS OF ONE BUILDING, so each
   card carries a photograph and a reason rather than a price alone. */
.avail-head { margin: 0 0 1.25rem; }
.avail-head h3 { margin: 0 0 .25rem; font-size: 1.25rem; }
.avail-head p { margin: 0; }

.avail-card-media { display: block; background: var(--bone-2); }
.avail-card-media img {
  width: 100%; height: 100%; aspect-ratio: 4 / 3; object-fit: cover;
}
.avail-card-body { padding: 1.1rem 1.15rem 1.25rem; display: grid; gap: .6rem; align-content: start; }
.avail-card-body h4 { margin: 0; font-family: var(--serif); font-size: 1.1rem; }
.avail-card-body h4 a { color: var(--ink); text-decoration: none; }
.avail-card-body h4 a:hover { color: var(--iris-dk); text-decoration: underline; }
.avail-card-body p { margin: 0; }
.avail-card-body .btn { justify-self: start; margin-top: .3rem; }

/* The one figure a guest most needs to see before they type a name. */
.bk-total { font-weight: 600; color: var(--ink); }

/* The key/value specification TABLE on a unit page.
   `.room-spec` above is a `<ul>` of icon+text facts and is keyed on `li`,
   `svg` and `span`; this is a two-column `<table>` and needed its own rules.
   They were one class for a while and the table rendered unstyled — the
   class-coverage check passed, because the CLASS existed. §13 recorded the
   same shape in a forked admin.css: score the ELEMENT selectors too. */
.unit-spec { width: 100%; border-collapse: collapse; font-size: .92rem; }
.unit-spec th, .unit-spec td {
  text-align: left; vertical-align: top; padding: .62rem .9rem .62rem 0;
  border-bottom: 1px solid var(--line);
}
.unit-spec th[scope="row"] {
  width: 34%; min-width: 9rem; font-weight: 600; color: var(--muted);
}
.unit-spec td { color: var(--body); overflow-wrap: anywhere; }
.unit-spec tr:last-child th, .unit-spec tr:last-child td { border-bottom: 0; }
@media (max-width: 520px) {
  .unit-spec th[scope="row"] { width: 42%; min-width: 0; }
}

/* ==========================================================================
   THE FOOTER IS A DARK BAND, AND EVERY COMPONENT PUT INSIDE IT MUST BE TOLD.

   `audit-live.mjs` reported 800 contrast failures across 49 pages x 2
   viewports, all of them here, and every one was a component that sets its own
   LIGHT-GROUND ink:

     .hours-table th { color: var(--ink) }    -> ink on ink, 1:1
     .hours-table td { color: var(--muted) }  -> 2.44:1
     .small          { color: var(--muted) }  -> 2.44:1

   The stylesheet's own comment already predicted this shape for `.info-card`,
   `.ind-card`, `.person`, `.plan` and `.sum` inside `.section--ink` (house
   rules §15 and §18), and named audit.mjs as the thing that would say so. It
   was right, and it was right about a band nobody had listed: the FOOTER.

   THE HOURS TABLE ONLY BECAME A FAILURE WHEN IT BECAME A TABLE. It shipped as
   a `<ul>` of spans, which inherited the footer's own colour and passed —
   while being completely unstyled, which is what check-selectors.mjs found and
   what the fix addressed. Fixing the markup is what exposed the colour. Two
   gates, two different defects, in the same six lines of output: neither one
   could see the other's.
   ========================================================================== */
.site-footer .hours-table caption { color: rgba(255,255,255,.5); }
.site-footer .hours-table th,
.site-footer .hours-table td { border-bottom-color: rgba(255,255,255,.12); }
.site-footer .hours-table th { color: #fff; }
.site-footer .hours-table td { color: rgba(255,255,255,.72); }
.site-footer .small { color: rgba(255,255,255,.6); }
.site-footer .footer-disclaimer { color: rgba(255,255,255,.78); }
.site-footer .footer-disclaimer strong { color: #fff; }

/* The chip's LINK, not just the chip.
   `.section--ink .chips li` was given a dark-ground colour and the `<a>`
   inside it was not, so eight boundary chips on the home page's dark band
   measured 1.48:1 — the anchor kept the light-ground link colour. Third time
   this exact shape has appeared in this build (unit-spec, the footer hours
   table, and now this): a component is styled for a dark ground and one
   ELEMENT inside it is missed. §13's rule, three times over — score the
   element selectors, not only the classes. */
.section--ink .chips a { color: inherit; text-decoration: none; }
.section--ink .chips li:hover { border-color: var(--iris); }
.section--ink .chips a:hover { color: var(--iris); }


/* == eye care: the one place red belongs == */
/* ==========================================================================
   .advice-note--urgent — THE ONLY RED BOX ON THIS SITE.

   The funeral home muted --warn from red to neutral for a good reason: it
   carried five ordinary qualifications on nearly every page, and a pink box
   repeated forty-five times reads as an error state. That fix is kept here
   and --warn is still the quiet, definite one.

   But this trade HAS a hazard, and it is the sharpest sentence on the site:
   sight lost suddenly, a curtain across the vision, a shower of new floaters
   with flashes, or a painful red eye with vision that has dropped is an
   emergency room today rather than an appointment. That is a safety warning
   and it should stop somebody.

   So the register is split. --warn is "read this properly" and appears
   everywhere. --urgent is "go now" and appears on three pages: the home page,
   the urgent visits page, and the boundary page about when to worry. If it
   ever appears on a fourth, the reason it works has gone.
   ========================================================================== */
.advice-note--urgent {
  border-color: var(--signal);
  border-left-color: var(--signal);
  border-left-width: 4px;
  background: var(--signal-lt);
  color: #7a2d24;
}
.advice-note--urgent svg { color: var(--signal); }
.advice-note--urgent strong { color: #5c1f18; }

/* ==========================================================================
   EYE CARE ADDITIONS — site.cjs, two classes it needed that nothing above
   declared.

   .svc-card-tag--amber IS THE JOIN, MADE A CLASS. --iris is already the
   sitewide default that every generic list/card icon resolves to
   (`.svc-card-tag svg`, `.link-more`, `.checklist svg`, `.chips` all read
   `var(--iris-dk)` above), so a CLINICAL card needs no extra marking — it
   already reads as clinical by doing nothing. A RETAIL card has to say so,
   the same way everywhere: this modifier, plus `.pill--amber` on the figure
   itself. Overriding only `color` and not `vertical-align` is deliberate —
   the base `.svc-card-tag svg` rule already sets the latter and this must
   not fight it, only recolour it; same specificity, later in the cascade,
   so the color wins without duplicating the alignment rule.

   .nav-link--book is "Book" styled as the primary action inside the nav bar
   proper, as a bare `.nav-link` (not a dropdown, not a `.btn` — the header
   already has a phone `.btn--primary` in `.header-cta` for that shape).

   SELECTOR SPECIFICITY, CHECKED RATHER THAN ASSUMED: `.nav > a` (declared
   above, for every bare top-level link) is one class plus a child combinator
   — specificity (0,1,1). `.nav-link--book` alone is one class — (0,1,0) —
   and LOSES to `.nav > a` on every property both set (colour, font-weight,
   padding), which would have silently undone this rule exactly the way
   house rules record for a component styled and then out-cascaded. `.nav
   .nav-link--book` is two classes — (0,2,0) — which beats (0,1,1)
   regardless of source order, so it is written that way, not as the bare
   class alone. */
.svc-card-tag--amber svg { color: var(--amber-dk); }

.nav .nav-link--book { background: var(--iris); color: var(--ink); font-weight: 700; }
.nav .nav-link--book:hover, .nav .nav-link--book:focus-visible { background: var(--iris-lt); color: var(--ink); }

/* ==========================================================================
   THREE CLASSES THIS BUILD EMITS AND THE PARENT STYLESHEET NEVER DEFINED.

   Found by validate.mjs's class-coverage check, which house rules §18 added
   after a build used `.section--bone` seventy-nine times for a class the
   stylesheet spells `.section--alt` and every alternating band rendered plain
   white until the check said so. Two of these are new UI this build's booking
   engine needs; the third has been emitted by the shared header for at least
   one generation and styled by nobody.
   ========================================================================== */

/* The running receipt in step one of the booking form: what has been chosen so
   far, what it adds to the length, and what it costs. It exists because this
   build's slot length is COMPUTED — type + extras + first-visit allowance —
   and a visitor picking three things should be able to see the arithmetic
   rather than discover it at the end. */
.bk-running {
  margin-top: 1.4rem; padding: 1rem 1.1rem;
  background: var(--amber-lt); border: 1px solid var(--line);
  border-left: 3px solid var(--amber-dk); border-radius: var(--r);
}
.bk-running-rows {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .45rem;
  font-size: .92rem; line-height: 1.6; color: var(--body);
}
.bk-running-rows li {
  display: flex; justify-content: space-between; gap: 1rem;
  overflow-wrap: anywhere;
}
.bk-running-rows li > span:last-child { color: var(--muted); white-space: nowrap; }
.bk-running-rows li[data-total] {
  border-top: 1px solid var(--line-2); padding-top: .5rem; font-weight: 650;
}
.bk-running-rows li[data-total] > span:last-child { color: var(--amber-dk); }

/* The secondary nav inside the mobile drawer. Emitted by site.cjs's header()
   and never styled — so on a phone the drawer's second-level links rendered as
   an unstyled inline list. Not visible in any desktop screenshot, which is
   why it survived. */
.drawer-sub-nav { display: block; margin-top: 1.4rem; }
.drawer-sub-nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
.drawer-sub-nav a {
  display: block; padding: .6rem .2rem;
  color: var(--body); text-decoration: none;
  font-size: .95rem; border-bottom: 1px solid var(--line);
}
.drawer-sub-nav a:hover, .drawer-sub-nav a:focus-visible { color: var(--iris-dk); }

/* ==========================================================================
   THE TODAY-ROW HIGHLIGHT IS A LIGHT-GROUND COMPONENT AND THE FOOTER IS DARK.

   `.hours-table tr[data-today]` paints `--iris-lt` behind the row and sets the
   cells to `--ink`, which is correct on a page. In the footer — where the
   hours table also appears, on all ninety-four pages — the surrounding rule
   has already made the cells WHITE for the dark band, so the light highlight
   lands under white text: audit.mjs measured 1.14:1 on the `<th>` and 1.1:1
   on the `<td>`.

   IT IS INVISIBLE SIX DAYS IN SEVEN. `data-today` is on exactly one row, so
   the defect only exists for whichever weekday it happens to be — it was found
   on a Tuesday, and a Wednesday run of the same audit would have reported it
   against a different word. That is the sharpest version of house rules §18's
   point about `.plan` on a dark ground: the stylesheet's own comment listed
   the components that would need this treatment "if one ever is" put in a dark
   band, and nobody listed a highlight that only paints one day a week.
   ========================================================================== */
.site-footer .hours-table tr[data-today] { background: rgba(169, 184, 232, .14); }
.site-footer .hours-table th[data-today] { color: #fff; }
.site-footer .hours-table td[data-today] { color: rgba(255, 255, 255, .92); }

/* ==========================================================================
   THE PAGE HEADER IS THE THIRD DARK BAND, AND `.small` FOUND IT.

   The stylesheet already carries two blocks about components that set their
   own light-ground ink and are then put on a dark ground: `.section--ink`
   (house rules §15, §18) and the FOOTER (§19, where it cost 800 contrast
   failures). Both of those blocks name `.small { color: var(--muted) }` as
   one of the offenders. Neither listed the PAGE HEADER, which is `--ink` on
   every one of the ninety-four pages.

   `.pagehead-meta` carries the appointment length and fee under the title,
   and the first-visit allowance inside it is a `<span class="small">`. On
   ink that measured 2.25:1.

   IT PASSED audit.mjs AND FAILED audit-live.mjs, which is the useful part.
   The two gates use different thresholds — 3:1 for the local sweep, 4.5:1
   for body text on the live one — and at 2.25:1 this was under both, but the
   local run only reports what it is asked about. §19's rule earns its keep
   again: re-run the other gates after fixing one, and believe the stricter.
   ========================================================================== */
.pagehead .small,
.pagehead-meta .small { color: rgba(255, 255, 255, .72); }
.pagehead .small a { color: var(--iris); }
