/* ==========================================================================
   Woodside Translation, LLC — design system.

   Hand-written, self-contained. No external font requests: validate.mjs fails
   the build on any @import or src:url pointing off-origin, so the display face
   is a system serif stack and the body face a system sans stack. Both stacks
   have Cyrillic coverage, because DEMO-HOUSE-RULES.md section 7 says a display
   font without it is one of the expensive traps to bake in on the English side
   — and on a TRANSLATION office's site it is not a trap, it is the product:
   this business sets Cyrillic, Greek and CJK names in its own running copy.

   THIS HEADER IS GENERATED BY _tools/swap-palette.mjs. Do not hand-edit it.
   It arrived at this build describing a funeral home's slate, moss and stone
   while the tokens directly beneath it were an optometrist's iris and amber —
   the fourth generation of a comment that was wrong about the twenty lines
   under its own last line. Nothing checks a comment, so this one is written
   by the same script that writes the tokens and cannot drift from them.

   Palette: slate, seal and brass.

   SLATE is the ground. A deep, slightly warm blue-grey with no black in it —
   the colour of a good desk in a room with one lamp on. It is warmer than the
   parent build's indigo on purpose: every photograph here is oak, paper,
   lamplight and daylight through a window, and a cold ground fought them.

   SEAL is the first accent and it is THE OFFICIAL HALF: certified
   translation, the certificate of accuracy, notarisation, apostille and
   legalisation, the document catalogue — everything that has to be ACCEPTED
   by an agency, a court or a consulate. It is the blue of security paper and
   of the ink in a good pen, which is the whole promise of that half of the
   business: this will be taken seriously by the person you hand it to.

   BRASS is the second accent and it is THE COMMERCIAL HALF, AND EVERY PRICE:
   professional and technical translation, interpreting, the rate card and the
   estimator. It is the desk lamp, the embosser and the wax. Money is brass
   everywhere on this site, without exception — a price in seal would read as
   an official fee rather than a quotation, which is the one thing it must not.

   The two accents MEAN different things rather than alternating. An accent
   that means nothing is decoration, and decoration is the first thing a
   visitor stops seeing.

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

   RED IS RATIONED. --signal is form validation and the one immovable
   deadline. The ten boundary pages — what this office will not certify, will
   not swear to, will not notarise — wear --warn, which on this build is a
   NEUTRAL ground with an ink rule, not pink. Ten red pages would mean no red
   pages. See .advice-note--warn far below for the whole argument.

   AND THE HARDCODED rgba() VALUES ARE PART OF THE PALETTE. Changing the
   tokens is not the swap. _tools/swap-palette.mjs does both halves — the
   tokens, the renames, the file header, the literal hexes and the literal
   rgba()s, including two the eye care swap MISSED because they were written
   in decimal rather than hex and were therefore two generations stale (the
   funeral home's moss, still sitting behind .section--ink .promise-ic, and
   its paper, still behind the sticky .menu-jump bar) — and then GREPS FOR THE
   OLD VALUES AFTERWARDS rather than trusting the page to look right. Run it
   with --check any time. DEMO-HOUSE-RULES.md section 12.

   THE HERO SCRIM IS TUNED TO THIS BUILD'S PHOTOGRAPH AND NOT THE PARENT'S.
   The parent's hero was a dark indigo wall; ours is a warm oak desk by a
   window, which is far lighter, so the scrim was made STRONGER rather than
   weaker. See .hero-media::after for the numbers and the reasoning.

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

:root {
  --ink:        #1e2733;   /* deep, slightly warm slate — the hero ground, the footer and every dark band. Warmer than the parent's indigo because the imagery is oak and lamplight, not a clinic wall. */
  --char:       #2a3542;
  --char-2:     #3a4657;
  --char-3:     #6a7482;
  --seal:       #9db4d4;   /* THE OFFICIAL HALF: certified translation, the certificate of accuracy, notarisation, apostille, the document catalogue. Security-paper blue. Dark grounds only. */
  --seal-dk:    #2f4f7a;   /* its light-ground twin, for links, icons and rules on --bone (8.3:1 on --paper) */
  --seal-lt:    #eaeff6;
  --brass:      #d8b071;   /* THE COMMERCIAL HALF AND EVERY PRICE: professional and technical work, interpreting, the rate card, the estimator. The desk lamp and the embosser. Dark grounds only. */
  --brass-dk:   #8a6420;   /* its light-ground twin (5.4:1 on --paper) */
  --brass-lt:   #faf3e6;
  --signal:     #a8443a;   /* muted brick — form validation and the one immovable deadline. Ten boundary pages do NOT get this; see the header. */
  --signal-lt:  #f9ece9;
  --bone:       #f7f7f5;   /* neutral-warm, following --ink off the parent's blue cast */
  --bone-2:     #eeeeeb;
  --paper:      #ffffff;
  --line:       #e3e2de;
  --line-2:     #c8c6c0;
  --body:       #262f3b;   /* contrast measured by audit.mjs, not asserted here */
  --muted:      #555d68;
  --muted-lt:   #626a76;   /* small labels only */
  --warm-line:  #cfdaea;   /* border of the --warm advice note, which is the SEAL one here */
  --warm-ink:   #23405f;   /* 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(30, 39, 51, .05), 0 8px 24px -12px rgba(30, 39, 51, .20);
  --shadow-lg: 0 2px 4px rgba(30, 39, 51, .06), 0 24px 48px -20px rgba(30, 39, 51, .30);
}

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

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

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

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

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

a { color: var(--char-2); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--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; } }

.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); }

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

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

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

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

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

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

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

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

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

     `visibility: hidden` does not remove it from the document's scroll extent
     — the same property §15 records for the closed mobile drawer, here on the
     desktop dropdown. Anchoring the right edge to the nav's own right edge
     makes the panel grow leftward into the page, where there is always room,
     and needs no clipping ancestor that would cut the panel off when open. */
  position: absolute; top: calc(100% + 10px); right: 0; transform: translateY(-6px);
  width: min(860px, calc(100vw - 3rem));
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.6rem 1.6rem 0;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s, transform .18s, visibility .18s;
}
.nav-drop[data-open="true"] .nav-panel {
  opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0);
}
.nav-panel-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem 2rem; }
.nav-col-h {
  font-size: .715rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--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. */
@media (max-width: 1023px) { .nav { display: none; } .burger { display: inline-flex; } }
@media (max-width: 780px) {
  .header-cta .btn--ghost, .header-cta .btn--primary { display: none; }
  .site-header .wrap { min-height: 66px; }
  .brand-sub { display: none; }
  .brand-name { font-size: 1.02rem; line-height: 1.14; }
}
@media (max-width: 430px) { .brand-name { font-size: .93rem; } }

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

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

   Clipping the fixed container costs nothing: when the drawer is open the
   panel is at translateX(0) and entirely inside the box. */
.drawer { position: fixed; inset: 0; z-index: 100; visibility: hidden; overflow: hidden; }
.drawer[data-open="true"] { visibility: visible; }
.drawer-veil {
  position: absolute; inset: 0; background: rgba(30, 39, 51,.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(30, 39, 51,.88) 0%, rgba(30, 39, 51,.78) 44%, rgba(30, 39, 51,.42) 76%, rgba(30, 39, 51,.12) 100%),
    linear-gradient(to top, rgba(30, 39, 51,.74), transparent 45%);
}
.hero-inner {
  position: relative; z-index: 2;
  padding: clamp(4rem, 9vw, 7.5rem) 0 clamp(3rem, 6vw, 5rem);
  max-width: 660px;
}
.hero h1 { color: #fff; margin-bottom: .5em; }
.hero .eyebrow { color: var(--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;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px; background: var(--line-2);
  border-top: 1px solid var(--line-2);
}
.hero-stats div { --strong: #fff; background: var(--ink); padding: 1.4rem 1.35rem; }
.hero .hero-stats { background: rgba(255,255,255,.14); border-top-color: rgba(255,255,255,.14); }
.hero .hero-stats div { background: rgba(30, 39, 51,.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-template-columns: repeat(2, 1fr); } }

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

.pagehead {
  --strong: #fff; position: relative; background: var(--ink); color: #fff; overflow: hidden;
  padding: clamp(2.6rem, 5.5vw, 4.4rem) 0 clamp(2.2rem, 4.5vw, 3.4rem);
}
.pagehead-media { position: absolute; inset: 0; }
.pagehead-media img { width: 100%; height: 100%; object-fit: cover; }
.pagehead-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(95deg, rgba(30, 39, 51,.95) 0%, rgba(30, 39, 51,.88) 42%, rgba(30, 39, 51,.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(157, 180, 212,.16); border-color: rgba(157, 180, 212,.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; }
.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(47, 79, 122,.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 #cfdaea; 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); }
.badge svg { flex: none; }
.badge--veg { border-color: #9db4d4; color: #2f4f7a; background: #eaeff6; }
a.badge--veg:hover { border-color: #2f4f7a; color: #22395c; }
.badge--heat { border-color: #edd2ce; color: var(--signal); background: var(--signal-lt); }
a.badge--heat:hover { border-color: var(--signal); color: #7a2d24; }

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

.figcap { margin: 0; }
.figcap img { border-radius: var(--r-lg); display: block; width: 100%; height: auto; }
.figcap figcaption {
  margin-top: .85rem; padding-left: .9rem;
  border-left: 2px solid var(--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: #7a2d24; margin-bottom: 1.3rem;
}
.bk-spin { text-align: center; padding: 2.4rem 0; color: var(--muted); font-size: .93rem; }
.bk-spin::before {
  content: ""; display: block; width: 1.6rem; height: 1.6rem; margin: 0 auto .9rem;
  border: 2px solid var(--line-2); border-top-color: var(--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: #7a2d24;
}
.advice-note--warn svg { color: var(--signal); }
.advice-note--warn strong { color: #5c1f18; }

.small { font-size: .86rem; line-height: 1.7; color: var(--muted); }
.small a { color: var(--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: #edd2ce; color: #7a2d24; }
.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: #e6d2a8; color: #5c4310; }
.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: #4a3612;
}
.advice-note--cool svg { color: var(--brass-dk); }
.advice-note--cool strong { color: #38290c; }

/* The spec strip on a service group card. Same shape as .room-spec, which it
   replaces; kept as its own name because a nail service's facts are a count,
   a length and whether it needs a chair, not a bed and a view. */
.svc-spec {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .35rem .8rem; list-style: none; margin: 0 0 .8rem; padding: 0;
  font-size: .84rem; color: var(--muted);
}
.svc-spec li { display: flex; align-items: flex-start; gap: .4rem; margin: 0; }
.svc-spec svg { color: var(--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. Only one exists: the jade "chair" marker on every
   service that needs one of the three pedicure stations as well as a
   technician, which is the constraint this build's booking engine is about. */
.pill {
  display: inline-block; vertical-align: baseline;
  padding: .06rem .42rem; border-radius: 999px;
  font-family: var(--sans); font-size: .68rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  border: 1px solid var(--line-2); color: var(--muted);
}
.pill--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; }
.sum-rows li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: .5rem 1rem; align-items: baseline;
  padding: .34rem 0; border-bottom: 1px dotted var(--line);
  margin: 0; color: var(--muted);
}
.sum-rows li span:first-child { min-width: 0; overflow-wrap: anywhere; }
.sum-rows .num { font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; min-width: 5.5rem; }
.sum-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(157, 180, 212, .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: #7a2d24;
}
.advice-note--urgent svg { color: var(--signal); }
.advice-note--urgent strong { color: #5c1f18; }

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