/* ==========================================================================
   THE HOUSE. Lou Lou's + Foster's, session 17.

   CONCEPT.md 4b. The climb was locked in session 1 and never actually built: it
   became an index on the edge beside a stack of framed prints. This sheet makes
   the building physical and lets the clock decide what the building is doing.

   It loads AFTER site.css and only ever adds. The menu card Campbell approved,
   the invitation forms, the Desk and the booking engine are untouched by it.

   Everything here composes on site.css tokens. No literal hex that already has
   a name upstairs, because a token duplicated is a token that will drift (the
   ink-ramp lesson, session 16).
   ========================================================================== */

:root {
  /* The house's own light, mixed from tokens that already exist. */
  --day-high:   color-mix(in srgb, var(--rattan-lit) 34%, var(--paper));
  --day-floor:  var(--paper-deep);
  --dusk-lamp:  color-mix(in srgb, var(--rattan-lit) 26%, var(--dusk));
  --shut-cool:  color-mix(in srgb, var(--dusk) 88%, var(--sage-deep));

  /* One easing family for the house. Slow in, long settle: peaceful jazz on a
     sunny spring day, twice his words. Nothing here snaps. */
  --house-ease: cubic-bezier(.22, .68, .24, 1);
  --house-slow: cubic-bezier(.32, .04, .18, 1);
}

/* ==========================================================================
   1. THE DOOR. The first viewport is the building, and the building is honest
      about what it is doing right now.

      The photograph never changes with state and that is deliberate. A hungry
      stranger at 3pm still wants to see the food; showing them an empty bar
      because the kitchen happens to be dark is a worse answer, not a more
      truthful one. Food carries the frame. The LIGHT, the LINE and the live
      elevation index on the edge carry the state.
   ========================================================================== */
.door {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
  align-items: center;
  gap: clamp(var(--space-6), 5vw, var(--space-9));
  padding: clamp(var(--space-9), 12vh, var(--space-11)) var(--edge) var(--space-9);
  overflow: clip;
  isolation: isolate;
}

/* The room light. One field, three moods, driven by the attribute the blocking
   head script set before anything painted. */
.door__light {
  position: absolute;
  inset: 0;
  z-index: -1;
  transition: opacity 1.4s var(--house-ease);
}
.door__light::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 90% at 18% 6%, var(--day-high), transparent 62%),
    linear-gradient(168deg, var(--paper) 0 38%, var(--day-floor));
}
[data-house="bar"] .door__light::before {
  background:
    radial-gradient(96% 74% at 74% 16%, var(--dusk-lamp), transparent 66%),
    linear-gradient(168deg, var(--dusk) 0 44%, var(--dusk-deep));
}
[data-house="shut"] .door__light::before {
  background:
    radial-gradient(110% 84% at 26% 10%, color-mix(in srgb, var(--sage-deep) 22%, var(--shut-cool)), transparent 64%),
    linear-gradient(168deg, var(--shut-cool) 0 46%, var(--dusk-deep));
}

/* Upstairs and shut are dark strata, and the way NOT to do this is to restate
   --ink on the container.

   That was the first thing tried here and it blanked the primary button on
   sight. `.btn` fills itself with var(--ink) and sets its label to var(--paper),
   so an --ink restated to paper makes the button paper on paper: a live, empty,
   invisible "Reserve a table" in the first viewport. The token is a TEXT colour
   in most places and a FILL in a few, and a blanket restatement cannot tell the
   difference.

   site.css already had the answer and `.on-dusk` has used it since session 5:
   name the children. So this names the children, and inverts the one component
   that legitimately paints WITH the ink rather than in it. */
[data-house="bar"] .door,
[data-house="shut"] .door { color: var(--paper); }

[data-house="bar"] .door h1,
[data-house="shut"] /* The .door__floors eyebrow lived here: a small letterspaced label above the
   H1, lifted from Amrit Palace. It is also exactly the eyebrow-above-the-H1 on
   the kill-list, and the wall blocked it. Stealing a device from a reference
   does not exempt the device from the law. The floors read live on the
   elevation index at the edge instead, and the headline names the open floor. */
.door h1 { color: var(--paper); }

[data-house="bar"] .door__lede,
[data-house="shut"] .door__lede { color: color-mix(in srgb, var(--paper) 84%, var(--dusk)); }


[data-house="bar"] .serve figcaption,
[data-house="shut"] .serve figcaption { color: color-mix(in srgb, var(--paper) 62%, var(--dusk)); }

/* the filled button inverts: paper ground, ink label */
[data-house="bar"] .door .btn,
[data-house="shut"] .door .btn {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--ink);
}
[data-house="bar"] .door .btn--ghost,
[data-house="shut"] .door .btn--ghost {
  background: transparent;
  border-color: color-mix(in srgb, var(--paper) 52%, transparent);
  color: var(--paper);
}
[data-house="bar"] .door .btn--ghost:hover,
[data-house="shut"] .door .btn--ghost:hover { background: var(--paper); color: var(--ink); }

.door__say { position: relative; z-index: 3; max-width: 34rem; }

/* Amrit's device, stolen and made to mean something: two small wide-tracked
   facts flanking the line at its optical centre. Theirs are decorative
   (SERVING CENTRAL FLORIDA / ESTABLISHED 1996). Ours are the two floors, and
   the one that is open is lit. */

.door h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3.1rem, 8.4vw, 6.2rem);
  line-height: 1;
  letter-spacing: .012em;
  margin: 0 0 var(--space-5);
  max-width: 11ch;
  text-wrap: balance;
}
.door__lede {
  font-size: clamp(1rem, 1.15vw, 1.14rem);
  line-height: 1.66;
  color: var(--ink-soft);
  max-width: 38ch;
  margin: 0 0 var(--space-6);
}
.door__go { display: flex; flex-wrap: wrap; gap: var(--space-4); }

/* ==========================================================================
   2. THE ELEVATION INDEX, MADE LIVE.

      site.css already draws this on the edge of the home page: Foster's above
      Lou Lou's, spatially literal, with a car that rides between them as you
      scroll. It reported ALTITUDE. It now also reports which floor is OPEN, so
      the building's two different sets of hours are readable on the edge of
      every screen.

      This replaced a second, drawn elevation that this session built first. Two
      objects saying the same thing is the redundancy law, and the one already
      on the page was the better object.
   ========================================================================== */
.elev__floor { transition: color .9s var(--house-ease), opacity .9s var(--house-ease); }
.elev__floor[data-lit="true"] { opacity: 1; }
.elev__floor span { position: relative; }

/* OPEN RIGHT NOW IS NOT A DOT.

   It was a small red circle under the floor's name, and the car riding the
   shaft was another small red circle, so the index could show three identical
   red marks meaning two different things at once. Campbell asked for "a better
   colour or a better way to represent that", and the house law is more
   specific than that: a status dot is the median's universal state vocabulary
   and it has already been killed twice on other builds. Reaching for it again
   is the tell, however well drawn it is.

   So the state is drawn from THIS building instead. A floor you can walk into
   has its lights on: the name burns at full strength with a warm lamp behind
   it, in the rattan of the charger plates and the upstairs lamplight. The
   floor that is shut stays cool and unlit. Nobody needs a legend for a lit
   window, and the red goes back to being the seal on a full stop. */
.elev__floor[data-lit="true"] span::before {
  content: "";
  position: absolute;
  inset: -.7rem -.85rem;
  border-radius: 999px;
  z-index: -1;
  pointer-events: none;
  transition: opacity .9s var(--house-ease);
}
/* A FLOOR THAT IS OPEN IS PRINTED. A FLOOR THAT IS SHUT IS GHOSTED.
   This is the whole state, and it does not change as you scroll: the car on the
   shaft is what reports where you are. */
.elev__floor[data-lit="true"] { color: var(--ink); }
.elev[data-ground="dark"] .elev__floor[data-lit="true"] { color: var(--paper); }

/* AND THE LAMP ONLY BURNS AFTER DARK, which is simply true: you cannot see a
   lamp lit in daylight. Measured rather than assumed, and it is why this is not
   a blanket rule: on bone paper the warm halo was invisible, so on the light
   strata the ink ramp above carries the state alone, exactly as a printed sign
   does in the afternoon. On the dusk strata the light itself does the telling. */
.elev[data-ground="dark"] .elev__floor[data-lit="true"] span::before {
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--rattan-lit) 58%, transparent) 0%,
    color-mix(in srgb, var(--rattan-lit) 20%, transparent) 52%,
    transparent 100%);
}

/* ==========================================================================
   3. THE SERVICE FIELD. Codex's damped spring, harvested and repaired.

      What was worth taking: one rAF loop for the whole field, per plate mass so
      the dishes do not move as one sheet, and not one tween allocated per
      pointer event. What was wrong is fixed in house.js, not here: the pointer
      was measured against the stage's width but not its offset, and the loop
      never parked once the spring had settled.
   ========================================================================== */
.door__serve {
  position: relative;
  z-index: 2;
  height: min(74svh, 46rem);
  min-height: 22rem;
  isolation: isolate;
}
.serve { position: absolute; margin: 0; will-change: transform; }
.serve__rim {
  display: block;
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  padding: clamp(.3rem, .55vw, .6rem);
  background: radial-gradient(circle at 40% 34%, #fffdf6, #e6dfd0 64%, #b0a184 88%, #f6f0e5 94%);
  box-shadow: 0 1.6rem 4.4rem color-mix(in srgb, var(--dusk-deep) 34%, transparent),
              0 0 0 1px color-mix(in srgb, #fff 52%, transparent);
}
.serve__rim img { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
/* Only the dish they are famous for is named. All three captions collided at
   1440: two sat on their neighbours' plates and the third was cut by the
   viewport. A caption that lands on another photograph is not a caption. */
.serve figcaption { display: none; }
/* The caption used to run the full width of the lead figure (397px), and the
   two smaller dishes crossed it by 138px and 121px. Measured, there is a clean
   gap between those two plates, centred almost exactly under the lead one, so
   the caption is cut to fit that gap and wraps to two lines inside it. */
.serve--lead figcaption {
  display: block;
  width: 6rem;
  margin-inline: auto;
  margin-top: var(--space-3);
  text-align: center;
  font-family: var(--display-sc), var(--display);
  font-size: var(--ms-dn2);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
/* THE TILT BELONGS TO THE PLATE, NOT TO THE PLATE AND ITS CAPTION (2026-08-02).

   The rotation used to sit on the whole <figure>, and the caption hangs below
   the plate, so rotating the figure swung the caption sideways about the
   figure's centre: measured 18px right of its own photograph's axis at every
   width. The law's own worked example is "a caption centres under its
   photograph", so this was the defect it names.

   A rotated pin on the story board is NOT the same fault and is left alone:
   that card is a rectangle, so the eye sees the tilt and reads the card and its
   caption as one tilted object. A circle rotated looks identical to a circle,
   so here the tilt is invisible and only its side effect shows, which reads as
   a caption somebody nudged.

   The tilt moves onto the rim. The rim is a circle with the photograph clipped
   inside it, so rotating the rim tilts the PICTURE exactly as before and the
   circle is unchanged, while the caption below now hangs square. The parallax
   translate stays on the figure so the drift is untouched. */
.serve--lead  { width: min(31vw, 27rem); left: 12%; top: 4%;   transform: translate(var(--sx,0), var(--sy,0)); }
.serve--side  { width: min(16vw, 13rem); right: 2%; bottom: 8%; transform: translate(var(--sx,0), var(--sy,0)); }
.serve--small { width: min(15vw, 12rem); left: 0%; bottom: 2%; transform: translate(var(--sx,0), var(--sy,0)); }
.serve--lead  .serve__rim { transform: rotate(-4.5deg); }
.serve--side  .serve__rim { transform: rotate(8deg); }
.serve--small .serve__rim { transform: rotate(-9deg); }

/* ==========================================================================
   4. THE CLIMB. The signature.

      Their own exterior staircase, with their own sign mounted at the landing,
      is the object between the two businesses. The page scrolls THROUGH it:
      the stair travels, the light goes from the bistro's daylight to Foster's
      dusk, and the words beside it change floor.

      The photograph is 1120x1991 in a column that renders around 380 to 460
      CSS px, so it sits at or under native even on a retina screen. That is the
      whole answer to a 720x480 still stretched full-screen: a staircase is
      narrow and tall, so the asset and the idea want the same shape.
   ========================================================================== */
.stairwell {
  position: relative;
  /* The ground does the separating. Daylight down to 42%, dusk from 56%, so the
     two floors literally cannot share a light level: downstairs sits in the
     paper band and upstairs sits in the dusk band. The first take put them side
     by side at one height and Foster's copy came out paper on paper, which is
     the same light-surface-in-a-dark-stratum fault this build has produced in
     five separate sessions. Composition solved it here, not a colour override. */
  background: linear-gradient(180deg,
    var(--dusk-deep) 0 6%,
    var(--dusk) 42%,
    var(--paper) 50%,
    var(--paper) 100%);
  overflow: clip;
}
.stairwell__in {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(18rem, 25vw, 23rem) minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: clamp(var(--space-5), 4vw, var(--space-8));
  row-gap: var(--space-10);
  padding: var(--space-9) var(--edge) var(--space-7);
}

/* the passage: a window cut in the page that the flight travels behind.
   spans both rows, so the stair is the one continuous thing on the screen and
   the two floors hang off it at their own altitudes. */
.stairwell__well {
  position: relative;
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: stretch;
  min-height: min(70svh, 40rem);
  overflow: hidden;
  background: var(--dusk-deep);
  box-shadow: 0 2.4rem 5.5rem color-mix(in srgb, var(--dusk-deep) 46%, transparent);
}
.stairwell__well img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 128%;
  object-fit: cover;
  object-position: 50% 50%;
  will-change: transform;
  transform: translate3d(0, var(--travel, -11%), 0);
}
/* the stairwell warms toward the landing */
.stairwell__well::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--rattan) 30%, transparent) 0%,
    transparent 40%,
    transparent 64%,
    color-mix(in srgb, var(--dusk-deep) 52%, transparent) 100%);
  mix-blend-mode: multiply;
  opacity: var(--warm, .5);
}

.stairwell__floor { position: relative; z-index: 2; max-width: 28rem; }
.stairwell__floor h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.6rem, 5.2vw, 4.6rem);
  line-height: 1;
  margin: var(--space-2) 0 var(--space-4);
}
.stairwell__floor p { line-height: 1.72; color: var(--ink-soft); }
.stairwell__floor .fine { margin-top: var(--space-4); }

/* downstairs: row 1, in the daylight, leaning toward the stair */
.stairwell__floor--down {
  grid-column: 1;
  grid-row: 2;
  justify-self: end;
  align-self: end;
  text-align: right;
}
/* upstairs: row 2, in the dusk, leaning away. it restates its own ramp at the
   element that paints, and it is genuinely on a dark ground here. */
.stairwell__floor--up {
  grid-column: 3;
  grid-row: 1;
  justify-self: start;
  align-self: start;
  color: var(--paper);
}
.stairwell__floor--up h2 { color: var(--paper); }
.stairwell__floor--up p { color: color-mix(in srgb, var(--paper) 82%, var(--dusk)); }
.stairwell__floor--up .engraved { color: color-mix(in srgb, var(--paper) 78%, var(--dusk)); }
.stairwell__floor--up .link { color: var(--paper); }

/* which tread you are on. no numbers: it is a building, not a scrollbar. */
.stairwell__tread {
  grid-column: 1 / -1;
  grid-row: 3;
  justify-self: center;
  margin: var(--space-4) 0 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.stairwell__tread i { display: block; width: 2.4rem; height: 1px; background: currentColor; opacity: .5; }

/* ==========================================================================
   5. PHONE. Composed, never collapsed.

      The fork's three broken acts were all the same fault: decorative plates
      left absolutely positioned while the canvas shrank, so they landed on the
      words. Nothing here stays absolute at phone width unless it has room that
      belongs to it alone.
   ========================================================================== */
@media (max-width: 62rem) {
  .door {
    grid-template-columns: 1fr;
    align-content: start;
    gap: var(--space-6);
    padding-top: clamp(var(--space-9), 14vh, var(--space-10));
    padding-bottom: var(--space-7);
  }
  /* THE DOOR JOINS THE SPINE (2026-08-01).

     The first viewport was the loudest example of the alignment Campbell
     called chaotic: headline, lede and both buttons hard LEFT, and then a
     plate collage centred underneath them, so the eye was handed two different
     axes in one screen. The page's own phone law is a centred spine with
     left-aligned reading matter, and the hero was the one act ignoring it.
     The lede is a deck, not reading matter, so it comes to the centre too. */
  .door__say { text-align: center; }
  .door h1 { font-size: clamp(3rem, 13.5vw, 4.6rem); max-width: 9ch; margin-inline: auto; }
  .door__lede { max-width: 32ch; margin-inline: auto; }
  .door__go { justify-content: center; flex-direction: column; align-items: center; }
  /* AND THE TWO BUTTONS MATCH. They were shrink-wrapped to their own labels, so
     "RESERVE A TABLE" and "READ THE MENU" stacked into a ragged right edge that
     read as two unrelated controls rather than a pair. */
  .door__go .btn { width: min(17rem, 82%); justify-content: center; }

  /* The field gets its own band under the words. The lead dish is centred and
     the two small ones tuck under it, INSIDE the frame.

     They used to hang off both edges (right: -4vw, left: -5vw) and at 390px two
     plates sliced by two opposite screen edges do not read as a deliberate
     crop, they read as a broken layout, which is exactly what Campbell saw.
     Overlapping the lead plate's lower corners instead makes the three read as
     one group: plates going out together, which is the act's whole idea. */
  /* THE STAGE HAS TO HOLD THREE PLATES AND A CAPTION.

     It did not. The lead plate hangs from the top and the two small plates are
     anchored to the stage's BOTTOM, so the caption, which sits in flow under
     the lead plate, landed in the same band the small plates occupy: measured
     at 430px the caption's box ran straight through the left plate, which is
     the "photos sitting on the text" Campbell photographed twice.

     Fitting the caption into the gap BETWEEN the plates was the old answer and
     it is the wrong one: that gap measures 70 to 89px across phone widths, so
     the caption would be narrower than the 6rem that caused the cramped
     two-line wrap in the first place. The stage gets the height instead, which
     costs nothing and lets all four things have their own room. */
  /* The stage is sized off the PLATES, not off the viewport height. The lead
     plate is 62vw, so it grows with the screen while an svh-based stage does
     not: at 430px that left the caption clearing the lower plates by 5px, and
     at 402 by 31. Tying the height to the same vw the plates use keeps the
     clearance constant at every phone width instead of shrinking as the phone
     gets wider, which is the direction the fault was travelling. */
  .door__serve { height: calc(62vw + 16rem); min-height: 20rem; margin-top: var(--space-4); }
  .serve--lead  { width: 62vw; left: 19vw; top: 0; }
  .serve--side  { width: 32vw; right: 4vw; bottom: 8%; }
  .serve--small { width: 30vw; left: 3vw; bottom: 12%; }

  /* THE CAPTION IS NOT A DESKTOP CAPTION. Its 6rem width is a DESKTOP
     constraint: up there the two smaller plates flank the lead one and the
     caption is cut to fit the clean gap between them (session 17, measured).
     Down here nothing flanks it, so that 6rem became a 97px column under a
     287px plate, wrapping "Chesapeake crab cakes" into two short lines jammed
     2px under the photograph. Read at phone size that is the picture sitting
     on its own caption, which is exactly what Campbell reported. The caption
     takes the plate's width here, and it gets air. */
  .serve--lead figcaption {
    width: 100%;
    margin-top: var(--space-4);
  }

  /* ONE COLUMN, FOUR ROWS, IN THE ORDER YOU WALK IT: downstairs, the stair,
     upstairs, the tread you are on.

     The desktop grid puts the flight in column 2 spanning both rows and hangs a
     floor off each side. Carrying that into one column without re-declaring the
     ROWS left both floors sharing cells with the staircase, so at 390 the copy
     printed straight onto the photograph. Auto-placement does not rescue a
     span, and every grid in this project names its rows for exactly this reason
     (sessions 4 and 5 lost an act each to the same thing). */
  .stairwell__in {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto;
    row-gap: var(--space-7);
    padding: var(--space-9) var(--edge);
    min-height: 0;
  }
  .stairwell__floor--up {
    grid-column: 1; grid-row: 1;
    justify-self: stretch; align-self: start;
    text-align: left; max-width: none;
  }
  .stairwell__well {
    grid-column: 1; grid-row: 2;
    width: min(64vw, 20rem);
    justify-self: center;
    align-self: center;
    min-height: 0;
    aspect-ratio: 7 / 12;
  }
  .stairwell__floor--down {
    grid-column: 1; grid-row: 3;
    justify-self: stretch; align-self: start;
    text-align: left; max-width: none;
  }
  .stairwell__tread { grid-column: 1; grid-row: 4; justify-self: center; margin-top: 0; }
}

/* ==========================================================================
   6. REDUCED MOTION. Complete and still. Nothing is hidden, nothing waits.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .stairwell__well img { transform: none; height: 100%; }
  .serve { transform: none !important; }
  .door__light, .elev__floor { transition: none; }
}

/* ==========================================================================
   7. THE RAIL OVER THE DOOR.

   Harvested from the Codex fork, which was right about this: a bone masthead
   sitting on top of a dusk hero draws a hard horizontal seam across the first
   viewport and cuts the building in half. On the home page the rail rides over
   the door instead, and only takes its own ground once the reader has left it.

   Home only. Every other route opens on paper, where the rail is correct as it
   is and has been since session 1.
   ========================================================================== */
body[data-path="/"] .rail {
  position: absolute;
  /* AN ABSOLUTE BOX SHRINK-WRAPS ITS CONTENT UNLESS IT IS TOLD ITS EDGES.
     Without these two lines the home masthead was only as wide as the mark
     plus the burger: 247px on a phone and 900px on desktop, inside a viewport
     of 390 and 1440. So the burger sat at the CENTRE of the phone screen and
     the desktop nav stopped 587px short of the right edge, on the one page
     that matters most, at every width. Every other route was correct because
     every other route leaves the rail sticky, which is a block box and fills
     its container. Campbell caught it on his phone: "the header is wrong in
     the main page on mobile." */
  left: 0;
  right: 0;
  background: transparent;
  border-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
[data-house="bar"] body[data-path="/"] .rail,
[data-house="shut"] body[data-path="/"] .rail { color: var(--paper); }
[data-house="bar"] body[data-path="/"] .rail__mark,
[data-house="shut"] body[data-path="/"] .rail__mark { filter: brightness(0) invert(1); }
[data-house="bar"] body[data-path="/"] .rail__nav a,
[data-house="shut"] body[data-path="/"] .rail__nav a { color: var(--paper); }
[data-house="bar"] body[data-path="/"] .rail__book,
[data-house="shut"] body[data-path="/"] .rail__book {
  border-color: color-mix(in srgb, var(--paper) 54%, transparent);
  color: var(--paper);
}
[data-house="bar"] body[data-path="/"] .rail__burger span,
[data-house="shut"] body[data-path="/"] .rail__burger span { background: var(--paper); }

/* ==========================================================================
   8. SERVICES: FOUR JOBS, FOUR ARCHITECTURES.

   The page used to run one skeleton four times (copy column, picture beside
   it, ruled list, centred pill) and alternate only which side the picture
   took. Alternating the side of a repeated skeleton is a stricter template
   than the zig-zag it replaced. Each service now gets the shape its own job
   asks for, and no two share bones.
   ========================================================================== */

.svc { position: relative; }
.svc h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.2rem, 4.2vw, 3.5rem);
  line-height: 1;
  margin: var(--space-2) 0 var(--space-4);
}
.svc .sub { font-size: var(--ms-1); line-height: 1.5; margin-bottom: var(--space-4); }
.svc p { line-height: 1.72; }

/* --- 1. DOWNSTAIRS: a broken plane. The dining room takes the right eight
       columns and the copy panel breaks over its left edge. Both sit in ONE
       row so the act fills its width.

       The first take made this a full-bleed band with the panel hanging BELOW
       it, which left the panel dangling 760px into empty cream with the whole
       right half of the page dead. Campbell, on sight: "the alignment is all
       over the place... placed all sloppily." He was right. A panel that
       "breaks over a corner" has to actually overlap the picture. --- */
.svc--room {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  align-items: center;
  padding: var(--space-9) var(--edge);
  column-gap: 0;
}
.svc__band { margin: 0; grid-column: 5 / -1; grid-row: 1; }
.svc__band img {
  display: block; width: 100%;
  height: clamp(26rem, 46vw, 40rem);
  object-fit: cover;
}
.svc__panel {
  grid-column: 1 / 8;
  grid-row: 1;
  z-index: 2;
  justify-self: start;
  width: 100%;
  margin: 0;
  padding: clamp(var(--space-6), 3vw, var(--space-8));
  background: var(--paper);
  box-shadow: 0 1.6rem 4rem color-mix(in srgb, var(--ink) 26%, transparent);
}

/* --- 2. UPSTAIRS: an overlay, not an overlap. The bar is the GROUND and the
       words sit in it, which is a different geometry from act 1 rather than
       the same one mirrored. Dark, because upstairs is dark. --- */
.svc--dusk {
  position: relative;
  display: grid;
  align-items: center;
  min-height: min(78svh, 44rem);
  padding: var(--space-10) var(--edge);
  background: var(--dusk-deep);
  color: var(--paper);
  isolation: isolate;
  overflow: clip;
  text-align: left;
}
/* THE PHOTOGRAPH SITS UNDER THE SCRIM, WHICH IS NOT WHERE IT WAS.

   Both this and .svc--dusk::before carried z-index: -1, and with an equal
   z-index the later element in tree order paints on top, so the photograph has
   always been ABOVE its own scrim: the scrim was only ever darkening the bare
   section, never the picture. It went unnoticed because the old photograph was
   a dark, soft upscale already knocked back to brightness(.52). The owners'
   real one is lit and full of people, and the copy over it measured 3.50:1.
   One number, and the scrim does its job. */
.svc__low { position: absolute; inset: 0; z-index: -2; margin: 0; }
.svc__low img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(.78) brightness(.52);
}
/* the scrim is read FROM the composition: the words live left, so the dark
   pools left and the bar keeps its own light on the right. */
.svc--dusk::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  /* The scrim was tuned against the OLD upstairs photograph, which was a dark,
     soft 720px upscale. The owners' real one is sharp, lit and full of people,
     and the copy over it measured 3.50:1 on its brightest patches: swapping in
     a better picture is exactly how a scrim stops being enough. Measured on
     rendered pixels with the glyphs hidden, not judged by eye. */
  background: linear-gradient(90deg,
    var(--dusk-deep) 0 38%,
    color-mix(in srgb, var(--dusk-deep) 95%, transparent) 56%,
    color-mix(in srgb, var(--dusk-deep) 58%, transparent) 78%,
    color-mix(in srgb, var(--dusk-deep) 24%, transparent) 100%);
}
.svc__mid { max-width: 34rem; margin: 0; }
.svc--dusk h2 { color: var(--paper); }
.svc--dusk .engraved { color: color-mix(in srgb, var(--paper) 72%, var(--dusk)); }
.svc--dusk p { color: color-mix(in srgb, var(--paper) 88%, var(--dusk)); }
.svc--dusk .points li { color: color-mix(in srgb, var(--paper) 88%, var(--dusk)); }
.svc--dusk .points li { border-color: color-mix(in srgb, var(--paper) 22%, transparent); }
.svc--dusk .btn--quiet {
  color: var(--paper);
  border-color: color-mix(in srgb, var(--paper) 58%, transparent);
}
.svc--dusk .btn--quiet:hover { background: var(--paper); color: var(--ink); }

/* --- 3. EVENTS: an invitation. The bone-and-gold card material Campbell asked
       for by name on the enquiry form, laid on the room celebrating. --- */
.svc--invite {
  display: grid;
  place-items: center;
  padding: var(--space-10) var(--edge);
  isolation: isolate;
  overflow: clip;
}
.svc__behind {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(.72) brightness(.42);
}
.svc__card {
  width: min(44rem, 100%);
  padding: clamp(var(--space-6), 4vw, var(--space-9));
  text-align: center;
  /* A SOLID BASE UNDER THE GRADIENT. A light card laid on a dark stratum must
     declare its own background-COLOR, not only a background-image: anything
     that reads background-color (the wall's contrast probe, a forced-colors
     mode, an engine that drops the gradient) otherwise reads straight through
     to the dark section behind and sees ink on ink. Measured on rendered
     pixels this card was always 7.5 to 11.5:1; the wall reported 1.26:1 for
     exactly this reason. The paint is unchanged, the base is now declared.

     THE COLOUR RIDES INSIDE THE SHORTHAND. Declaring background-color on its
     own line ABOVE a `background:` shorthand does nothing at all: the
     shorthand resets every background property it does not name, including
     the colour. Same family as the four specificity and shorthand faults this
     build has already shipped. */
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 96%, #fff), var(--paper-warm)) var(--paper-warm);
  border: 1px solid color-mix(in srgb, var(--gold) 52%, transparent);
  box-shadow: 0 2rem 5rem color-mix(in srgb, #000 42%, transparent);
  /* a light card nested in a dark stratum restates its own ramp, at the element
     that paints. sessions 5, 12, 13, 16 each lost an element to skipping this. */
  color: var(--ink);
}
.svc__card h2, .svc__card p { color: var(--ink); }
.svc__card p { color: var(--ink-soft); }
.svc__mark { width: clamp(3rem, 5vw, 4rem); height: auto; margin: 0 auto var(--space-4); display: block; }
.svc__card .points { text-align: left; display: inline-block; }

/* --- 4. CATERING: a lane. The kitchen travels, so the picture runs the width
       and the terms run along one rule underneath it. --- */
.svc--lane { padding: var(--space-10) var(--edge); }
.svc__head { max-width: 40rem; margin: 0 auto var(--space-7); text-align: center; }
.svc__wide { margin: 0 auto; max-width: 82rem; }
.svc__wide img { display: block; width: 100%; height: clamp(18rem, 34vw, 26rem); object-fit: cover; }
.svc__terms {
  max-width: 82rem;
  margin: var(--space-6) auto 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(var(--space-5), 4vw, var(--space-8));
  align-items: start;
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.svc__terms > p:last-child { grid-column: 1 / -1; justify-self: center; margin-top: var(--space-4); }

/* the ruled list runs as a ROW where the architecture is horizontal */
.points--row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--space-6); }

@media (max-width: 62rem) {
  /* ACT 1 ON A PHONE: one column, picture then panel.

     The desktop act is a broken plane, the photograph in columns 5 to 12 and
     the panel breaking over it from columns 1 to 7. Carrying that into a 360px
     screen without resetting the COLUMNS squeezed the picture to 212px and let
     the panel paint over 67% of it. The wall caught it: "a box may never cover
     a picture it does not own." Same fault as the stairwell rows, same cause:
     a grid re-authored for one width and inherited at the other. */
  .svc--room { grid-template-columns: 1fr; padding-inline: 0; }
  .svc__band { grid-column: 1; grid-row: 1; }
  .svc__band img { height: 15rem; }
  .svc__panel {
    grid-column: 1;
    grid-row: 2;
    width: auto;
    margin: -2.5rem var(--edge) 0;
    justify-self: stretch;
  }
  /* NO COPY OVER A PHOTOGRAPH ON A PHONE.

     Campbell, 2026-08-01: "the image for fosters (the bar photo) is not clear
     enough to use as a full section photo because it becomes blurry and the
     text overlayed is hard to read." Both halves of that are true and both
     have the same root.

     bar-room.jpg is 1800x1200 on disk but it is a 4x UPSCALE of a 720x480
     video still, so its real detail is 720px. Run full bleed at 390px on a
     retina screen it is asked for 1170 device pixels of detail it has never
     had, and it goes soft. Session 6 learned this exact lesson on the home
     band and wrote it down: smaller, framed, centred and sharper, because the
     GEOMETRY does the work. Framed at ~330px it is at its own native detail.

     And a top-to-bottom scrim was never going to save the copy: session 17
     added one and the lower half still put a ruled list and a button over a
     sunlit wooden chair. So on a phone the picture and the words separate.
     The photograph is a mounted print, the words sit on solid dusk, and
     nothing is over anything. The desktop act is untouched: there the copy
     lives in the dark left third and the bar keeps its own light on the
     right, which is why it works there and fails here. */
  .svc--dusk { padding: var(--space-9) var(--edge); min-height: 0; display: block; }
  .svc--dusk::before { display: none; }
  .svc__low {
    position: static;
    inset: auto;
    margin: 0 auto var(--space-8);
    width: min(20rem, 86%);
    /* the site's own material: a paper mat, a ruled gold fillet, one shadow */
    padding: clamp(.5rem, 2vw, .75rem);
    background: var(--paper);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 46%, transparent) inset,
                0 1.4rem 3rem color-mix(in srgb, #000 44%, transparent);
  }
  .svc__low img {
    height: auto;
    aspect-ratio: 3 / 2;
    /* no scrim over it now, so it is shown at its own brightness */
    filter: saturate(.9) brightness(.92);
  }
  .svc__mid { max-width: none; }

  .svc__terms { grid-template-columns: 1fr; }
  .points--row { grid-template-columns: 1fr; }
  .svc--invite, .svc--lane { padding-block: var(--space-9); }
}

/* ==========================================================================
   9. THE WALL: a salon hang on their own green.

   Campbell, 2026-07-31: "The other pages should have some green and creams and
   whites as well. All used/earned placed with style and creativity. everything
   earns it's place."

   The colour is earned rather than decorated: it is the green Wesley put on
   the walls, pipetted from their own photographs of the room, and her
   grandfather's sporting paintings hang on it. So the page where their
   photographs hang is that wall, and the mats stay bone and the fillets stay
   gold, which is what a framed print on a green wall actually looks like.
   (The paint's trade name is not recorded here: it was wrong for a year, and
   a stylesheet is a bad place to keep a fact about a client. It lives, with
   its open question, in the project brain.)

   It also fixes a law break. Every tile was forced to aspect-ratio 4/5 with one
   double every seventh, which is a CONTACT SHEET, and our own kill-list forbids
   exactly that: "a photo gallery that is just a masonry dump of the camera
   roll." Columns let each photograph keep its real proportions, so a portrait
   hangs tall and a landscape hangs wide, the variety is the pictures' own, and
   a column flow cannot leave the holes a dense grid was leaving.
   ========================================================================== */

body[data-path="/photos/"] {
  --wall-green: color-mix(in srgb, var(--sage-deep) 30%, var(--dusk));
}
body[data-path="/photos/"] main { background: var(--wall-green); }

/* the opening and the filters sit on the wall, so they restate their own ramp */
body[data-path="/photos/"] .display,
body[data-path="/photos/"] .lead { color: var(--paper); }
body[data-path="/photos/"] .lead { color: color-mix(in srgb, var(--paper) 86%, var(--wall-green)); }
body[data-path="/photos/"] .rule { background: color-mix(in srgb, var(--gold) 60%, transparent); }
body[data-path="/photos/"] .filters button {
  color: color-mix(in srgb, var(--paper) 88%, var(--wall-green));
  border-color: color-mix(in srgb, var(--paper) 34%, transparent);
  background: transparent;
}
body[data-path="/photos/"] .filters button[aria-pressed="true"] {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
}

/* THE HANG. Columns, not a grid: no forced aspect, no holes. */
body[data-path="/photos/"] .wall {
  display: block;
  column-count: 2;
  column-gap: var(--gutter);
  grid-template-columns: none;
}
@media (min-width: 48rem) { body[data-path="/photos/"] .wall { column-count: 3; } }
@media (min-width: 75rem) { body[data-path="/photos/"] .wall { column-count: 4; } }

body[data-path="/photos/"] .wall__pic {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  display: block;
  width: 100%;
  margin: 0 0 var(--gutter);
  grid-column: auto;
  grid-row: auto;
  /* HAND-HUNG, AND IT COSTS NO TRANSFORM (2026-08-02).

     This wall used to tilt each print with `transform: rotate(var(--tilt))`.
     In WebKit, which is Safari and therefore the phone, a TRANSFORMED box
     inside a multi-column flow fragments across the column break even with
     `break-inside: avoid` set, and the remainder paints. Measured: print 21 at
     1440 returned two client rects, the second 309px wide and 2px tall, landing
     at the TOP OF THE NEXT COLUMN. Screenshotted, it is a stray bone hairline
     with a shadow floating on the green wall above an unrelated photograph.
     Present at 1440, 1280, 900, 768 and 390. Chromium never showed it, which is
     why nine sessions of desktop checking never caught it.

     Seven candidate fixes were measured across six widths and only ONE cleared
     it at every width: no transform anywhere in the column flow. `contain`,
     `isolation`, `will-change`, the `rotate` property, `display:inline-block`,
     and moving the tilt onto an atomic inner wrapper ALL still slivered, so
     this is the transform itself, not the way it was written.

     A mat cut by hand is not square, so the hand is still in the wall: the mat
     is now UNEVEN per position, deeper on one edge than the other, which is
     what a hand-cut window actually looks like and what makes a real hang read
     as hung rather than machine-set. Deterministic per position, so the wall is
     the same every visit, exactly as the tilt was. */
  transition: box-shadow var(--dur-element, .4s) var(--ease-standard, ease);
}
body[data-path="/photos/"] .wall__pic:nth-child(6n+1) { padding: calc(var(--mat) * .8) var(--mat) calc(var(--mat) * 1.5); }
body[data-path="/photos/"] .wall__pic:nth-child(6n+3) { padding: var(--mat) calc(var(--mat) * .8) calc(var(--mat) * 1.7) calc(var(--mat) * 1.2); }
body[data-path="/photos/"] .wall__pic:nth-child(6n+4) { padding: calc(var(--mat) * 1.2) calc(var(--mat) * 1.3) calc(var(--mat) * 1.6) var(--mat); }
body[data-path="/photos/"] .wall__pic:nth-child(6n+6) { padding: calc(var(--mat) * .9) var(--mat) calc(var(--mat) * 1.4) calc(var(--mat) * 1.1); }

/* SIZE, SESSION 18. Session 17 gave every photograph its own PROPORTIONS, which
   killed the forced 4:5 contact sheet. But every print was still exactly one
   column wide, so the wall was regular down every edge: a column, not a hang.
   Nobody hangs a wall where all the frames are the same width.

   The widths are deterministic per position, so the wall is identical on every
   visit, and they sit off-centre in alternate directions so the gaps between
   frames vary the way they do on a real wall. Columns cannot span, so the
   variety is made INSIDE the column, which is also what stops it fighting the
   filters: hide any subset and what is left still hangs. */
body[data-path="/photos/"] .wall__pic:nth-child(5n+2) { width: 86%; margin-left: auto; }
body[data-path="/photos/"] .wall__pic:nth-child(5n+3) { width: 93%; }
body[data-path="/photos/"] .wall__pic:nth-child(7n+5) { width: 78%; margin-inline: auto; }
body[data-path="/photos/"] .wall__pic:nth-child(9n+7) { width: 88%; margin-right: auto; }
/* a wall needs a few big ones. these keep the full column and get the deepest
   mat, so they read as the pictures the room was hung around. */
body[data-path="/photos/"] .wall__pic[data-big="true"] { width: 100%; margin-inline: 0; }

/* each photograph keeps its own proportions. that IS the variety. */
body[data-path="/photos/"] .wall__pic img,
body[data-path="/photos/"] .wall__pic[data-big="true"] img {
  width: 100%;
  height: auto;
  aspect-ratio: auto;
}
body[data-path="/photos/"] .wall__pic[data-big="true"] { grid-column: auto; grid-row: auto; }
body[data-path="/photos/"] .wall__pic[data-big="true"] .wall__win { height: auto; }

/* a bone mat and a gold fillet, on green: what their sporting prints actually
   look like on that wall. */
body[data-path="/photos/"] .wall__pic.mount {
  /* A SOLID BASE UNDER THE GRADIENT. A light card laid on a dark stratum must
     declare its own background-COLOR, not only a background-image: anything
     that reads background-color (the wall's contrast probe, a forced-colors
     mode, an engine that drops the gradient) otherwise reads straight through
     to the dark section behind and sees ink on ink. Measured on rendered
     pixels this card was always 7.5 to 11.5:1; the wall reported 1.26:1 for
     exactly this reason. The paint is unchanged, the base is now declared.

     THE COLOUR RIDES INSIDE THE SHORTHAND. Declaring background-color on its
     own line ABOVE a `background:` shorthand does nothing at all: the
     shorthand resets every background property it does not name, including
     the colour. Same family as the four specificity and shorthand faults this
     build has already shipped. */
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 96%, #fff), var(--paper-warm)) var(--paper-warm);
  border-color: color-mix(in srgb, #000 34%, transparent);
  box-shadow: 0 1px 0 color-mix(in srgb, #fff 58%, transparent) inset,
              0 1.1rem 2.4rem color-mix(in srgb, #000 40%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  body[data-path="/photos/"] .wall__pic { transition: none; }
}

/* The display's emphasis tone is a muted olive tuned for bone paper. On the
   green wall it computed to rgb(67,72,63) against rgb(59,75,73): the same tone,
   about 1.1:1, and "the dogs." simply vanished. On this wall the emphasis takes
   the warm gold off their own frame fillets, which is the one thing in the room
   that already reads against that green. */
body[data-path="/photos/"] .display em { color: var(--rattan-lit); font-style: normal; }
body[data-path="/photos/"] .display .seal { color: var(--rattan-lit); }

/* ==========================================================================
   10. WESLEY'S BOARD.

   She is an interior designer, and what a designer produces before anything is
   built is a BOARD: the samples pinned up together. Her real one is in the
   archive (`samples.jpg`, her tartan over a fan of wood and paint chips) and
   had never been used on the site.

   So this act is not a picture of her work beside a paragraph about it. It IS
   the board, and the swatches carry the real material names, which are
   documented facts about this building rather than mood words.

   Bones nothing else on the site shares: an asymmetric pinned field, not a
   split, not a grid of equal cards, not a stack.
   ========================================================================== */
.board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-5) var(--gutter);
  align-items: start;
  padding: var(--space-10) var(--edge);
  background:
    radial-gradient(120% 80% at 80% 10%, color-mix(in srgb, var(--sage) 20%, transparent), transparent 60%),
    var(--paper-warm);
}
.board__say { grid-column: 1 / 6; grid-row: 1; align-self: center; }
.board__say h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.2rem, 4vw, 3.4rem);
  line-height: 1;
  margin: var(--space-2) 0 var(--space-4);
}
.board__say p { line-height: 1.72; color: var(--ink-soft); }
.board__say p + p { margin-top: var(--space-4); }

/* the pins. paper mat, one soft shadow, hand-set angles. */
.board__pin {
  position: relative;
  margin: 0;
  padding: clamp(.5rem, .8vw, .8rem) clamp(.5rem, .8vw, .8rem) 0;
  /* A SOLID BASE UNDER THE GRADIENT. A light card laid on a dark stratum must
     declare its own background-COLOR, not only a background-image: anything
     that reads background-color (the wall's contrast probe, a forced-colors
     mode, an engine that drops the gradient) otherwise reads straight through
     to the dark section behind and sees ink on ink. Measured on rendered
     pixels this card was always 7.5 to 11.5:1; the wall reported 1.26:1 for
     exactly this reason. The paint is unchanged, the base is now declared.

     THE COLOUR RIDES INSIDE THE SHORTHAND. Declaring background-color on its
     own line ABOVE a `background:` shorthand does nothing at all: the
     shorthand resets every background property it does not name, including
     the colour. Same family as the four specificity and shorthand faults this
     build has already shipped. */
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 96%, #fff), var(--paper)) var(--paper);
  border: 1px solid color-mix(in srgb, var(--gold) 32%, transparent);
  box-shadow: 0 1.1rem 2.6rem color-mix(in srgb, var(--ink) 20%, transparent);
  /* The rotation lives here and the pins deliberately carry NO [data-rise]:
     the reveal resolves to transform:none, which is the same property, so the
     reveal would silently flatten every angle. Session 10 banked this exact
     collision (a pointer tilt on a [data-rise] element killed the rise). The
     pins are never hidden, which is this session'''s law anyway. */
  transform: rotate(var(--pin, 0deg));
}
.board__pin img { display: block; width: 100%; height: auto; }
.board__tag {
  margin: 0;
  padding: var(--space-2) 0 var(--space-3);
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .13em;
  text-transform: uppercase;
  /* --ink-faint measured 4.42:1 here against the board ground: a whisper tone
     used for a functional caption, which is the same fault session 16 caught on
     the invitation labels. Functional text takes --ink-soft. */
  color: var(--ink-soft);
  text-align: center;
}
.board__pin--samples   { grid-column: 6 / 9;  grid-row: 1; --pin: -2.2deg; }
.board__pin--art       { grid-column: 9 / 12; grid-row: 1; --pin: 2.6deg; align-self: end; }
.board__pin--banquette { grid-column: 7 / 10; grid-row: 2; --pin: 1.6deg; }
.board__pin--ceiling   { grid-column: 10 / -1; grid-row: 2; --pin: -1.8deg; }

/* the four things a visitor can actually see in the dining room, named. */
.board__chips {
  grid-column: 1 / 6;
  grid-row: 2;
  list-style: none;
  margin: 0;
  padding: var(--space-5) 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: var(--space-3);
}
.board__chips li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--t-caption);
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.board__chips span {
  width: 1.5rem; height: 1.5rem; flex: none;
  background: var(--chip);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 22%, transparent) inset;
}

@media (max-width: 62rem) {
  /* one column, and the pins become a real two-up field under the words rather
     than four boxes in a stack. Rows named, because a grid re-authored for one
     width and inherited at the other is the fault this build keeps producing. */
  .board { grid-template-columns: repeat(2, 1fr); padding: var(--space-9) var(--edge); }
  .board__say { grid-column: 1 / -1; grid-row: 1; }
  .board__pin--samples   { grid-column: 1; grid-row: 2; }
  .board__pin--art       { grid-column: 2; grid-row: 2; align-self: start; }
  .board__pin--banquette { grid-column: 1; grid-row: 3; }
  .board__pin--ceiling   { grid-column: 2; grid-row: 3; }
  .board__chips { grid-column: 1 / -1; grid-row: 4; grid-template-columns: repeat(2, 1fr); }
}
@media (prefers-reduced-motion: reduce) { .board__pin { transform: none; } }

/* ==========================================================================
   11. BEFORE. The two site photographs, overlapping on a dark ground.

   They were a two-up row of equal mounted panes, which is a card row wearing
   mats. These are SITE pictures from the build, so they overlap the way
   photographs pinned up during a job actually do, and the dark is earned:
   this is the only stretch of the story where the lights were off.
   ========================================================================== */
.before {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  align-items: center;
  gap: var(--space-6) var(--gutter);
  padding: var(--space-10) var(--edge);
  background: var(--dusk-deep);
  color: var(--paper);
  overflow: clip;
}
.before__say { grid-column: 1 / 5; grid-row: 1; }
.before__say .engraved { color: color-mix(in srgb, var(--paper) 66%, var(--dusk)); }
.before__say p:last-child {
  margin-top: var(--space-3);
  line-height: 1.72;
  color: color-mix(in srgb, var(--paper) 86%, var(--dusk));
}
.before__shot {
  margin: 0;
  padding: clamp(.45rem, .7vw, .7rem) clamp(.45rem, .7vw, .7rem) 0;
  background: color-mix(in srgb, var(--paper) 94%, var(--dusk));
  box-shadow: 0 1.6rem 4rem color-mix(in srgb, #000 56%, transparent);
}
.before__shot img { display: block; width: 100%; height: auto; }
.before__shot figcaption {
  padding: var(--space-2) 0 var(--space-3);
  text-align: center;
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .13em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 74%, var(--paper));
}
/* they overlap: the inherited bar behind, the stripped shell in front of it */
.before__shot--bar   { grid-column: 5 / 10; grid-row: 1; transform: rotate(-1.6deg); z-index: 1; }
.before__shot--shell { grid-column: 8 / -1; grid-row: 1; transform: rotate(2deg) translateY(2.5rem); z-index: 2; }

@media (max-width: 62rem) {
  .before { grid-template-columns: 1fr; padding: var(--space-9) var(--edge); }
  .before__say { grid-column: 1; grid-row: 1; }
  .before__shot--bar   { grid-column: 1; grid-row: 2; transform: rotate(-1.4deg); width: 88%; }
  .before__shot--shell { grid-column: 1; grid-row: 3; transform: rotate(1.6deg) translateY(-1.5rem); width: 88%; justify-self: end; }
}
@media (prefers-reduced-motion: reduce) { .before__shot { transform: none !important; } }

/* ==========================================================================
   12. THE MENU, SESSION 18.

   Campbell approved this page's card by name in session 3 ("I like the menu.
   It feels a little bit better, a little bit more compact") and it is not
   touched here. What is added is the one thing a menu on a screen can do that
   a printed card cannot, which is know the hour, and the removal of the .split that
   was sitting on the one page whose whole material is the engraved card.
   ========================================================================== */

/* --- THE SERVICE INDEX. The site's own ghosted-index device (CONCEPT.md §5,
       stolen from Obys for the elevation marker) turned on its side to carry
       the hours. NOT a status pill, NOT a badge, NOT a dot: the median's
       vocabulary is banned on this build and this is drawn from the house.
       Ruled entries, no boxes, no cards, so it cannot read as a 4-across
       card grid. --- */
.service {
  max-width: 74rem;
  margin-inline: auto;
  text-align: center;
}
.service__now {
  font-family: var(--display);
  font-size: clamp(1.05rem, 1.9vw, 1.45rem);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 40ch;
  margin: 0 auto var(--space-6);
}
/* the live sentence, once the clock has spoken, carries the house's weight */
.service[data-live] .service__now { color: var(--ink); }

.service__list {
  list-style: none;
  margin: 0;
  padding: var(--space-4) 0;
  /* A FLEX MASTHEAD, NOT A TABLE. First take was a 4-column grid with vertical
     rules between the cells, and on screen that is a table: four equal boxes
     reading as four equal buttons. An engraved card sets its services as a
     masthead line, so this wraps and breathes instead of dividing. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-4) clamp(var(--space-6), 6vw, var(--space-10));
  border-top: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
}
.service__list li {
  display: flex;
  flex-direction: column;
  gap: .4rem;
  transition: opacity .9s var(--house-ease), color .9s var(--house-ease);
}
.service__list b {
  font-family: var(--display-sc), var(--display);
  font-weight: 400;
  font-size: var(--ms-1);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink);
  position: relative;
  display: inline-block;
}
.service__list i {
  font-style: normal;
  font-size: var(--t-label);
  line-height: 1.45;
  color: var(--ink-soft);
}

/* GHOSTING ONLY HAPPENS WHEN SOMETHING IS LIT. The first take ghosted all four
   by default, so at 10am, with JS off, or in a screenshot, the whole index read
   as disabled rather than as quiet. An index with no active entry is just a
   list, and a list should be legible. */
.service[data-live] .service__list li { opacity: .44; }
.service[data-live] .service__list li[data-on="true"] { opacity: 1; }

/* THE LIVE ENTRY. Ink, full weight, and their red under it: the same mark the
   elevation index puts on the floor you can walk into right now, and nowhere
   else on the site. */
.service__list li[data-on="true"] b::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: -.42rem;
  width: .3rem;
  height: .3rem;
  border-radius: 50%;
  background: var(--tolix);
}
.service__list li[data-on="true"] i { color: var(--ink); }

@media (max-width: 46rem) {
  /* On a phone the four become a ruled list, read top to bottom, because four
     stacked pairs centred at 390px is four columns of broken words. */
  .service__list { flex-direction: column; gap: 0; align-items: stretch; text-align: left; }
  .service__list li {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
  }
  .service__list li + li { border-top: 1px solid color-mix(in srgb, var(--ink) 12%, transparent); }
  .service__list i { text-align: right; flex: 0 1 auto; }
  .service__list li[data-on="true"] b::after { left: auto; right: -.7rem; transform: none; bottom: .28rem; }
}

/* --- UPSTAIRS. Was a .split. Now the dark room the bar actually is, with the
       card nobody has printed yet laid on it. The geometry is deliberately NOT
       the services page's .svc--dusk (words in the ground, bar keeping its
       light on one side): here the room is the full field and the object on it
       is a CARD, because card is this page's material and the whole point is
       that this one is blank. --- */
.upstairs {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(var(--space-7), 6vw, var(--space-10));
  padding: clamp(var(--space-9), 9vw, var(--space-11)) var(--edge);
  background: var(--dusk-deep);
  color: var(--paper);
  isolation: isolate;
  overflow: clip;
}
.upstairs__room {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(.7) brightness(.38);
}
.upstairs::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* the lamp is up and to the right, where the real one is in the photograph */
  background:
    radial-gradient(110% 80% at 74% 20%, color-mix(in srgb, var(--dusk-lamp) 40%, transparent), transparent 58%),
    /* the fully dark band reaches PAST the copy column (30rem + the edge is
       about 42% of a 1440 viewport), because the hours line was measuring
       3.14:1 against the lightest pixel actually painted behind it once the
       bar's own lamps showed through the scrim. Measured on rendered pixels
       with the text hidden, never on the scrim's declared colour. */
    linear-gradient(96deg, var(--dusk-deep) 0 44%, color-mix(in srgb, var(--dusk-deep) 88%, transparent) 62%, color-mix(in srgb, var(--dusk-deep) 62%, transparent) 100%);
}
/* A DARK STRATUM NAMES ITS CHILDREN, never restates --ink wholesale: --ink is a
   text colour in most places and a FILL in a few (.btn fills itself with it),
   and a blanket restatement blanked a live button in session 17. */
.upstairs__say { position: relative; z-index: 2; max-width: 30rem; }
.upstairs__say h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.4rem, 5vw, 4rem);
  line-height: 1;
  margin: var(--space-2) 0 var(--space-5);
  color: var(--paper);
}
.upstairs__say p { color: color-mix(in srgb, var(--paper) 88%, var(--dusk)); line-height: 1.72; }
/* FUNCTIONAL TEXT IS NEVER A WHISPER COLOUR. These are the bar's real opening
   hours, which is the single most useful sentence in the act, and at 66% paper
   it was a caption tone doing a signpost's job. Session 16 caught the same
   class on the invitation card labels. */
.upstairs__say .fine { color: color-mix(in srgb, var(--paper) 86%, var(--dusk)); margin-top: var(--space-4); }

/* --- THE BLANK CARD. Foster's is weeks old and its drinks list does not exist
       in anything we hold, so the page prints the card it actually has. The
       ruled lines are decorative and aria-hidden; every word a reader needs is
       real text under them. --- */
.blankcard {
  position: relative;
  z-index: 2;
  margin: 0;
  justify-self: center;
  width: min(30rem, 100%);
}
.blankcard__in {
  padding: clamp(var(--space-6), 3.4vw, var(--space-8));
  text-align: center;
  /* a light card nested in a dark stratum restates its own ramp AT THE ELEMENT
     THAT PAINTS. Sessions 5, 12, 13, 16 and 17 each lost an element by
     inheriting a stratum's tokens instead. */
  color: var(--ink);
  /* A SOLID BASE UNDER THE GRADIENT. A light card laid on a dark stratum must
     declare its own background-COLOR, not only a background-image: anything
     that reads background-color (the wall's contrast probe, a forced-colors
     mode, an engine that drops the gradient) otherwise reads straight through
     to the dark section behind and sees ink on ink. Measured on rendered
     pixels this card was always 7.5 to 11.5:1; the wall reported 1.26:1 for
     exactly this reason. The paint is unchanged, the base is now declared.

     THE COLOUR RIDES INSIDE THE SHORTHAND. Declaring background-color on its
     own line ABOVE a `background:` shorthand does nothing at all: the
     shorthand resets every background property it does not name, including
     the colour. Same family as the four specificity and shorthand faults this
     build has already shipped. */
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 96%, #fff), var(--paper-warm)) var(--paper-warm);
  border: 1px solid color-mix(in srgb, var(--gold) 52%, transparent);
  box-shadow: 0 2rem 5rem color-mix(in srgb, #000 46%, transparent);
}
.blankcard__mark { width: clamp(2.6rem, 4vw, 3.4rem); height: auto; margin: 0 auto var(--space-4); display: block; }
.blankcard__head {
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0;
}
.blankcard__rule {
  border: 0;
  height: 1px;
  background: color-mix(in srgb, var(--gold) 62%, transparent);
  margin: var(--space-4) auto var(--space-5);
  width: 42%;
}
/* the empty rules. This is the whole idea: an engraved card with nothing
   printed on it yet. */
.blankcard__lines { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: grid; gap: var(--space-5); }
/* the same two-part row the engraved card downstairs sets every dish on: the
   name's rule, then the price rail. Both blank, because the list is blank. */
/* GRID, not flex: the price rail holds one right column on every row exactly
   as it does on the real card two inches to the left. Under flex the rails
   floated in to meet each shorter name rule and the right edge went ragged. */
.blankcard__lines li { display: grid; grid-template-columns: 1fr 2.4rem; align-items: center; gap: var(--space-4); }
.blankcard__lines i, .blankcard__lines b { height: 1px; background: color-mix(in srgb, var(--ink) 20%, transparent); }
/* dish names are not all one length, so neither are the blanks for them */
.blankcard__lines i { width: 100%; }
.blankcard__lines li:nth-child(3n) i { width: 68%; }
.blankcard__lines li:nth-child(4n) i { width: 84%; }
.blankcard__say {
  font-size: var(--ms-1);
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 0 auto;
  max-width: 30ch;
}
.blankcard__call { margin-top: var(--space-5); }
.blankcard__call .link { color: var(--ink); }

/* WHEN FOSTER'S IS ACTUALLY POURING, the card is under a lit lamp. The site's
   one live truth, applied to the one room that has nothing to read. */
[data-house="bar"] .blankcard__in {
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--rattan-lit) 34%, transparent),
    0 2rem 5rem color-mix(in srgb, #000 46%, transparent),
    0 -1.2rem 5rem color-mix(in srgb, var(--rattan-lit) 22%, transparent);
}

@media (max-width: 62rem) {
  /* THE PHONE IS A DIFFERENT COMPOSITION, NOT THE DESKTOP ONE COLLAPSED.

     Stacked into one column, the opaque bone card landed ON TOP of the bar
     photograph and covered 67% of it: the picture was doing nothing but
     peeking round the edges of a box that does not own it, and the card read
     as floating on a random image. The wall caught it at 360, 390 and 430.

     So on a phone the photograph stops being a background and becomes its own
     band, full bleed, at its own brightness, with nothing over it. The room,
     then the words, then the card, in that order, each with the whole width.
     The desktop act is untouched: there the card sits in the dark BESIDE the
     lit half of the room, which is why it works there and not here. */
  .upstairs {
    grid-template-columns: 1fr;
    padding-top: clamp(var(--space-8), 8vw, var(--space-9));
  }
  /* THE SAME LAW AS THE SERVICES ACT: a soft photograph is never a full-bleed
     band on a phone. I shipped it as one during the session-18 wall pass and
     Campbell caught it the same day. bar-room.jpg is a 4x upscale of a 720px
     video still, so full bleed at DPR 3 asks it for detail it does not have.
     Framed at ~330px it renders at its own native resolution and it is sharp,
     and it carries the material language every other photograph on this site
     already wears. Geometry does the work. */
  .upstairs__room {
    position: static;
    z-index: auto;
    grid-row: 1;
    justify-self: center;
    width: min(20rem, 86%);
    max-width: none;
    height: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    padding: clamp(.5rem, 2vw, .75rem);
    background: var(--paper);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 46%, transparent) inset,
                0 1.4rem 3rem color-mix(in srgb, #000 44%, transparent);
    filter: saturate(.9) brightness(.92);
  }
  /* the scrim protected copy that no longer sits on the picture */
  .upstairs::before { display: none; }
  .upstairs__say { grid-row: 2; max-width: none; padding-top: var(--space-8); }
  .blankcard { grid-row: 3; width: 100%; }
}

/* ==========================================================================
   13. OUR STORY, SESSION 18. The last three .split skeletons on the site.

   Session 17 built the board (Wesley) and the before (the handover) and left
   four acts running the same bones: words in one column, a picture in the
   other, alternating sides. Alternating the side of a repeated skeleton is a
   stricter template than the zig-zag it replaced, which is the exact note
   Campbell gave in session 3 and the reason services was rebuilt in 17.
   ========================================================================== */

/* --- THE PASS. Scott's act, and the OPPOSITE SPATIAL LAW to Wesley's board.
       A designer's work is a board: flat, overlapping, pinned, all at once.
       A chef's work is a line at eye level under a lamp, one thing after
       another, in order. --- */
.pass {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  align-items: end;
  column-gap: clamp(var(--space-7), 5vw, var(--space-10));
  row-gap: 0;
  padding: clamp(var(--space-9), 8vw, var(--space-11)) var(--edge) 0;
  background: var(--dusk-deep);
  color: var(--paper);
  isolation: isolate;
  overflow: clip;
}
/* the heat lamp. A pass is lit from directly above and nowhere else. */
.pass__lamp {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(85% 46% at 50% 0%, color-mix(in srgb, var(--rattan-lit) 26%, transparent), transparent 70%),
    linear-gradient(180deg, color-mix(in srgb, var(--dusk) 60%, var(--dusk-deep)), var(--dusk-deep) 62%);
}

/* A DARK STRATUM NAMES ITS CHILDREN. Never a blanket --ink restatement: --ink
   is a text colour in most places and a FILL in a few, and restating it
   wholesale blanked a live button in session 17. */
.pass__say { grid-column: 2; grid-row: 1; padding-bottom: clamp(var(--space-8), 5vw, var(--space-10)); }
.pass__say h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.4rem, 5vw, 3.8rem);
  line-height: 1;
  margin: 0 0 var(--space-5);
  color: var(--paper);
}
.pass__say p { color: color-mix(in srgb, var(--paper) 86%, var(--dusk)); line-height: 1.75; max-width: 54ch; }

/* his portrait BREAKS the rail, because he is the one standing at it.

   A transform alone does NOT do this: translateY moves the pixels and leaves
   the layout box where it was, so the first take left a 75px gap between the
   frame and the rail and nothing crossed anything. The negative margin pulls
   the rail back up by the same distance the frame travelled down, so the frame
   genuinely lands ON the line. */
.pass__him {
  --break: clamp(2.5rem, 5vw, 4.5rem);
  grid-column: 1;
  grid-row: 1;
  margin: 0 0 calc(-1 * var(--break));
  align-self: end;
  position: relative;
  z-index: 2;
  transform: translateY(var(--break));
}
/* THE LEGEND, BESIDE THE PLATE, not under it.

   Under it, the caption was the lowest thing in the figure, so IT was what
   crossed the rail instead of the frame, and the hairline ran straight through
   the words. Beside the frame it sits in dark that was empty anyway, reads
   cleanly, and lets the frame itself be the thing that breaks the line, which
   was the whole point. Back into normal flow the moment the columns collapse. */
.pass__him figcaption {
  position: absolute;
  left: calc(100% + var(--space-5));
  /* low, near the frame it belongs to (higher up it sat under the last line of
     the bio with no air and read as part of the paragraph), but clear of the
     rail: at --space-2 it landed exactly 1px onto the hairline. */
  bottom: var(--space-5);
  width: 15rem;
  margin: 0;
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .13em;
  line-height: 1.6;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 62%, var(--dusk));
}

/* THE RAIL. Four kitchens, in the order he cooked in them, hanging off one
   line that runs the width of the room. Every stop is a fact from his own
   written bio, which is why the row is marked earned. */
.pass__rail {
  grid-column: 1 / -1;
  grid-row: 2;
  list-style: none;
  margin: clamp(var(--space-7), 4vw, var(--space-8)) 0 0;
  padding: var(--space-6) 0 clamp(var(--space-9), 7vw, var(--space-11));
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* TWO NAMED ROWS, and each stop spans both of them as a subgrid, so every
     description starts on one baseline. Without this "Jackson Hole, Wyoming"
     wraps to two lines and drops its own description below the other three,
     which reads as a mistake because it is one. */
  grid-template-rows: auto auto;
  gap: var(--space-6);
  border-top: 1px solid color-mix(in srgb, var(--rattan-lit) 42%, transparent);
}
.pass__rail li {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  align-content: start;
  gap: .5rem;
  position: relative;
  padding-top: var(--space-2);
}
/* engines without subgrid keep a working stack rather than a broken one */
@supports not (grid-template-rows: subgrid) {
  .pass__rail li { display: flex; flex-direction: column; }
  .pass__rail b { min-height: 2.3em; }
}
/* the ticket hangs from the rail */
.pass__rail li::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--space-6) - 1px);
  left: 0;
  width: 1px;
  height: var(--space-6);
  background: color-mix(in srgb, var(--rattan-lit) 42%, transparent);
}
.pass__rail b {
  font-family: var(--display-sc), var(--display);
  font-weight: 400;
  font-size: var(--ms-1);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--paper);
}
.pass__rail i {
  font-style: normal;
  font-size: var(--t-label);
  line-height: 1.5;
  color: color-mix(in srgb, var(--paper) 72%, var(--dusk));
}
/* the last stop is where he is now, so it is the one in their light */
.pass__rail li:last-child b { color: var(--rattan-lit); }

@media (max-width: 62rem) {
  /* the section carries its own bottom air here, so the rail's LINE can stop
     where the last stop stops. Left on the rail's padding it ran on for
     another 180px past "his own pass, at last" and read as unfinished. */
  .pass { grid-template-columns: 1fr; padding-inline: var(--edge); padding-bottom: clamp(var(--space-9), 8vw, var(--space-10)); }
  /* the words come first on a phone, then the man they are about */
  .pass__say { grid-column: 1; grid-row: 1; padding-bottom: var(--space-7); }
  .pass__him { grid-column: 1; grid-row: 2; transform: none; margin-bottom: 0; width: min(24rem, 88%); }
  .pass__him figcaption { position: static; width: auto; margin-top: var(--space-3); }
  .pass__rail {
    grid-row: 3;
    grid-template-columns: 1fr;
    gap: 0;
    border-top: 0;
    /* on one column the rail is VERTICAL, which is what a line of four stops
       actually is at 390px. The stops hang off its left edge. */
    border-left: 1px solid color-mix(in srgb, var(--rattan-lit) 42%, transparent);
    padding: 0 0 0 var(--space-6);
    margin-top: var(--space-7);
  }
  .pass__rail li { padding: var(--space-4) 0; }
  .pass__rail li::before {
    top: calc(var(--space-4) + .55rem);
    left: calc(-1 * var(--space-6));
    width: var(--space-5);
    height: 1px;
  }
}

/* --- THE PAIR. Lou Lou low, Foster high. They have carried opposite MOTION
       laws since session 3 (she settles with no overshoot, he comes in hot and
       rocks back) and this gives them opposite SPATIAL ones, which is also
       true of the building: the bistro is hers downstairs, the bar is his up
       the staircase. --- */
.pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: clamp(var(--space-7), 5vw, var(--space-10));
  margin-top: var(--space-9);
}
.pair__one { display: flex; flex-direction: column; gap: var(--space-6); }
.pair__one--low  { transform: translateY(clamp(2.5rem, 6vw, 5rem)); }
.pair__one--high { transform: translateY(clamp(-2rem, -3vw, -1rem)); }
/* the name card sits under its own portrait, on the portrait's axis */
.pair .namecard { margin: 0; }

@media (max-width: 62rem) {
  /* one column: the stagger has nothing to express, so it goes. A transform
     that survives the collapse only ever produces a gap and a nudge. */
  .pair { grid-template-columns: 1fr; gap: var(--space-10); }
  .pair__one--low, .pair__one--high { transform: none; }
}

/* --- THE LEGEND. Wesley's line set under the portrait of the two of them,
       rather than in a column beside it. --- */
.quote--legend { max-width: 34ch; margin: var(--space-7) auto 0; text-align: center; }

/* ==========================================================================
   14. THE BOOK, GROUPED BY SERVICE (session 18).

   The one fact that makes this restaurant different from every other one in
   Columbus is that downstairs runs a split: lunch, then the kitchen goes dark
   for a few hours, then dinner. The book knew it (every slot the engine
   returns carries its service name) and printed it in small caps under all
   forty pills, which is the same three words forty times and the fact itself
   nowhere. Said once, over the times it belongs to.
   ========================================================================== */
.book__service + .book__service { margin-top: var(--space-6); }
.book__when {
  font-family: var(--display-sc), var(--display);
  font-size: var(--t-caption);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
  margin: 0 0 var(--space-4);
  /* a hairline out to both sides, the same rule the invitation card bands use,
     so the book is set in the stationery it sits on */
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.book__when::before, .book__when::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: color-mix(in srgb, var(--gold) 46%, transparent);
}
.book__row { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); }
