/* ==========================================================================
   Woodside Massage & Acupuncture — design system. Forked from the demo
   before it (demo 46) and re-paletted for this build by
   _tools/palette-massage.mjs, which swaps the :root tokens AND every
   hardcoded literal of the old palette (house rules §12) and asserts none is
   left. The header below describes THIS build.

   INHERITED COMPONENT SETS. Roughly half the classes defined below belong to
   components no page of this build emits — the restaurant's menu, the
   driving school's lesson picker, the venue's floor plans, the storage
   estimator, the photographer's portfolio grid — carried through the fork
   chain. Their section headers name the build each came from and are
   accurate about THAT build. validate.mjs checks the other direction (every
   class emitted is defined), so removing a dead set is safe to do.

   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 with
   Cyrillic coverage (house rules §7: the Bulgarian mirror is owed later).

   ------------------------------------------------------------ THE PALETTE

   BOTTLE GREEN, BRASS AND BONE — the shop's own materials: the painted
   wainscot, the brass coat hooks and the cream of a folded towel. The
   oxblood of the chairs lives in the photographs and nowhere in the
   interface, because a red accent next to a red refusal is the component
   wearing the wrong accent that every contrast check passes (§20).

   --seal     BRASS (dark grounds): links, icons, rules and the one lit thing
              in a dark band.
   --seal-dk  BOTTLE GREEN, its light-ground twin, for links and icons on
              white and bone.
   --brass    EVERY PRICE on a dark ground — a pale cream, so a price reads
              as information rather than as a sale. --brass-dk is its twin on
              light grounds.
   --signal   crimson brick: form validation and a refusal, nothing else.

   Contrast is measured by _tools/audit.mjs and audit-live.mjs, not asserted
   here.

   THE HERO'S TWO CHILDREN ARE BOTH POSITIONED — see .hero-media img, video.
   Six builds left the poster and the video in normal flow, so the video was
   laid out beneath the clipped hero box while every gate reported it playing
   (house rules §16).
   ========================================================================== */

:root {
  --ink:        #132a2c;   /* deep teal-slate — the hero ground, the footer and every dark band */
  --char:       #1b3537;
  --char-2:     #244244;
  --char-3:     #647577;
  --seal:       #e2b99a;   /* WARM CLAY, dark grounds only: links, icons and rules on ink */
  --seal-dk:    #2b5b5e;   /* deep teal, the light-ground twin: links and icons on --paper and --bone */
  --seal-lt:    #eef5f5;
  --brass:      #f4e6da;   /* every price on a dark ground: pale clay */
  --brass-dk:   #6b3f27;   /* every price on a light ground: burnt clay */
  --brass-lt:   #faf1ea;
  --signal:     #a3363c;   /* crimson brick: form validation and a refusal, nothing else */
  --signal-lt:  #f8e9ea;
  --bone:       #f7f3ee;   /* linen */
  --bone-2:     #eee7df;
  --paper:      #ffffff;
  --line:       #e5ddd4;
  --line-2:     #cbc2b6;
  --body:       #1b2324;   /* contrast measured by audit.mjs, not asserted here */
  --muted:      #4f5a5b;
  --muted-lt:   #5a6566;   /* small labels only */
  --warm-line:  #efd9c9;   /* border of the --warm advice note */
  --warm-ink:   #5e3520;   /* 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(19, 42, 44, .05), 0 8px 24px -12px rgba(19, 42, 44, .20);
  --shadow-lg: 0 2px 4px rgba(19, 42, 44, .06), 0 24px 48px -20px rgba(19, 42, 44, .30);
}

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

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

/* [hidden] MEANS HIDDEN (this build's brief, addendum 3, from demo 40). The UA
   stylesheet's `[hidden] { display: none }` loses to ANY class that sets
   display — and it did here: `.bk-grid { display: grid }` kept the CAT
   vaccination fields (FVRCP) on screen for every dog owner on book.html,
   desktop and mobile, while every gate passed. check-live.mjs found it on the
   live site the first time it asked, and asserts it on every page. */
[hidden] { display: none !important; }

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(--seal-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(--seal-lt); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--seal-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; } }

/* ==========================================================================
   .sr — VISUALLY HIDDEN, STILL READ ALOUD.

   Used by site.cjs's dmv() for "(opens dmv.ny.gov in a new tab)" and by the
   overflow-safe patterns in the tables. It must NOT be display:none, which
   removes it from the accessibility tree as well, and it must not have a
   width or a height of zero for the same reason. The clip-path form below is
   the one that survives a forced-colours mode.

   §24.5 records a sibling where 136 of these, inside a scroll box whose
   wrapper was `position: static`, escaped their container and made the
   document 705px wide on a 400px phone — because a scroll box does not clip
   what it does not contain. `.table-wrap` and `.table-scroll` are
   `position: relative` in this sheet for that reason; check it before
   moving one of these inside a new scroll box.
   ========================================================================== */
/* ==========================================================================
   TWO MODIFIERS THE BOOKING PANELS NEED AND THE FORK DID NOT CARRY.

   §18/§19: score a class name against this sheet BEFORE writing the markup,
   not after. These two were written by the booking page and had no rule, so
   they are declared here rather than left as classes that style nothing —
   which is the defect that shipped four components on a sibling build and
   passed its class-coverage check on all four, because the class existed.
   ========================================================================== */
.bk-field--wide { grid-column: 1 / -1; }

.slot--wait {
  background: var(--bone-2);
  color: var(--muted);
  border-style: dashed;
}
.slot[disabled] { opacity: .5; cursor: not-allowed; }

.sr {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.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(--seal-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--seal { background: var(--seal); color: var(--ink); border-color: var(--seal); }
.btn--seal:hover { background: var(--seal-lt); border-color: var(--seal-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 SEAL, NOT INK.

   .btn--primary is a dark fill (--char) 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 MEASURES fine — white on --char is 12.5:1 — and it
   looks wrong, which is exactly the gap between a legibility check and a
   design one. A LEGIBILITY CHECK IS NOT A DESIGN CHECK; a sibling build
   shipped this at 16:1 and it still read as a hole in the page.

   The fix is a LIGHT fill, so the failure cannot recur by degrees: --seal on
   --ink is 7.1:1 as a RECTANGLE, not merely as text, and --ink on --seal is
   the same 7.1:1 for the label. Nothing about it is a dark shape on a dark
   shape.

   SEAL RATHER THAN BRASS, and that is a meaning decision rather than a
   contrast one — brass measures 7.4:1 here and would look fine. The primary
   action on this site is "get a document that will be accepted", which is the
   official half, and brass is reserved for prices. A brass primary button
   would quietly turn every call to action into a quotation.

   .btn--seal carried this declaration, unused, 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(--seal); color: var(--ink); border-color: var(--seal);
}
.hero .btn--primary:hover,
.section--ink .btn--primary:hover,
.cta-band .btn--primary:hover {
  background: var(--seal-lt); border-color: var(--seal-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(--seal-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(--seal); flex: none; }
.topbar-note { display: flex; align-items: center; gap: .42rem; white-space: nowrap; }
.topbar-note svg { color: var(--seal); }
.topbar a { color: #fff; text-decoration: none; font-weight: 600; }
.topbar a:hover { color: var(--seal-lt); }

/* `.topbar-left li { display: flex }` out-specifies a bare `.topbar-hide-sm`,
   so the rule below never hid anything until it matched the li's specificity. */
@media (max-width: 900px) { .topbar-left .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 .wrap { justify-content: center; }

  /* WHICH HALF OF THE STRIP SURVIVES A PHONE IS A DECISION, AND THE INHERITED
     ONE WAS WRONG FOR THIS BUILD.

     The parent hid `.topbar-left` entirely below 640 and kept the office
     hours. On this site the left list carries the DEMONSTRATION NOTICE, which
     house rule §5 requires a visitor to be able to see — so hiding it leaves
     a phone visitor with a site that looks like a real business until they
     reach the footer.

     Found by measuring rather than by looking: at 390 the notice's `<li>`
     computed `display: flex` and a width of ZERO, squeezed out by the note
     beside it. An element that reports itself displayed and occupies no space
     is §24.1's family — the page claiming something the visitor cannot see.

     So the office hours go instead. They are on the contact page, in the
     footer of every page, and behind the menu button; the notice is not. */
  .topbar-note { display: none; }
  .topbar-left { flex: 1 1 auto; justify-content: center; }
  .topbar-left li { justify-content: center; text-align: center; }

  /* The surviving line is nowrap on desktop so its icon and sentence stay
     together. On a 390px phone a nowrap string of this length is wider than
     the screen and pushes the whole document sideways — a horizontal scroll
     on every page, 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-left li {
    white-space: normal;
    flex-wrap: wrap;
    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(--seal-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. */
/* margin-left:auto on the CTA: the hidden .nav was what pushed it right. */
@media (max-width: 1023px) { .nav { display: none; } .burger { display: inline-flex; } .header-cta { margin-left: auto; } }
@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(19, 42, 44,.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(--seal-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(--seal-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(19, 42, 44,.88) 0%, rgba(19, 42, 44,.78) 44%, rgba(19, 42, 44,.42) 76%, rgba(19, 42, 44,.12) 100%),
    linear-gradient(to top, rgba(19, 42, 44,.74), transparent 45%);
}
/* PADDING-BLOCK, NOT THE SHORTHAND. This sits on `<div class="wrap hero-inner">`,
   and `padding: X 0 Y` zeroed .wrap's side padding: hero text touched both
   edges of a phone. And .wrap's `margin-inline: auto` centred the 660px column
   in a 1440 screen instead of lining it up with the logo; the left margin now
   is the wrap's own gutter. (Both reported by the demo 38 session, measured
   here before fixing: h1 at 0-390 on a 390 screen, 390-1050 at 1440.) */
.hero-inner {
  position: relative; z-index: 2;
  padding-block: clamp(4rem, 9vw, 7.5rem) clamp(3rem, 6vw, 5rem);
  max-width: 660px;
  margin-inline: max(0px, calc((100% - var(--wrap)) / 2)) auto;
}
.hero h1 { color: #fff; margin-bottom: .5em; }
.hero .eyebrow { color: var(--seal); }
.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;
  /* AS MANY COLUMNS AS THERE ARE FIGURES, not four. The hero carries three,
     and a fixed four-column track left an empty lit panel beside them that
     read as a stat somebody had forgotten to write — invisible to every gate
     in this build (it is not overflow, it is not contrast, it is not a
     console error) and plain in the first screenshot anybody looked at. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 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(19, 42, 44,.62); }
.hero-stats dt {
  font-family: var(--serif); font-size: 1.75rem; color: var(--seal);
  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-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* AND AN ODD ONE OUT SPANS THE PAIR, rather than sitting beside an empty
     lit panel — the same defect as the desktop fourth column, one breakpoint
     down, and the phone screenshot is where it shows. */
  .hero-stats div:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* -------------------------------------------------------------- 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(19, 42, 44,.95) 0%, rgba(19, 42, 44,.88) 42%, rgba(19, 42, 44,.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(--seal); }
.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(--seal-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(--seal); 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(--seal); 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
   --seal-dk, because inside .svc-card the ground is the card's own paper. */
.section--ink .link-more { color: var(--seal); }
.section--ink .svc-card .link-more { color: var(--seal-dk); }
.section--ink .svc-card-tag { color: var(--seal-dk); }
.section--ink .eyebrow { color: var(--seal); }
.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(--seal-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(226, 185, 154,.16); border-color: rgba(226, 185, 154,.34); color: var(--seal); }
.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(--seal-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(--seal-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(--seal-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(--seal-dk); flex: none; margin-top: .3em; }
.section--ink .checklist svg { color: var(--seal); }

.deflist { list-style: none; margin: 0; padding: 0; }
/* a list that follows or precedes running prose inside the same wrap needs
   air: its first item has no top padding, so it sat flush under the lede
   (how-these-pictures-were-made.html, seen in the marketing shot). */
.prose + .deflist { margin-top: 1.8rem; }
.deflist + .prose { margin-top: 1.8rem; }
.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(--seal-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(--seal-dk); border-bottom: 1.5px solid var(--seal-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(--seal); 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(--seal-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(--seal); }
.cta-band .lead { color: rgba(255,255,255,.8); max-width: 62ch; margin: 0; }
.cta-band .btn--primary { background: var(--seal); color: var(--ink); border-color: var(--seal); }
.cta-band .btn--primary:hover { background: var(--seal-lt); border-color: var(--seal-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(--seal);
  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(--seal-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(--seal-lt); border-color: var(--warm-line); border-left-color: var(--seal-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(43, 91, 94,.18);
}
.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(--seal-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;
  /* --seal 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. --seal-dk is the same hue at 5.5:1. */
  color: var(--seal-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(--seal); }
.footer-contact li { display: flex; align-items: flex-start; gap: .55rem; }
.footer-contact svg { color: var(--seal); 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(--seal); 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(--seal-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 --seal, NOT --signal, AND THAT IS THE PALETTE'S
   OWN RULE APPLIED. This stylesheet's header says --seal is THE OFFICIAL HALF
   (certification, acceptance, every primary action and the telephone number)
   and --signal is THE REFUSAL AND THE DEADLINE. "Bring the document in and we
   will tell you what the consulate will accept" is not a refusal.

   It arrived in the signal family from the parent build, with a --seal-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(--seal-lt); border: 1px solid #efd9c9; border-left: 3px solid var(--seal-dk);
  border-radius: var(--r); padding: .95rem 1.15rem; margin: 0 0 2rem;
}
.emerg svg { color: var(--seal-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(--seal-dk); color: var(--seal-dk); }
/* A badge that carries a whole price line ("beard trim and line-up $25 · beard
   sculpt $35") ran 48px off a 320px screen with nowrap (sweep-overflow,
   2026-09-25). On a narrow phone it may wrap, and a two-line pill is
   squared off so it still reads as one tag. */
@media (max-width: 420px) {
  .badge { white-space: normal; max-width: 100%; border-radius: .7rem; }
}
.badge svg { flex: none; }
.badge--veg { border-color: #e2b99a; color: #2b5b5e; background: #eef5f5; }
a.badge--veg:hover { border-color: #2b5b5e; color: #5e3520; }
.badge--heat { border-color: #efd3d5; color: var(--signal); background: var(--signal-lt); }
a.badge--heat:hover { border-color: var(--signal); color: #7a2528; }

/* --------------------------------------------------- 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(--seal-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(--seal-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(--seal-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(--seal-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(--seal-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(--seal-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(--seal); 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(--seal-dk); background: var(--seal-lt);
  box-shadow: 0 0 0 1px var(--seal-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(--seal); 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(--seal-dk); background: var(--seal-lt); }
.cal-day[aria-pressed="true"] {
  background: var(--ink); border-color: var(--ink); color: #fff;
}
.cal-day[aria-pressed="true"] .cal-dot { background: var(--seal); }
.cal-day.empty { background: transparent; }
.cal-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--seal-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(--seal-dk); background: var(--seal-lt); }
.slot[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.slot[aria-pressed="true"] small { color: var(--seal); }

.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(--seal-dk); box-shadow: 0 0 0 3px var(--seal-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(--seal-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(--seal-lt); color: var(--seal-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: #7a2528; 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(--seal-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(--seal-lt); color: var(--seal-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(--seal-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(--seal-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(--seal-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(--seal-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(247, 247, 245, .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(--seal-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(--seal-lt); border-radius: var(--r);
  padding: .5rem .7rem; margin: .55rem 0 0; max-width: 62ch;
}
.dish-note svg { color: var(--seal-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(--brass-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(--seal-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(--seal-dk); background: var(--seal-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(--seal-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(--seal-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(--seal-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. INHERITED FROM A FOOD BUILD, WHERE IT WAS RED AND
   RED WAS RIGHT — there it carried the allergen notice, which is a hazard.
   This declaration is superseded further down the cascade by the neutral
   version; it is left here because the override is easier to read as an
   override than as the only rule. See .advice-note--warn below for why red
   is wrong for this trade. */
.advice-note--warn {
  border-left-color: var(--signal);
  background: var(--signal-lt);
  color: #7a2528;
}
.advice-note--warn svg { color: var(--signal); }
.advice-note--warn strong { color: #5a1b1f; }

.small { font-size: .86rem; line-height: 1.7; color: var(--muted); }
.small a { color: var(--seal-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(--seal-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(--brass-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: #efd3d5; color: #7a2528; }
.avail-state--full svg { color: var(--signal); }
.avail-state--arrivals { background: var(--seal-lt); border-color: var(--warm-line); color: var(--warm-ink); }
.avail-state--arrivals svg { color: var(--seal-dk); }
.avail-state--minstay { background: var(--brass-lt); border-color: #ecd8c8; color: #5a3a26; }
.avail-state--minstay svg { color: var(--brass-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(--seal-dk); color: var(--seal-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(--seal);
  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, and on this build it is THE PRICE ONE.
   --warm is the seal "this is what the receiving authority requires"; --warn
   is the quiet ink "read this properly"; --cool is BRASS, and it carries the
   estimate caveat — what a quotation includes, what it excludes, and what
   changes it. Money is brass everywhere on this site, so the note that
   qualifies a price is brass too rather than a fourth colour. */
.advice-note--cool {
  border-left-color: var(--brass-dk);
  background: var(--brass-lt);
  color: #53341f;
}
.advice-note--cool svg { color: var(--brass-dk); }
.advice-note--cool strong { color: #3b2416; }

/* 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(--seal-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. Inherited: no page in this build prints one, and
   the rule is kept only because the class list is shared. */
.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--brass { border-color: var(--brass-dk); color: var(--brass-dk); background: var(--brass-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(--brass-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(--brass-dk); flex: none; }
.sum-rows { list-style: none; margin: 0; padding: 0; font-size: .93rem; }
/* The label column has a FLOOR (demo 47): a spoken length in the middle
   column ("an hour and forty minutes") sized to its max-content squeezed the
   label to one letter per line. The middle column may wrap instead. */
.sum-rows li {
  display: grid; grid-template-columns: minmax(7.5rem, 1fr) minmax(0, 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-rows li.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(--brass-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(--seal-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(--seal); }
.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(--seal-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(--seal); }
.section--ink .info-card svg { color: var(--seal); }
.section--ink .info-card .info-list a { color: var(--seal); }

/* ==========================================================================
   .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.

   AND IT CAME TRUE AGAIN, TWICE, IN THIS BUILD. .person (the interpreter
   roster) and .sum (the worked estimate) are both put in .section--ink bands
   here, and .ind-card is one careless section from joining them. All three
   now have real dark-ground blocks in the palette-swap tail at the foot of
   this file, written the same way .info-card and .plan were: a translucent
   well with a hairline, rather than a light card with its text colours
   restated. Nothing in this family is "still light-ground only" any more,
   which is the first time that sentence has been retired instead of inherited.
   ========================================================================== */
.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(--seal);
}
.section--ink .plan h3 { color: #fff; }
.section--ink .plan-price { color: #fff; }
.section--ink .plan-price strong { color: var(--brass); }
.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(--seal); }
.section--ink .plan-best { color: rgba(255, 255, 255, .72); }
.section--ink .plan-best strong { color: #fff; }
.section--ink .plan-flag { background: var(--brass); 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(--seal-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 everyday
   variants — seal is "this is what the receiving authority requires", brass
   is "this is what it costs", ink is "read this properly" — and it stops
   shouting.

   THIS BUILD IS THE STRONGEST CASE THE FIX HAS HAD. A translation office
   carries the demonstration notice and an estimate caveat on very nearly
   every page, plus TEN boundary pages about what it will not certify, will
   not swear to and will not notarise. Every one of those is a qualification
   and not one of them is a hazard. Painted red they would make this the most
   alarming site in the portfolio and would leave nothing left to say with red
   when something actually cannot be done.

   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(--seal-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(--seal-dk); color: var(--seal-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(--seal-dk); color: var(--seal-dk); background: var(--seal-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(--brass-dk); font-weight: 600; }
.hint--cool { color: var(--brass-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(--seal-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(--seal-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(--seal-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(--seal); }
.section--ink .chips a:hover { color: var(--seal); }

/* ==========================================================================
   TWO CLASSES site.cjs NEEDS THAT NOTHING ABOVE DECLARES.

   .svc-card-tag--brass IS THE JOIN, MADE A CLASS. --seal 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(--seal-dk)` above), so a CERTIFIED-WORK card needs no extra marking —
   it already reads as official by doing nothing. A COMMERCIAL card has to say
   so, the same way everywhere: this modifier, plus `.pill--brass` 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--brass svg { color: var(--brass-dk); }

.nav .nav-link--book { background: var(--seal); color: var(--ink); font-weight: 700; }
.nav .nav-link--book:hover, .nav .nav-link--book:focus-visible { background: var(--seal-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(--brass-lt); border: 1px solid var(--line);
  border-left: 3px solid var(--brass-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(--brass-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(--seal-dk); }

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

   `.hours-table tr[data-today]` paints `--seal-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(226, 185, 154, .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(--seal); }


/* == translation: palette-swap tail — generated by _tools/swap-palette.mjs == */
/* ==========================================================================
   .advice-note--urgent — THE ONLY RED BOX ON THIS SITE, AND IT IS RARE.

   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, and
   this build is the strongest case it has had — the demonstration notice and
   an estimate caveat appear on very nearly every page here, and there are TEN
   boundary pages besides.

   NONE OF THOSE TEN GETS RED. "We do not provide sworn translation, because
   the United States has no sworn-translator institution" is not a hazard and
   not a failure; it is an explanation, and it wears --warn. That is the whole
   decision: if all ten boundary pages were red then red would have stopped
   meaning anything by the second one, and there would be nothing left to say
   with it.

   So --urgent is kept for the two things on this site that genuinely stop
   somebody: a form field that has failed validation, and a deadline that
   cannot be moved — a filing date, a consular appointment, a court date that
   the document has to exist before. If it starts appearing on the boundary
   pages, 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: #7a2528;
}
.advice-note--urgent svg { color: var(--signal); }
.advice-note--urgent strong { color: #5a1b1f; }

/* ==========================================================================
   THE DARK GROUND, PART THREE — .person, .sum AND .ind-card.

   The stylesheet has now made this same fix three times, in three separate
   blocks, each written as if it were the first: .svc-card (six invisible
   headings at 1.00:1), .info-card (six elements at 1.05:1), .plan (eighteen
   elements at 1:1, and the comment above .plan had PREDICTED it by name a
   generation earlier). Every time, the cause is the same one sentence — a
   dark section does not make everything inside it dark-ground, and a
   component carrying its own light background paints white text onto white
   paper, where there is nothing to see because there is nothing there.

   The prediction listed three components it had not yet had to fix:
   .ind-card, .person and .sum. THIS BUILD USES TWO OF THEM ON DARK BANDS —
   .person for the interpreter roster and .sum for the worked estimate — so
   rather than wait for audit.mjs to report the same defect a fourth and fifth
   time, all three are done here, including the one that is not on a dark band
   yet. Finishing the list is cheaper than being right about it again.

   .sum IS THE INTERESTING ONE, because it would have PASSED. Every text
   colour inside it is restated explicitly (--ink, --muted), so on a dark band
   it is perfectly legible — a light card sitting in a dark stripe, wrong
   rather than unreadable, and audit.mjs reports zero failures on it. That is
   §20's shape exactly: a component that measures fine and is still incorrect.
   Only a screenshot, or this comment, catches it.

   All three become real dark-ground cards — a translucent well with a
   hairline — rather than light cards with their text colours restated. The
   well is rgba(255,255,255,.05) over --ink, which puts white at 12.98:1,
   white-.78 body copy at 8.56:1 and --seal at 6.13:1.

   SPECIFICITY WAS CHECKED RATHER THAN ASSUMED, because a rule that loses the
   cascade is the same defect as a rule that was never written. Every selector
   below is the base selector plus .section--ink, so each is exactly one class
   heavier than the rule it overrides.
   ========================================================================== */

/* .person — the interpreter roster. The photograph sits above the text and is
   unaffected; the card body is what changes ground. */
.section--ink .person {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .14);
}
.section--ink .person h3 { color: #fff; }
.section--ink .person-role { color: var(--seal); }
.section--ink .person p:not(.person-role) { color: rgba(255, 255, 255, .78); }
.section--ink .person .bio { color: rgba(255, 255, 255, .78); }
.section--ink .person-focus { color: rgba(255, 255, 255, .78); }
.section--ink .person-focus svg { color: var(--seal); }

/* .sum — the worked estimate, the one component here that shows the words and
   the minutes being added up into a number. IT IS BRASS, NOT SEAL: it is the
   estimator, which is the commercial half, and the total at the bottom of it
   is a price. Its 3px left rule moves from --brass-dk (a light-ground value,
   2.1:1 against this well) to --brass, 6.4:1. */
.section--ink .sum {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .14);
  border-left-color: var(--brass);
}
.section--ink .sum-h { color: #fff; }
.section--ink .sum-h svg { color: var(--brass); }
.section--ink .sum-rows li {
  color: rgba(255, 255, 255, .78);
  border-bottom-color: rgba(255, 255, 255, .12);
}
/* ".sum-rows li.sum-total", NOT ".sum-total". The bare class is (0,2,0) here
   and loses to ".section--ink .sum-rows li" (0,2,1) three lines up — the same
   specificity collision that had kept the base rule dead for generations, and
   I walked straight into it again writing this block. The browser said so;
   the arithmetic in the comment above it did not. */
.section--ink .sum-rows li.sum-total {
  color: var(--brass);
  border-top-color: rgba(255, 255, 255, .22);
}
.section--ink .sum-note { color: rgba(255, 255, 255, .72); }

/* .ind-card — not on a dark band today. Done anyway, for the reason above. */
.section--ink .ind-card {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .14);
}
.section--ink .ind-card h3 { color: #fff; }
.section--ink .ind-card p { color: rgba(255, 255, 255, .78); }

/* ==========================================================================
   THE FOUR DARK BANDS, LISTED ONCE.

   Three separate blocks in this file each discovered independently that a
   component had been dropped onto a dark ground: .section--ink (house rules
   §15, §18), the FOOTER (§19, 800 contrast failures across 49 pages) and the
   PAGE HEADER (found by a single `.small` at 2.25:1, which passed the local
   audit's 3:1 threshold and failed the live audit's 4.5:1). The hero is the
   fourth and was never listed at all, because its own block happened to
   restate everything.

   All four are var(--ink). Any component that sets its own light background
   or its own light-ground ink needs a rule for each of the four it can appear
   in. That is the sentence; it has cost four separate investigations to write
   it down in one place.
   ========================================================================== */
/* == end palette-swap tail == */

/* ==========================================================================
   THE WOODSIDE HALL ADDITIONS.

   Floor plans, drawn by site.cjs's floorPlan() from the engine's layout
   figures. The SVG carries no fill or stroke of its own — every colour is
   here, so a palette change reaches the drawings, and `vector-effect:
   non-scaling-stroke` keeps a small room's lines as thin as a large one's
   when both are scaled to the same column width.
   ========================================================================== */
.floorplan { margin: 0 0 1.6rem; }
.floorplan svg { display: block; width: 100%; height: auto; background: var(--paper); border-radius: var(--r); }
.floorplan svg * { vector-effect: non-scaling-stroke; }
.floorplan figcaption { font-size: .82rem; color: var(--muted); margin-top: .55rem; }
.fp-room { fill: var(--bone); stroke: var(--ink); stroke-width: 2.5; }
.fp-table { fill: var(--paper); stroke: var(--seal-dk); stroke-width: 1.6; }
.fp-chair { fill: var(--seal-dk); }
.fp-floor { fill: var(--brass-lt); stroke: var(--brass-dk); stroke-width: 1; stroke-dasharray: 4 3; }
.fp-door { fill: var(--brass-dk); }
.fp-label { fill: var(--ink); font-family: var(--sans); }

/* ==========================================================================
   THE VENUE BOOKING APP — the classes _tools/app-mounts.cjs and
   assets/js/venue-booking.js / tour.js emit that the forked stylesheet did not
   carry. Grep `vb-` in app-mounts.cjs for the checklist this answers.
   ========================================================================== */

/* The month view. Four day states and a past day; the colour is never the
   only signal — every day button carries its state word as its label, and
   the key under the grid prints the same four words. */
.vb-cal { margin: 0 0 1.5rem; }
.cal-day[data-day-state] {
  background: var(--paper); border-color: var(--line-2); color: var(--ink);
  font-weight: 600; cursor: pointer; position: relative;
}
.cal-day[data-day-state]:hover:not(:disabled) { border-color: var(--ink); }
.cal-day[data-day-state="free"] .cal-dot { background: var(--seal-dk); }
.cal-day[data-day-state="part"] { background: var(--seal-lt); border-color: #d3e6e6; }
.cal-day[data-day-state="part"] .cal-dot { background: var(--brass-dk); }
.cal-day[data-day-state="hold"] { background: var(--brass-lt); border-color: #ecd8c8; color: #5a3a26; }
.cal-day[data-day-state="hold"] .cal-dot { background: var(--brass-dk); }
.cal-day[data-day-state="booked"] { background: var(--signal-lt); border-color: #efd3d5; color: #7a2528; }
.cal-day[data-day-state="booked"] .cal-dot { background: var(--signal); }
.cal-day[data-day-state="past"] {
  background: transparent; border-color: transparent; color: var(--muted-lt);
  font-weight: 500; cursor: default;
}
.cal-day[data-day-state][aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.cal-day[data-day-state][aria-pressed="true"] .cal-dot { background: var(--seal); }

.vb-key { width: .8rem; height: .8rem; border-radius: 3px; display: inline-block; border: 1px solid var(--line-2); }
.vb-key--free { background: var(--paper); }
.vb-key--part { background: var(--seal-lt); border-color: #d3e6e6; }
.vb-key--hold { background: var(--brass-lt); border-color: #ecd8c8; }
.vb-key--booked { background: var(--signal-lt); border-color: #efd3d5; }

/* The nav buttons carry their words for a screen reader and draw a chevron
   for everybody else — the forked .cal-nav rule rotated an <svg> these
   buttons do not have. */
.vb-cal-nav-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.cal-nav button { position: relative; }
.cal-nav button::before {
  content: ""; width: .5rem; height: .5rem;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
}
.cal-nav button.prev::before { transform: translateX(.1rem) rotate(45deg); }
.cal-nav button.next::before { transform: translateX(-.1rem) rotate(-135deg); }

/* One room's answer for the date. */
.vb-space-row {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem;
  padding: 1rem 0; border-bottom: 1px solid var(--line);
}
.vb-space-row:first-child { padding-top: 0; }
.vb-space-row:last-child { border-bottom: 0; padding-bottom: 0; }
.vb-space-row > div:first-child { min-width: 0; }
.vb-space-name { font-weight: 650; color: var(--ink); line-height: 1.35; }
.vb-space-note { margin-top: .2rem; line-height: 1.5; }
.vb-space-row .avail-price { min-width: 0; flex: none; }
.vb-space-row .avail-price .sub { margin-bottom: 0; }
.vb-space-row[data-state="blocked"] .avail-price .big { font-size: 1.1rem; color: #7a2528; }
.vb-space-row[data-state="blocked"][data-blocked="hold"] .avail-price .big { color: #5a3a26; }
.vb-space-row[data-state="season"] .avail-price .big { font-size: 1.1rem; color: var(--muted); }
@media (max-width: 520px) {
  .vb-space-row { flex-direction: column; gap: .35rem; }
  .vb-space-row .avail-price { text-align: left; }
}

/* The mounts' root classes are the scripts' hooks, and they also clear the
   sticky header when a link jumps to one (check-a-date.html#enquire). */
.vb-datechecker, .vb-enquiry, .vb-tour { scroll-margin-top: 6rem; }
.advice-note--lead { margin: 0 0 1.8rem; }
.vb-tour-form { margin-top: 1.6rem; padding-top: 1.4rem; border-top: 1px solid var(--line); }
.vb-tour-event-hint { margin: .4rem 0 0; }
.vb-out:empty { display: none; }
.vb-out { margin-top: 1.4rem; }

/* ==========================================================================
   MOTION — a little, done properly (house rules §10).

   ONE scroll listener and ONE rAF per frame in site.js, reads before writes.
   Transforms and opacity only, so CLS stays 0. The hero IMAGE and VIDEO never
   animate (the poster is the LCP element); only the hero's text enters.
   Content is visible WITHOUT JavaScript: site.js adds `.rv` only to elements
   below the fold at load, so nothing is ever hidden that a script failed to
   reveal. Entrances are a scroll-driven SWEEP, not an IntersectionObserver,
   which a fast wheel or an anchor jump can skip. Everything is off under
   prefers-reduced-motion, and the screenshot scripts neutralise .rv/.hx.
   ========================================================================== */
.hero-inner > * { animation: hx-rise .9s cubic-bezier(.2, .7, .2, 1) both; }
.hero-inner > *:nth-child(2) { animation-delay: .08s; }
.hero-inner > *:nth-child(3) { animation-delay: .16s; }
.hero-inner > *:nth-child(4) { animation-delay: .24s; }
.hero-inner > *:nth-child(5) { animation-delay: .32s; }
@keyframes hx-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

.rv { opacity: 0; transform: translateY(22px); transition: opacity .7s ease, transform .8s cubic-bezier(.2, .7, .2, 1); }
.rv.is-in { opacity: 1; transform: none; }

/* The page header photograph drifts a few pixels against the scroll. Scaled
   up so the drift never shows an edge; transform only, never opacity. */
/* --px is declared here and overwritten INLINE on the same element by
   site.js (headImg.style.setProperty), so the inline value always wins. */
.pagehead-media img { --px: 0px; transform: translate3d(0, var(--px), 0) scale(1.08); will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .hero-inner > * { animation: none; }
  .rv { opacity: 1; transform: none; transition: none; }
  .pagehead-media img { transform: none; }
}

/* ==========================================================================
   THE PRICE BLOCK — the one component that renders a rate, and the reason it
   is a component rather than a span.

   6 RCNY s 2-496(a), in force 1 November 2026, makes it a deceptive trade
   practice in New York City to advertise a storage unit without showing the
   OCCUPANCY FEE more prominently than any other pricing information, and
   without showing — AS PROMINENTLY AS THE FEE ITSELF — the conditions on it,
   its guaranteed duration, and the total of any non-recurring mandatory
   charge payable before occupancy.

   "As prominent as" is a TYPOGRAPHIC requirement, so it is met here rather
   than in the markup: .price-due and .price-hold are body size and full
   contrast, not .small and not --muted. THE ONE THING THIS BLOCK MUST NEVER
   GROW IS A SMALLER FONT-SIZE ON THOSE TWO RULES. A footnote is precisely
   what the rule was written about.
   ========================================================================== */
.price-block {
  margin: 1.4rem 0;
  padding: 1.15rem 1.25rem 1.05rem;
  background: var(--bone);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brass-dk);
  border-radius: var(--r-lg);
}
.price-fee { margin: 0 0 .5rem; line-height: 1.1; }
.price-figure {
  font-family: var(--serif);
  font-size: clamp(2rem, 1.2rem + 2.6vw, 2.9rem);
  font-weight: 600;
  color: var(--brass-dk);
  letter-spacing: -.01em;
}
.price-per { font-size: 1rem; color: var(--muted); margin-left: .3rem; }
.price-hold,
.price-due {
  margin: 0 0 .45rem;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--body);
}
.price-conds { list-style: none; margin: .8rem 0 0; padding: .8rem 0 0; border-top: 1px solid var(--line); }
.price-conds li { display: flex; gap: .55rem; align-items: flex-start; margin-bottom: .4rem; font-size: .95rem; line-height: 1.5; color: var(--body); }
.price-conds li:last-child { margin-bottom: 0; }
.price-conds svg { flex: 0 0 auto; margin-top: .22em; color: var(--brass-dk); }

.section--ink .price-block { background: rgba(255, 255, 255, .05); border-color: rgba(255, 255, 255, .16); border-left-color: var(--brass); }
.section--ink .price-figure { color: var(--brass); }
.section--ink .price-per,
.section--ink .price-hold,
.section--ink .price-due,
.section--ink .price-conds li { color: var(--bone); }
.section--ink .price-conds { border-top-color: rgba(255, 255, 255, .16); }
.section--ink .price-conds svg { color: var(--brass); }

/* The rate on a size card. Same rule as above in miniature: the figure is the
   prominent thing and the first-month line sits beside it at .small, which is
   allowed because a CARD is not an advertised price on its own — every card
   links to a page carrying the full .price-block. */
.svc-card-price {
  margin: .6rem 0 0;
  font-family: var(--serif);
  font-size: 1.28rem;
  font-weight: 600;
  color: var(--brass-dk);
}
.svc-card-price .small { color: var(--muted); font-weight: 400; font-family: var(--sans); }

/* A sub-heading inside a section that already has a .section-head — used
   where a page argues one thing and then its opposite (what this building
   has, and what it does not). */
.section-head--tight { margin: 2.2rem 0 1rem; }
.section-head--tight h3 { font-family: var(--serif); font-size: 1.32rem; margin: 0; }
.section--ink .section-head--tight h3 { color: var(--bone); }

/* ==========================================================================
   THE DARK GROUND, PART THREE — .numbered AND .deflist

   The two notes above record this defect on `.svc-card` and then on
   `.info-card`, each time a generation after somebody predicted it, and each
   time the prediction named the mechanism exactly: a component that sets its
   own text colour for a light ground keeps setting it on a dark one, and
   `--muted` — which is 6.9:1 on paper — is 2.3:1 on ink.

   `.numbered p` and `.deflist p` both do it. `audit-live.mjs` found four
   elements at 2.28:1 on the LIVE home page, on the band explaining how a
   reservation works, which is the one place on this site a visitor is being
   asked to do something.

   And the prediction is worth restating rather than removing, because it has
   now come true three times: **every component in this stylesheet that sets
   `color: var(--muted)` on its body text will do this the first time
   somebody puts it inside `.section--ink`.** At the time of writing that is
   `.deflist p`, `.numbered p`, `.sum-note`, `.faq-body p`, `.team-role`,
   `.plan-blurb`, `.figcap figcaption` and `.svc-card-price .small`. They are
   all corrected below, whether or not this build happens to use them there
   yet — because the cheap moment to do it is now and the expensive moment is
   the next fork's live audit.
   ========================================================================== */
.section--ink .numbered p,
.section--ink .deflist p,
.section--ink .sum-note,
.section--ink .faq-body p,
.section--ink .team-role,
.section--ink .plan-blurb,
.section--ink .figcap figcaption,
.section--ink .svc-card-price .small { color: rgba(255, 255, 255, .78); }

.section--ink .numbered h3,
.section--ink .deflist h3,
.section--ink .sum-h { color: #fff; }

.section--ink .numbered > li::before {
  background: rgba(226, 185, 154, .16);
  border-color: rgba(226, 185, 154, .34);
  color: var(--seal);
}
.section--ink .deflist > li,
.section--ink .numbered > li { border-bottom-color: rgba(255, 255, 255, .14); }

/* `.svc-card` is a light card ON the dark band (see part one), so its own
   price line must stay on the LIGHT-ground token rather than inheriting the
   rule above. Written as a second, more specific rule rather than by
   narrowing the first, so that adding a component to the list above cannot
   silently reach inside a card. */
.section--ink .svc-card .svc-card-price { color: var(--brass-dk); }
.section--ink .svc-card .svc-card-price .small { color: var(--muted); }

/* ==========================================================================
   THE SIZE ESTIMATOR'S STEPPER ROWS.

   These are `est-*` rather than the wizard's `wiz-*` because they are not a
   wizard: there are no steps, no branching and no result to unlock — it is a
   list of quantities and a running total. The first version borrowed the
   forked build's `.wiz-q` and `.wiz-count`, and the marketing screenshot
   showed a ragged column of labels each with a minus, a squeezed digit and a
   plus pushed to a different place on every line. Nothing could see it: it
   does not overflow, the contrast is fine and nothing logs (§28.9).

   The row is a three-column grid so the steppers line up whatever the label
   length, and the cubic-feet figure is shown because it is the number the
   whole calculation is made of and hiding it would make the answer a guess.
   ========================================================================== */
.est-list { list-style: none; margin: 0 0 .4rem; padding: 0; }

.est-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: .5rem 1rem;
  padding: .62rem 0;
  border-bottom: 1px solid var(--line);
  margin: 0;
}
.est-row:last-child { border-bottom: 0; }

.est-name { color: var(--body); font-size: .97rem; line-height: 1.35; }
.est-cuft {
  font-size: .82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.est-step {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--paper);
}
.est-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--body);
  font-size: 1.1rem;
  line-height: 1;
  width: 2.35rem;
  height: 2.2rem;
  cursor: pointer;
  font-family: var(--sans);
}
.est-btn:hover { background: var(--bone-2); }
.est-btn:focus-visible { outline: 2px solid var(--seal-dk); outline-offset: -2px; }
.est-n {
  min-width: 2.1rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--body);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* A row with something in it is marked, so a long list reads at a glance as
   "these four are the ones I picked" rather than as forty identical rows. */
.est-row.is-on .est-name { color: var(--strong, var(--ink)); font-weight: 600; }
.est-row.is-on .est-step { border-color: var(--brass-dk); }
.est-row.is-on .est-n { color: var(--brass-dk); }

@media (max-width: 560px) {
  .est-row { grid-template-columns: 1fr auto; }
  .est-cuft { grid-column: 1; grid-row: 2; }
  .est-step { grid-column: 2; grid-row: 1 / span 2; }
}

/* The item half of the estimator is a longer list of smaller things, so its
   rows are tighter than the room half's. Declared because it is emitted —
   §19: `validate.mjs` fails a class the stylesheet never defines, which is
   how a build ships markup styled by nothing. */
.est-list--items .est-row { padding: .48rem 0; }
.est-list--items .est-name { font-size: .93rem; }

/* The estimator's closing line: the rate, and the link on to the size page.
   It replaced `.wiz-fallback`, which the stylesheet styles as a no-JavaScript
   FIELDSET and which this build used as a paragraph — a class that exists,
   over markup it was not written for (§19). */
.est-note {
  margin: 1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
  font-size: .95rem;
  color: var(--body);
}
.est-note .link-more { margin-left: .2rem; }

/* ==========================================================================
   THE PORTFOLIO — the components this build adds.

   A portfolio photograph is only ever drawn by site.cjs's pfFigure() or
   pfCard(), and both write two spans into the <figcaption>: the style
   (.pf-cap) and the demonstration notice (.pf-demo). THE NOTICE IS NEVER
   HIDDEN, NEVER display:none AT ANY WIDTH, AND NEVER SMALLER THAN THE
   CAPTION'S OWN SMALL TEXT — house rules §29.8 records a demonstration
   notice that reported display:flex and measured zero width on a phone.
   check-live.mjs measures this one's box on every portfolio page at 390px.

   The grid is CSS columns, so a 2:3 frame and a 3:2 frame sit together the
   way a photographer's own proofs do, without any cropping: the frames are
   shown at their own shape, because cropping someone's photograph to fit a
   grid is the first thing a photographer would notice.
   ========================================================================== */
.pf-grid { columns: 3 280px; column-gap: 1.35rem; }
.pf { break-inside: avoid; margin: 0 0 1.6rem; }
.pf img { display: block; width: 100%; height: auto; border-radius: var(--r); background: var(--bone-2); }
.pf--land img { aspect-ratio: 3 / 2; }
.pf--port img { aspect-ratio: 2 / 3; }
.pf figcaption { margin-top: .6rem; font-size: .9rem; line-height: 1.5; color: var(--body); }
.pf-cap { display: block; }
.pf-demo {
  display: block; margin-top: .25rem;
  font-size: .8rem; line-height: 1.45; letter-spacing: .01em; color: var(--muted);
}

.pf-card {
  margin: 0; background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow);
}
.pf-card img { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
.pf-card figcaption { padding: 1rem 1.15rem 1.2rem; }
.pf-card h3 { font-family: var(--serif); font-size: 1.2rem; font-weight: 600; margin: 0 0 .35rem; }
.pf-card h3 a { color: var(--ink); text-decoration: none; }
.pf-card h3 a:hover { color: var(--seal-dk); }
.pf-card .small { color: var(--body); }
.pf-card .pf-demo { margin-top: .5rem; }

/* A PORTFOLIO FRAME ON A DARK GROUND. §15, §18, §19 and §29.8 each record a
   component whose var(--muted) text failed on ink one build later than the
   last; this one is fixed before any page puts it there. */
.section--ink .pf figcaption,
.section--ink .pf-cap { color: rgba(255, 255, 255, .86); }
.section--ink .pf-demo { color: rgba(255, 255, 255, .72); }
.section--ink .pf img { background: var(--char-2); }

@media (max-width: 700px) {
  .pf-grid { columns: 1; }
}

/* ==========================================================================
   A GENERATED PERSON, LABELLED (New York General Business Law § 396-b —
   `_research/LAW.md` §13). site.cjs's img() wraps every picture that shows a
   person, outside the portfolio and the page headers, in .gen-wrap and puts
   the label ON the picture: a label under a card can be separated from its
   picture by a layout, a label on it cannot. The page-header version is a
   line in the heading (.gen-note), because the header photograph sits under
   a dark gradient and the heading's own text.
   ========================================================================== */
.gen-wrap { position: relative; display: block; }
.gen-wrap > img { display: block; width: 100%; height: auto; }
.gen-tag {
  position: absolute; left: .55rem; bottom: .55rem; right: .55rem; z-index: 2;
  width: fit-content; max-width: calc(100% - 1.1rem);
  padding: .28rem .55rem; border-radius: 6px;
  background: rgba(19, 42, 44, .82); color: #fff;
  font-size: .75rem; line-height: 1.35; font-weight: 500;
}
.pagehead .gen-note {
  display: flex; align-items: flex-start; gap: .4rem;
  margin: 1.1rem 0 0; max-width: 62ch;
  font-size: .8rem; line-height: 1.45; color: rgba(255, 255, 255, .78);
}
.pagehead .gen-note svg { flex: none; margin-top: .15rem; color: var(--seal); }

/* A "read more" link closing one part of a section is followed by the next
   part's heading; without this the heading sat directly under the link
   (home page, "How it works"). */
.section-foot + h3, .section-foot + h2 { margin-top: 2.4rem; }
/* The gallery grid under a gallery's own introduction: the prose's last line
   sat directly on the first row of frames. */
* + .pf-grid { margin-top: 2.2rem; }

/* ==========================================================================
   MASSAGE & ACUPUNCTURE (demo 47) — the components this fork adds.
   ========================================================================== */

/* THE PRICE AT EACH LENGTH: one row per length, the figure, the length said,
   and under it the table time and the tax AT BODY SIZE — never .small. The
   same rule as .price-block above: what a price buys, and what is added to
   it, is as prominent as the line the figure sits on. */
.price-lengths { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.price-lengths li { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: .4rem; row-gap: .1rem; }
.price-lengths .price-figure { font-size: clamp(1.7rem, 1.1rem + 1.8vw, 2.3rem); }
.price-sub { flex-basis: 100%; font-size: 1rem; line-height: 1.5; color: var(--body); }
.section--ink .price-sub { color: var(--bone); }

/* THE EVIDENCE: a heading per therapy and a list whose every item carries
   its source in the same line (the honesty scanner's source scope). */
.evidence { margin: 1.8rem 0 0; }
.evidence h3 { margin: 0 0 .6rem; }

/* THE CONTRAINDICATIONS TABLE: four columns, scrolled inside .table-wrap on
   a phone rather than squeezed into unreadable ribbons. */
.contra-table th[scope="row"] { min-width: 11rem; }
.contra-table td { min-width: 9.5rem; }

/* THE PACKAGE NOTICE: the rule's own capitals, set to be read. */
.notice-box { margin: 1.6rem 0 0; padding: 1.1rem 1.25rem; background: var(--bone); border: 1px solid var(--line-2); border-radius: var(--r); }
.notice-box p { margin: 0 0 .7rem; font-size: .95rem; line-height: 1.55; color: var(--body); }
.notice-box p:last-child { margin-bottom: 0; }
.notice-caps { font-size: .9rem; letter-spacing: .02em; line-height: 1.65; overflow-wrap: anywhere; }

/* The rooms table's row headers are "Room 1" … "Room 4": the widest
   column beside them squeezed each onto two lines ("Room" / "1") in the
   rooms-page shot, 2026-09-25. */
.rate-table--rooms th[scope="row"] { white-space: nowrap; }
