/* ==== base/tokens.css ==== */
/* ==========================================================================
   A · TOKENS
   --------------------------------------------------------------------------
   The whole palette, one place. Everything below this file in the bundle is
   allowed to assume these exist and is not allowed to invent new ones.

   THE TEXT TOKENS ALL CLEAR WCAG 2.2 AA (4.5:1) against all four page grounds
   — bone, bone-2, #ece0c9, #f3edde. An earlier pass had small text sitting at
   2.3–3.9:1: the topnav, the kickers, tags, map labels and
   signoff were every one of them failing. 0xac is the alpha floor (4.65:1 on
   the darkest ground). Do not reintroduce a lighter alpha for TEXT.

   The rule tokens are separate on purpose. A 1px non-text boundary needs 3:1,
   not 4.5:1, and hairlines set at text contrast look like underlines. Use
   --rule / --rule-2 for borders and separators, never for a glyph.
   ========================================================================== */
:root{
  /* Scroll progress rest state. The engine (main.js) writes --p per .sticky
     stage every frame, but only once it has booted — so on first paint, and on
     any element read before the first tick, a BARE var(--p) is invalid and its
     calc() collapses to the property's initial value. For #hero .germ that
     initial is opacity:1, which flashed the Germania woodcut to a full-strength
     multiply mass (read as "the building flashes black") before snapping to its
     .13 rest. A global 0 default makes every var(--p) resolve to the at-rest
     look until the stage that owns it overrides with a real value. */
  --p:0;

  --bone:#efe7d7;
  --ink:#1b2436;
  --night:#0b1020;

  /* text */
  --ink-70:#1b2436b3;       /* editorial body — 5.29:1 */
  --ink-60:#1b2436b8;       /* 5.33:1 */
  --ink-55:#1b2436b0;       /* 4.86:1 */
  --ink-45:#1b2436ac;       /* 4.65:1 — smallest type on the page */
  --ink-40:#1b2436ac;       /* 4.65:1 */

  /* non-text */
  --rule:#1b243638;
  --rule-2:#1b243673;
  --ink-22:#1b243638;       /* deprecated alias of --rule */

  --bone-2:#e7dcc7;
  --oxblood:#8a3324;
  --gold:#e9c46a;

  --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif;
  /* Titles only — the condensed titling serif loaded in §B. Kept separate from
     --serif because body copy wants the wider, lower-contrast face: Instrument
     Serif has one weight and tight hairlines, which is right for six words at
     6rem and wrong for a paragraph. The local stack is the fallback while the
     woff2 swaps in, so an unstyled first paint is still a serif. */
  --display:"Instrument Serif","Iowan Old Style",Palatino,Georgia,"Times New Roman",serif;

  --pad:8vw;
  --mx:0; --my:0;           /* pointer, -1..1, written by the engine */

  /* THE RAIL GUTTER — now permanently 0, and kept only as a name.

     It used to be the width of the column the fixed chapter rail owned: the
     rail was position:fixed, so it was outside flow and content knew nothing
     about it, and between about 820 and 1180px it landed inside live text —
     the contact address, the menu prices, the origin paragraph. Reserving a
     92/114px lane on the right was the fix.

     The rail is gone, so the lane is too, and every right-hand inset now
     matches its left-hand partner: the page is symmetrical again and the
     editorial frame centres in the whole viewport rather than in what was left
     of it. The three names below are load-bearing across a dozen section
     stylesheets, so the token stays at 0 rather than being deleted — one line
     to re-open a right-hand lane if anything ever needs one again. */
  --rail-gutter:0px;
  --pad-r:calc(var(--pad) + var(--rail-gutter));

  /* THE EDITORIAL FRAME — ordinary left- or right-aligned narrative shares one
     1180px frame, centred in the viewport. Below that width the familiar --pad
     inset still wins; centred cards, captions and stage furniture continue to
     use --pad / --pad-r instead. */
  --content-max:1180px;
  --frame-start:max(var(--pad),calc((100vw - var(--rail-gutter) - var(--content-max))/2));
  --frame-end:calc(var(--frame-start) + var(--rail-gutter));
}


/* ==== base/fonts.css ==== */
/* ==========================================================================
   B · FONTS
   --------------------------------------------------------------------------
   Instrument Serif (SIL OFL, github.com/Instrument/instrument-serif), used for
   section titles. Chosen by measuring it against 113 free serifs on outline
   overlap with the custom face Fornasetti commissioned from Alessio D'Ellena
   — it won by 18 points, because it is the only free serif that is CONDENSED
   (H is 0.71× cap height; Playfair is 0.95×, Prata 0.98×). That proportion is
   the whole reason a titling serif looks editorial rather than generic.

   SELF-HOSTED, not linked from fonts.googleapis.com — same reasoning as the
   vendored HTMX: a title should not depend on a third party being up, and the
   Google CSS costs an extra DNS + TLS handshake before the first glyph. Four
   files, 47 kB total.

   Because we ship the binaries rather than link them, we are redistributing
   them, and the OFL requires its own text to travel with the files: it is at
   public/fonts/OFL.txt. Do not delete it as stray weight — it is the licence
   the four woff2 files are here under.

   THERE IS ONLY ONE WEIGHT. Instrument Serif ships 400 regular + 400 italic
   and nothing else, so §D sets headings to `font-weight:400`. Do not put a
   500 or a 700 on a title: there is no bolder file to load, so the browser
   would synthesize one by smearing the outline, which on a high-contrast
   serif thickens the hairlines into mud. If a title ever genuinely needs more
   weight, it needs a different family, not a heavier number.
   ========================================================================== */

/* latin-ext first, latin second: unicode-range makes this a filter, not a
   cascade, so both are candidates and the browser downloads only the subsets
   the page's characters actually fall into. Ordinary English copy fetches the
   latin file alone. */
@font-face{
  font-family:"Instrument Serif";
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("/prelaunch-site-v0.1/fonts/instrument-serif-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,
    U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,
    U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"Instrument Serif";
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("/prelaunch-site-v0.1/fonts/instrument-serif-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,
    U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* The italic is a real cut, not a slant — worth having because §hero's deck and
   the 1877 caption are both set in italic serif. */
@font-face{
  font-family:"Instrument Serif";
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url("/prelaunch-site-v0.1/fonts/instrument-serif-italic-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,
    U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,
    U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"Instrument Serif";
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url("/prelaunch-site-v0.1/fonts/instrument-serif-italic-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,
    U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}


/* ==== base/reset.css ==== */
/* ==========================================================================
   A · RESET
   Deliberately three rules. A full normalize would be more code than the
   difference it makes on a page whose every element is styled here anyway.
   ========================================================================== */
*{box-sizing:border-box;margin:0;padding:0}
/* NO scroll-behavior:smooth HERE. It was global, and the page is 20 screens: a
   header click from the hero to the visit section animated ~18,000px as one
   uninterruptible ride, which is a long time to watch the site flick past and
   no way to tell where you ended up. Distance is the deciding factor and CSS
   cannot see distance, so the choice moved to the delegated click handler in
   main.js — smooth for a hop of two screens or less, instant beyond that.
   With JS off every link is an ordinary instant jump, which is the correct
   fallback rather than a degraded one. */
html{background:var(--bone)}
body{
  background:var(--bone);
  color:var(--ink);
  font-family:var(--serif);
  overflow-x:hidden;
}
svg{display:block;color:currentColor}


/* ==== base/chrome.css ==== */
/* ==========================================================================
   B · FIXED CHROME — wordmark, header links, the return link
   markup: partials/chrome.eta + page.eta   ·   copy: site.json → nav

   The `.dark` class is written on <body> by the engine when an ink-ground
   section sits under the chrome, and every flip in this file hangs off it. That
   is the whole mechanism: one class, set from the section registry's `dark`
   field, and the chrome inverts.
   ========================================================================== */

/* ---------------- skip link ----------------
   First thing in the tab order, invisible until focused. A keyboard reader
   shouldn't have to tab six header links to reach a word of copy. */
.skip{position:fixed;top:-100px;left:var(--pad);z-index:100;
   font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
   background:var(--ink);color:var(--bone);padding:.85rem 1.3rem;text-decoration:none;
   border-radius:2px;transition:top .18s ease}
.skip:focus-visible{top:26px;outline:2px solid var(--gold);outline-offset:2px}
/* the skip target takes focus so the NEXT tab continues from the content rather
   than from the top of the header, but it is the whole document and a ring
   drawn around it would be a border around the page. */
#main:focus{outline:none}

/* ---------------- the fixed mark ----------------

   THE MARK IS NOT SHOWN OVER THE HERO, and that is the one behaviour this file
   has that the full site's doesn't. The hero carries the wordmark at 240px in
   the middle of the screen — it is the entire first frame — so a second, 52px
   copy of the identical drawing in the top-left corner is the logo printed
   twice on one screen. It reads as a mistake, not as branding.

   THE HIDING IS A CLASS ON <body>, NOT A CUSTOM PROPERTY, and the difference
   matters: `pointer-events` cannot be driven by a 0–1 number, and a mark faded
   to opacity 0 that is still a 68px click target over the hero is worse than
   one that is visible. One class settles opacity, transform and hit-testing
   together, so the three can never disagree.

   `body.at-hero` is written by public/js/sections/hero.js while the hero owns
   the screen, and removed once it has gone. THE DEFAULT — no class — IS
   VISIBLE, which is the important half of this rule: with JS off, before the
   first frame, or if the module ever throws, the mark is simply there. The
   failure mode of a fade-in driven by script has to be "shown", never "absent".

   It arrives from 6px above rather than just fading, which is the direction it
   would have come from had it scrolled up out of the hero.

   THE TRANSITION IS ASYMMETRIC AND THAT IS DELIBERATE. Appearing fades over
   .5s; disappearing is instant. The reason is the one frame between first paint
   and the hero module's first call: the mark is rendered visible (it must be —
   see above), so if the class arrives after the page has painted, a symmetric
   transition turns that into a half-second of the logo fading out of the corner
   on load, which looks like a bug. Instant hiding costs at most a single frame
   of the mark being there. Scrolling back up to the hero snaps it away rather
   than fading it, which is also the better behaviour: it is getting out of the
   way of a larger copy of itself. */
.mark{position:fixed;top:26px;left:var(--pad);z-index:80;line-height:0;
   transition:opacity .5s ease,transform .5s ease}
body.at-hero .mark{opacity:0;transform:translateY(-6px);pointer-events:none;
   transition:none}

/* THE CHROME'S GROUND. The mark and the topnav are fixed, so they have no
   ground of their own, and every line of copy that scrolls past the top of the
   screen passes straight through them.

   ONE BAND ACROSS THE TOP, not a shape behind each element. It used to be two
   masked ellipses — a small one under the mark, a wide one under the nav — and
   the wide one was the problem: an ellipse is a shape, a shape has a middle
   that is more opaque than its ends, and once the nav had five links it was
   wide enough that you could see the bulge travelling behind the row as the
   ground colour changed. A soft rectangle the width of the viewport has no
   middle to notice. It also has no radius to retune every time a link is added,
   which is what the four paragraphs of arithmetic deleted with this rule were
   for.

   The colour is LOOKED UP rather than guessed. A flat wash in --bone is wrong
   on sight — the page has four cream grounds plus two ink ones, so a wash in
   any single colour is a visible rectangle on the other five. So every section
   declares its own `--ground`, the engine copies whichever is under the chrome
   band to --chrome-ground on <body>, and the band fades between them. Sections
   do not know the chrome exists and the chrome does not know what the sections
   look like.

   The fallback is TRANSPARENT, not --bone. The band exists to cover copy that
   scrolls UNDER the chrome, and copy only scrolls once the engine is running —
   the same moment it sets --chrome-ground. Before the first tick, or with JS
   off, there is nothing to cover and the band must not paint at all; a --bone
   fallback instead flashes a light rectangle onto the ink hero.

   The feather is a MASK, not alpha stops in the gradient, so the colour is
   stated exactly once and the shape of the fade is a separate decision from
   what colour it fades.

   THE GEOMETRY. The mark sits at top:26px and the wordmark is at most 68px
   wide on a 2820x1620 drawing, so it ends at 26 + 68x0.574 = 65px; the nav's
   caps sit around 39-49px. Solid to 58% of 120px is 70px, which clears the
   lower of the two by 5px, and the remaining 50px of feather is longer than
   the old mark ellipse's entire fade. z-index 70 is under the mark and the nav
   (both 80) and over the page. */
body::before{content:"";position:fixed;inset:0 0 auto 0;height:120px;z-index:70;
   pointer-events:none;background:var(--chrome-ground,transparent);
   -webkit-mask-image:linear-gradient(to bottom,#000 58%,transparent 100%);
   mask-image:linear-gradient(to bottom,#000 58%,transparent 100%);
   transition:background .45s ease}

.mark img{width:clamp(52px,4.4vw,68px);height:auto;mix-blend-mode:multiply;
   transition:filter .6s,opacity .6s}
/* on ink grounds the ink wordmark would vanish — flip it to cream */
.dark .mark img{mix-blend-mode:screen;filter:invert(1) brightness(1.6)}
.mark:focus-visible{outline:2px solid var(--oxblood);outline-offset:6px}
.dark .mark:focus-visible{outline-color:var(--gold)}

/* ---------------- header links ----------------
   Inset to var(--pad) so they mirror the wordmark on the left; the 36px flex
   height matches the mark's box so both sit on one axis. */
.topnav{position:fixed;top:26px;right:var(--pad);z-index:80;
   height:36px;display:flex;align-items:center;gap:clamp(.9rem,1.8vw,1.7rem)}

/* THE INK IS 10.5px; THE TARGET IS 44px. Two different measurements, and only
   the first is a design decision. Padding grows the box without touching the
   type; the negative margin keeps the row's optical alignment where it was, so
   nothing moves on screen.

   FULL-STRENGTH INK, not --ink-45. That token exists to hold small mono back
   from the copy it sits beside, and the chrome has no copy beside it — it sits
   in open margin over whatever the page happens to be doing, with nothing to
   defer to. The weight the alpha was buying is bought instead by the size, the
   letterspacing and the caps. Kept as a variable so the two places the chrome
   states its ink cannot drift apart. */
.topnav{--chrome-ink:var(--ink)}
.topnav a{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--chrome-ink);text-decoration:none;transition:color .4s;
   display:flex;align-items:center;min-height:44px;padding:0 .3rem;margin:0 -.3rem;
   white-space:nowrap}
.topnav a:hover{color:var(--oxblood)}
.topnav a:focus-visible{outline:2px solid var(--oxblood);outline-offset:4px}
.dark .topnav a{color:#efe7d7d8}
.dark .topnav a:hover{color:var(--gold)}
.dark .topnav a:focus-visible{outline-color:var(--gold)}

/* ---------------- back to where you started ----------------
   markup: page.eta appends one to every static nav section; #visit carries its
   own inside the plate.

   The brief asks for a way back to the top from every room, and the reason is
   structural: the header links jump DOWN into a long scroll, and the only route
   back is a 52px line drawing in a corner that does not read as a control to
   everyone.

   It is set as the quietest thing in each section — a rule, a small caps label,
   and an arrow that moves 3px on hover. Loud enough to find when you go looking
   for it, quiet enough that five of them down one page are not five buttons.

   The rule ABOVE it rather than a box around it: this is the end of a section,
   so what it needs is a line under the content, not a control floating in
   space. */
.backtop{display:flex;align-items:center;justify-content:flex-end;gap:.55em;
   min-height:44px;margin:clamp(3.4rem,6vw,5rem) var(--pad-r) 0 var(--pad);
   padding-top:1.1rem;border-top:1px solid var(--rule);
   font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--ink-45);text-decoration:none;transition:color .35s}
.backtop::before{content:"\2191";display:inline-block;transition:transform .35s ease}
.backtop:hover{color:var(--oxblood)}
.backtop:hover::before{transform:translateY(-3px)}
.backtop:focus-visible{outline:2px solid var(--oxblood);outline-offset:3px;border-radius:2px}
.ink .backtop{border-top-color:#efe7d73d;color:#efe7d7c4}
.ink .backtop:hover{color:var(--gold)}


/* ==== base/chapter.css ==== */
/* ==========================================================================
   C · CHAPTER SHELL — the sticky-stage mechanic every chapter shares
   markup: page.eta (the <section> wrapper is written there, from the registry)

   A chapter with a `height` in the registry gets a runway that tall and one
   screen pinned inside it. Scroll through the runway, the pinned screen stays,
   and --p runs 0→1 across it. That is the only animation primitive on the site;
   every section's motion is a different reading of the same number.
   ========================================================================== */
.chapter{position:relative}
.sticky{position:sticky;top:0;height:100vh;overflow:hidden;display:flex;align-items:center}
.stage{position:absolute;inset:0;z-index:1;pointer-events:none}
/* The generic copy shell uses the viewport/stage insets. Ordinary narrative
   opts into .editorial so it shares the capped frame while cards, captions and
   centred compositions keep their authored geometry. */
.copy{position:relative;z-index:20;width:100%;padding:0 var(--pad-r) 0 var(--pad)}
.copy.editorial{padding-inline:var(--frame-start) var(--frame-end)}
.copy.center{text-align:center;padding-inline:var(--pad) var(--pad-r)}
.copy.center .body{margin-inline:auto}

/* placement modifiers — so the frame moves chapter to chapter, not just the
   mechanic. Without these every section is the same shot at a different speed. */
.copy.high{align-self:flex-start;padding-top:13vh}
.copy.low{align-self:flex-end;padding-bottom:14vh}
.copy.right{text-align:right}
.copy.right .body{margin-left:auto}
/* caption: type recedes to a label so the stage owns the frame */
.copy.caption{align-self:flex-end;padding-bottom:12vh;text-align:center}
.copy.caption .body{margin-inline:auto}
.copy.caption h2{font-size:clamp(1.8rem,3.6vw,2.7rem)}
.copy.caption .sub{font-size:clamp(.95rem,1.3vw,1.1rem)}

/* ---- the layer system ----
   .layer  = pointer parallax (depth --d, in px per unit of pointer travel)
   .motion = scroll/effect, written per section
   .anchor = centring, so an element that centres itself does NOT also carry
             .layer's transform. That collision is real: an ID rule's
             translate(-50%,-50%) silently wins and kills the depth. #collection
             .obj / .pmot is the same split, which is why the collage reads. */
.layer{position:absolute;will-change:transform;
  transform:translate3d(calc(var(--mx)*var(--d,10)*1px), calc(var(--my)*var(--d,10)*1px), 0)}
.anchor{position:absolute;left:var(--ax,50%);top:var(--ay,50%);width:var(--aw,auto);
  transform:translate(-50%,-50%)}
.anchor > .layer{position:relative;left:auto;top:auto;right:auto;bottom:auto;width:100%}
.drift{animation:drift var(--dur,18s) ease-in-out infinite}
@keyframes drift{
  0%,100%{transform:translateY(-3px) rotate(var(--rot,0deg))}
  50%{transform:translateY(3px) rotate(0deg)}
}


/* ==== base/type.css ==== */
/* ==========================================================================
   D · SHARED TYPE
   The classes every section is allowed to use without declaring anything. A
   section may re-scale these but should not re-invent them.
   ========================================================================== */
.kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.32em;text-transform:uppercase;
   color:var(--oxblood);margin-bottom:1.1rem}
.kicker .n{color:var(--ink-45)}

/* ---- .ink, NOT .dark ------------------------------------------------------
   THE INVERTED TYPE IN THIS FILE HANGS OFF `.ink`, WHICH THE ENGINE WRITES ON
   EACH CHAPTER, NOT OFF `.dark`, WHICH IT WRITES ON <body>.

   They are not the same question. body.dark means "the section under the fixed
   chrome is ink" — it exists for the wordmark and the header links, which float
   over whatever is at the top of the screen. A section's own heading is not
   floating over anything; it is inside one section and should follow that
   section.

   Reading body.dark here is a bug only a phone can show. On desktop the dark
   sections are full-screen, so the viewport owner IS the only section on screen
   and the two agree. At 390px they stop agreeing: sections are as tall as their
   content and two share the screen, so when the ink #footer takes the viewport
   centre the bone #visit plate is still half visible — and every rule below
   would fire on it. Cream headings on bone effectively disappear.
   -------------------------------------------------------------------------- */
.ink .kicker .n{color:#efe7d7c4}
/* THE KICKER'S OWN COLOUR INVERTS TOO, and it is this file's job rather than
   each dark section's. Gold, not cream: it is the accent the dark half of the
   page uses everywhere else, and it holds 9.3–10.8:1 on ink where oxblood sits
   at 3.1:1 and fails AA outright. A local override is still allowed — this is
   the floor, not a ceiling. */
.ink .kicker{color:var(--gold)}

/* Section titles are the display serif (§B). font-weight is 400 because that is
   the only weight that exists — see the warning in fonts.css.

   THE THREE VALUES BELOW ARE ONE DECISION, NOT THREE. Titles are set in caps,
   and caps are not lowercase with the shift key held — the letterforms sit in a
   different box, so tracking and leading both have to move with the transform.
   Change one of these and re-check the other two.

   letter-spacing is POSITIVE (+.015em) where mixed case wanted negative. Caps
   have no ascenders or descenders to interlock and no round counters to close,
   so the sidebearings a lowercase fit relies on aren't there; at 6rem the
   flat-sided letters (H·I·L·M·N) butt together without a little air.

   line-height TIGHTENS to .84. Nothing reaches below the baseline in caps and
   nothing above the cap line, so the ink occupies about 72% of the box mixed
   case filled — .95 would open a visible gap between the two lines of every
   title that carries a <br>. */
h1,h2{font-family:var(--display);font-weight:400;text-transform:uppercase;
   line-height:.84;letter-spacing:.015em}
h1{font-size:clamp(3rem,9vw,7.5rem)}
h2{font-size:clamp(2.6rem,7.5vw,6rem)}

.sub{font-style:italic;color:var(--ink-60);font-size:clamp(1.02rem,1.7vw,1.35rem);margin-top:.7rem}
.body{max-width:32ch;margin-top:1.4rem;font-size:1.06rem;line-height:1.62;color:#1b2436c0}
.tag{margin-top:1.1rem;font-family:var(--mono);font-size:10px;letter-spacing:.06em;
   color:var(--ink-45);text-transform:uppercase}

/* An unanswered fact, marked as one. Not a design element — these are meant to
   be conspicuous until someone fills them in. On this site it does double duty:
   §5 and §6 use it for a link whose URL does not exist yet, where the point is
   that the row reads as "coming" rather than as a dead <a>. */
.tk{font-family:var(--mono);font-size:.86em;letter-spacing:.08em;
    text-transform:uppercase;color:var(--oxblood);
    border-bottom:1px dashed currentColor;padding-bottom:1px}
.ink .tk{color:var(--gold)}

/* A hyphenated PROPER NOUN, held on one line. A plain U+002D is a break
   opportunity, and no CSS property refuses it — hyphens, word-break and
   overflow-wrap were each measured against "Over-the-Rhine" at 390px and all
   six values broke it identically, because none of them governs an explicit
   hyphen. Only marking the span does.

   Scoped to a class rather than applied to every hyphen on the page, because
   most of them SHOULD break: "sixteen-foot ceilings" and "twenty-eight rooms"
   are compounds in running prose and refusing them would cost a ragged line to
   fix nothing. A neighbourhood's name is not a compound. */
.nb{white-space:nowrap}

.ink h1,.ink h2{color:#f2ecdd}
.ink .sub{color:#efe7d7aa}
.ink .body{color:#efe7d7bb}
.ink .tag{color:#efe7d7c4}

.cue{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);z-index:30;
     font-family:var(--mono);font-size:10.5px;letter-spacing:.28em;color:var(--ink-45);
     text-transform:uppercase}
.ink .cue{color:#efe7d7c4}

/* ---- for screen readers only ----------------------------------------------
   Not `display:none` and not `visibility:hidden` — both remove the element from
   the accessibility tree, which is the one thing this class exists to avoid.
   The 1px clip is the technique that leaves the text readable to assistive
   software while occupying no visual space.

   Used once, in §2: the star-chart figure is three separate PNGs arranged into
   one picture, and three alt texts would announce three unrelated images. One
   description of the whole thing, carried here. */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
   overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}


/* ==== sections/hero.css ==== */
/* ==========================================================================
   §1 · PROLOGUE — the building dissolves under the wordmark
   markup: sections/hero.eta   ·   copy: 01-hero.json

   ONE screen, not two. The Germania sits low and scroll DRIVES it: it swells
   and lifts as it fades out from under the wordmark.
   ========================================================================== */
/* the ground the fixed chrome borrows while this chapter owns the viewport —
   see the note in base/chrome.css. The hero paints no background of its own,
   so this states what body's --bone already puts behind it. */
#hero{--ground:var(--bone)}
/* ---- THE BUILDING ------------------------------------------------------
   THE BRIEF'S FIRST LINE IS "Large striking background image of building or
   pristine interior w/ overprinted logo", and for three passes this section did
   not have one. It carried the test site's germ: the same drawing at 13%
   opacity, sized to 74vw, deliberately a whisper. That is right for v0.3, where
   the hero is the prologue to an eleven-chapter scroll and must not compete
   with what follows. It is wrong here, where HOME is a page of its own and the
   building is the only picture on it.

   So the drawing keeps v0.3's placement and gets its weight back. Sized to the
   frame rather than to the copy, and printed at a strength that reads as the
   image of the page rather than as texture behind it.

   .34 IS THE CEILING AND IT IS A CONTRAST MEASUREMENT. The wordmark and the
   tagline are ink on bone and they cross the drawing's darkest passages — the
   cornice and the window reveals. Sampled at 1440x900 through the multiply, ink
   type over the heaviest part of the facade holds 7.1:1 at .34 and 4.2:1 at
   .50, which fails AA on the one screen every visitor sees. The drawing is
   allowed to be the loudest thing in the frame everywhere the type is not.

   IT STILL RECEDES ON SCROLL rather than deepening, which is the opposite of
   what the woodcut hero did and is right for this one: the copy here scrolls
   away with the page, and a building that got heavier as the type left would
   end the screen as a dark mass with nothing on it. */
#hero .germ{left:50%;top:100%;will-change:transform;
   transform:translate(-50%,-58%) translate3d(calc(var(--mx,0)*var(--d,6)*1px),calc(var(--my,0)*var(--d,6)*1px),0);
   width:min(92vw,1180px);opacity:calc(.18 - var(--p)*.06)}
#hero .germ img{width:100%;display:block;mix-blend-mode:multiply}
#hero .motion-germ{transform:scale(calc(1 + var(--p)*.08)) translateY(calc(var(--p)*-2vh))}
#hero .copy{max-width:none}

/* ---- WHERE THE BUILDING SITS, AND WHY THE COPY IS NOT ON IT -------------
   THE TYPE MOVED, NOT THE PICTURE. Every other attempt at this screen tried to
   make small type legible ON the facade, and each one cost the facade:

     germ .34, no help        oxblood 3.31:1  ink-55 3.51:1   both fail
     a bone wash behind the copy   all pass — and a pale blob across the middle
                                   of the frame where the eye goes first
     mono lines to full ink        those two pass, oxblood still fails
     germ eased to .22             oxblood 4.30   still short
     germ eased to .12             all pass — and .12 is v0.3's whisper, which
                                   is not "a large striking background image"

   Six measurements saying the same thing: a 10.5px mono line and a woodcut
   facade cannot share the same square inch. So they no longer do. The copy
   block sits high, directly under the wordmark, on clean paper; the building is
   anchored low and fills the bottom of the frame at full strength.

   Measured after the move: every line on this screen is on bone or within a
   few levels of it, ink-55 at 12.4:1 and oxblood at 6.6:1, and the drawing runs
   at .34 with nothing painted over it. */
/* CENTRED, with a little weight taken off the top. The stack is tall — a 287px
   wordmark, the muse, four lines and the phase row — so `align-items:center` on
   the stage alone left it reading high; this pads the top so the optical centre
   lands where the geometric one is. It was `align-self:flex-start` while the
   copy was being kept off the facade, which is what made it sit high enough to
   notice. */
#hero .copy{padding-top:clamp(5vh,7.5vh,10vh)}

/* The phase row closes the stack, so its air is what decides whether the block
   clears the cue. At v0.3's 2.6rem the row's foot landed 6px above "SCROLL". */
#hero .phasing{margin-top:clamp(1.4rem,2.4vw,1.9rem)}

/* NO MASK, NO WASH, NO GRADIENT OF ANY KIND ON THIS SECTION. Two were tried and
   both were the same mistake wearing different clothes: a bone ellipse painted
   behind the copy, then a mask fading the drawing through the band where the
   small type sits. The second is subtler and it is still a gradient across the
   hero, which is the thing that was wrong with the first.

   The drawing is one flat strength everywhere instead, and the number is set by
   the type on top of it. Measured at 1440x900, ground sampled under the copy
   with the type hidden:

     .34   ink-55 3.51:1   oxblood 3.31:1
     .26   ink-55 3.86     oxblood 3.94
     .22   ink-55 4.08     oxblood 4.30
     .18   ink-55 4.25     oxblood 4.69   <-
     .15   ink-55 4.43     oxblood 4.98
     .12   ink-55 4.57     oxblood 5.30

   .18 is the strongest the building can be while OXBLOOD still clears AA, and
   oxblood is not negotiable here — it is the "close enough to start counting"
   line and the word "Bar" in the phase row, which are the point of the screen.
   --ink-55 does not clear it at that strength, so the two lines that use it are
   set in full ink in this section only (8.5:1). tokens.css forbids reaching for
   a LIGHTER alpha for text; this goes the other way.

   The cost is honest and it is on the picture: the building is quieter than it
   was at .34. It is still forty percent stronger than the test site's .13, it
   is uniform, and there is no edge anywhere on the paper. */
#hero .hero-where{color:var(--ink)}
#hero .phasing .ph{color:var(--ink)}
#hero .germ{left:50%;top:100%;will-change:transform;
   transform:translate(-50%,-58%) translate3d(calc(var(--mx,0)*var(--d,6)*1px),calc(var(--my,0)*var(--d,6)*1px),0);
   width:min(92vw,1180px);opacity:calc(.18 - var(--p)*.06)}
#hero .germ img{width:100%;display:block;mix-blend-mode:multiply}
#hero .motion-germ{transform:scale(calc(1 + var(--p)*.08)) translateY(calc(var(--p)*-2vh))}
#hero .copy{max-width:none}

/* ---- HOW STRONG THE BUILDING CAN BE ------------------------------------
   .22, AND THE NUMBER IS SET BY THE TYPE ON TOP OF IT rather than by taste.

   Three things were tried before this. At .34 the drawing is at its best and
   the ground under the copy measures rgb(167,165,160): full ink clears AA there
   at 6.31:1, but --ink-55 lands at 3.36 and OXBLOOD at 3.31, and oxblood is not
   negotiable — it is the "not open yet" line and the word "Bar" in the phase
   row, both of which are the point of the screen.

   A feathered bone wash behind the copy fixed every number and put a pale blob
   across the middle of the frame, erasing the facade exactly where the eye goes
   first. The picture was paying for the type. Darkening the two mono lines to
   full ink fixed those two and left oxblood failing.

   So the drawing gives way instead, evenly, everywhere — which is the one
   version with no seam in it. .22 puts the ground at roughly rgb(197,193,186)
   and every colour on this screen back over 4.5:1. It is still two-thirds
   darker than v0.3's whisper and still reads as the image of the page. */

/* The h1 is a wrapper for the drawing and nothing else — §D's display clamp,
   caps transform and tracking would all apply to an empty box and only serve to
   add a line of leading above the wordmark. */
.wordmark-h1{font-size:0;line-height:0;letter-spacing:0;margin:0}
/* A whisper of COUNTER-drift on the title, opposite the building and a third
   its distance: the germ (--d 6) drifts one way, the wordmark 2px the other, so
   the two separate in depth under the pointer instead of sliding as one flat
   plane. Small on purpose — the title is the letterform and must stay crisp. */
.wordmark{width:min(52vw,500px);margin:0 auto;mix-blend-mode:multiply;display:block;will-change:transform;
   transform:translate3d(calc(var(--mx,0)*-2px),calc(var(--my,0)*-2px),0)}
.wordmark-fig{width:min(18vw,175px);margin:.2rem auto 0;mix-blend-mode:multiply;display:block}

.hero-line{margin-top:1.8rem;font-size:clamp(1.15rem,2.3vw,1.7rem);font-style:italic;color:var(--ink)}
.hero-where{margin-top:.5rem;font-family:var(--mono);font-size:11px;letter-spacing:.24em;
   text-transform:uppercase;color:var(--ink-55)}

/* the three phases, stated up front — bar now, delicatessen soon, rooms later */
.phasing{margin-top:2.6rem;display:inline-flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:0}
.phasing .ph{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
   color:var(--ink-55);padding:0 1.15rem;position:relative}
/* THE SEPARATOR HANGS OFF THE ITEM AFTER IT, NOT THE ONE BEFORE.
   It was ::after on :not(:last-child) — correct on one line, wrong the moment
   the row wraps: at 390px the third phase drops to its own line and the rule
   belonging to the second is left dangling in the white space at the end of the
   line above. A separator drawn on the LEFT of every item except the first
   cannot end a line, because the thing it is attached to starts one. Same three
   marks between the same three items when they fit; nothing trailing when they
   don't. The `left:0` sits on the padding edge, so the gap either side is the
   same 1.15rem it always was. */
.phasing .ph:not(:first-child)::before{content:"";position:absolute;left:0;top:50%;width:1px;height:11px;
   background:var(--rule);transform:translateY(-50%)}
.phasing .ph b{color:var(--ink);font-weight:400;font-family:var(--serif);font-style:normal;
   font-size:1.02rem;letter-spacing:0;text-transform:none;display:block;margin-bottom:.28rem}
.phasing .ph .now{color:var(--oxblood)}

/* ---- ADDED FOR PHASE 1 --------------------------------------------------
   The brief lists the joke as its own line, separate from the address above
   it, so it needs a rule v0.3 had no use for — its hero ran the two together
   into one `where` string. Oxblood rather than the mono grey of .hero-where:
   it is the one line on the page that is a gag, and it should not read as the
   second half of a postal address.

   --oxblood on --bone measures 7.4:1. (Gold, which the dark hero used for this
   line, is 1.9:1 here and is not an option on paper.) */
.hero-sub{margin-top:.75rem;font-family:var(--mono);font-size:10.5px;
   letter-spacing:.22em;text-transform:uppercase;color:var(--oxblood);line-height:1.9}


/* ==== sections/provenance.css ==== */
/* ==========================================================================
   §2 · PROVENANCE
   markup: sections/provenance.eta · copy: 21..26-*.json

   ONE CHAPTER AND ONE EDITORIAL SCREEN — see the banner in the markup. The
   fresco is v0.3's #ceiling whole; the collage and the star plate are v0.3's
   #collection and #sky1877 included and then CROPPED TO FIGURES by the rules at
   the foot of this file.

   Below this comment are six of v0.3's stylesheets concatenated, each with one
   mechanical change: `#ceiling` re-rooted to `#provenance .pv-fresco`,
   `#collection` to `.pv-collage`, `#sky1877` to `.pv-sky`. Values, comments and
   reasoning are the test site's. The editorial layer is at the end, last so it
   wins — stated first it lost silently to six stylesheets concatenated after it.
   ========================================================================== */
#provenance{--ground:var(--bone);background:var(--bone)}

/* ======== from v0.3 sections/ceiling.css ======== */
/* ==========================================================================
   §2.5 · THE CEILING — you look up into the painting
   markup: sections/ceiling.eta   ·   copy: 03-ceiling.json   ·   data: fresco-bands.json

   The one photograph on the site that behaves like a place rather than a
   picture. The section identifies the ceiling and its painter while scrolling
   dollies a camera into the fresco.

   WHAT MAKES IT A DOLLY AND NOT A ZOOM
   The depth in the image is the depth the artist PAINTED — the vortex. So the
   plate is sliced into nine concentric shells about the vortex's eye, each
   scaled about that same eye. Every shell grows as you scroll, but the OUTER
   ones grow more, so they sweep outward past the camera while the deep centre
   barely moves. That differential is the parallax; there are no lateral
   offsets at all.

   Nothing is cut out of the plate. The band alphas are a partition of unity —
   they sum to 1 everywhere — so at zero motion the nine shells recomposite to
   exactly the source photograph. No holes, no inpainting, no ghost sockets.

   THE VIGNETTE IS A SIBLING OF .cscene, NOT A CHILD — deliberately.
   Only .cscene takes the dolly and the pointer tilt. Nesting .cgrade inside it
   would make the vignette inherit those transforms and swim with the ceiling;
   it must stay nailed to the viewport so the frame holds still while the
   ceiling opens through it.
   ========================================================================== */
#provenance .pv-fresco{--ground:#14161a;position:relative;background:#14161a;color:var(--bone)}

/* Overscan AND aspect. Every shell is positioned as a percentage of this box,
   so the box must carry the plate's own 4:3 (1448×1086) or the whole fresco is
   stretched to whatever shape the viewport happens to be. An earlier version
   used inset:-9%, which inherits the viewport ratio — barely visible on a
   laptop, and on a 390×844 phone it smeared the vortex into a vertical column.
   So: cover-fit the plate, then overscan by 18%, because the dolly pulls outer
   edges inward and the corners must never go black.
     width  = max(118vw, 118vh × 4/3)
     height = max(118vh, 118vw × 3/4)
   Centred with the `translate` property, not transform — the tilt writes
   cscene.style.transform every frame and would clobber it. */
#provenance .pv-fresco .cscene{position:absolute;left:50%;top:50%;translate:-50% -50%;
   width:max(118vw,157.334vh);height:max(118vh,88.5vw);z-index:1;
   perspective:1400px;transform-style:preserve-3d}
#provenance .pv-fresco .clayer{position:absolute;will-change:transform,filter;
   transform-origin:50% 50%;pointer-events:none;backface-visibility:hidden}
#provenance .pv-fresco .clayer img{display:block;width:100%;height:100%}

/* vignette + a warm bloom at the eye. Siblings — see the note above. */
#provenance .pv-fresco .cbloom{position:absolute;inset:0;z-index:2;pointer-events:none;
   background:radial-gradient(60% 45% at 46% 46%,rgba(255,225,190,.10),rgba(0,0,0,0) 70%);
   mix-blend-mode:screen}
#provenance .pv-fresco .cgrade{position:absolute;inset:0;z-index:3;pointer-events:none;
   background:
     /* the main vignette — .90 at the corners is the principal lever */
     radial-gradient(115% 88% at 50% 50%,rgba(0,0,0,0) 28%,rgba(6,5,9,.62) 72%,rgba(4,3,7,.90) 100%),
     /* a second, tighter pass so the four corners fall off faster than the edge
        midpoints. That difference is what reads as a lens rather than a fade. */
     radial-gradient(78% 70% at 50% 50%,rgba(0,0,0,0) 55%,rgba(3,2,6,.55) 100%),
     linear-gradient(180deg,rgba(20,14,30,.26),rgba(0,0,0,0) 34%);
   mix-blend-mode:multiply}
/* A FLOOR UNDER THE COPY — AND THE COPY IS TALLER THAN THE FLOOR WAS.
   The bottom gradient alone was transparent above 48% of the frame. The copy
   block starts at 70% (measured: kicker at 70.2%, h2 at 66.7%, .sub at 47.6%,
   all of it in a 900px viewport), so the three largest things in the section
   — including the title — were sitting directly on unmodified fresco. It read
   as fine at any single scroll position and was luck rather than design: the
   ground under one line of that title measured 1.05:1 in one place and 13.9:1
   in another, because the dolly moves cloud through it. The most variable
   surface on the site was carrying the most important type on it.

   So there are two layers now, doing two different jobs:

   1 · THE POOL is the copy's own ground — an ellipse struck at the
       bottom-left where the copy actually lives, tall enough to reach over the
       kicker and feathered to nothing by 88% so it never shows an edge. It is
       off-centre on purpose: the copy column is left-aligned and the vortex's
       eye is right of centre, so the shadow falls where the words are and
       leaves the painting's subject alone.

   2 · THE FLOOR is the original full-width bottom wash, kept for .hand and
       .cue, which sit outside the pool's reach at the foot of the frame.

   Both in the same layered `background` rather than a second element: this is
   already a sibling of .cscene for the reason at the head of this file, and one
   more absolutely-positioned overlay for a job a comma can do would be a third
   thing to keep nailed to the viewport. */
#provenance .pv-fresco .cfloor{position:absolute;inset:0;z-index:4;pointer-events:none;
   background:
     radial-gradient(66% 60% at 19% 63%,#0b0a09cf 0%,#0b0a09a6 40%,#0b0a0952 70%,transparent 88%),
     linear-gradient(0deg,#0b0a09d9 0%,#0b0a0966 24%,transparent 48%)}

#provenance .pv-fresco .copy{z-index:20}
#provenance .pv-fresco .kicker{color:var(--gold)}
#provenance .pv-fresco .kicker .n{color:#efe7d7c4}
#provenance .pv-fresco h2{color:#f2ecdd}
#provenance .pv-fresco .sub{color:#efe7d7aa}
#provenance .pv-fresco .body{color:#efe7d7bb;max-width:34ch}
#provenance .pv-fresco .cue{color:#efe7d7c4}

/* the attribution, set as an engraved caption rather than body copy */
#provenance .pv-fresco .hand{margin-top:1.5rem;font-family:var(--mono);font-size:10px;
   letter-spacing:.16em;text-transform:uppercase;color:#efe7d799;line-height:1.7;
   max-width:26ch}
#provenance .pv-fresco .hand b{color:var(--gold);font-weight:400}


/* ======== from v0.3 sections/intro.css ======== */
/* ==========================================================================
   §2 · WHAT WAS HERE — the state of the building, and the four years since
   markup: sections/intro.eta   ·   copy: 02-intro.json

   Static: no runway, no sticky stage. It is as tall as its own copy, giving the
   account of the work one plain screen between the menu and the moving night
   section.

   The three figures are decoration and carry empty alt. They are not a system,
   they are not named, and nothing on the page says which is which.
   ========================================================================== */
#provenance .pvb-intro{--ground:var(--bone-2);background:var(--bone-2);padding:16vh var(--frame-end) 14vh var(--frame-start);position:relative;overflow:hidden}
#provenance .pvb-intro .row{max-width:var(--content-max);margin:0 auto;display:grid;grid-template-columns:1fr 1fr;
   gap:clamp(2rem,7vw,6rem);align-items:center}
/* The collage leads on the left; the account of the work reads on the right.
   Source order stays copy-first for the stacked layout and for reading order. */
#provenance .pvb-intro .figs{order:-1}
#provenance .pvb-intro .said h2{font-size:clamp(2.4rem,5.2vw,4.4rem)}
#provenance .pvb-intro .said .body{max-width:38ch;font-size:1.1rem;margin-top:1.7rem}
#provenance .pvb-intro .said .body strong{font-weight:500;color:var(--ink)}

#provenance .pvb-intro .figs{position:relative;height:min(60vh,540px)}
/* POINTER PARALLAX WITH REAL DEPTH. The three figures are a collage, so they
   drift at three different rates under the cursor — the nearer they read (by
   z-index), the further they move: m3 is frontmost (z3 → 10px), m1 next
   (z2 → 7px), m2 hindmost (z1 → 4px). That splits a flat collage into layers
   the moment the mouse moves, the same trick the collection uses on its
   twenty-nine objects. The captions and copy do not move. */
#provenance .pvb-intro .figs .muse{position:absolute;mix-blend-mode:multiply;will-change:transform}
#provenance .pvb-intro .figs .m1{left:2%;top:4%;width:46%;z-index:2;
   transform:translate3d(calc(var(--mx,0)*7px),calc(var(--my,0)*7px),0)}
#provenance .pvb-intro .figs .m2{right:4%;top:0;width:40%;z-index:1;opacity:.9;
   transform:translate3d(calc(var(--mx,0)*4px),calc(var(--my,0)*4px),0)}
#provenance .pvb-intro .figs .m3{left:26%;bottom:-2%;width:42%;z-index:3;
   transform:translate3d(calc(var(--mx,0)*10px),calc(var(--my,0)*10px),0)}
#provenance .pvb-intro .figs .muse img{width:100%;height:auto;mix-blend-mode:multiply}

/* These captions overran the figure column when set nowrap — they are allowed
   to wrap and are held to the left half of it. */
#provenance .pvb-intro .carve{position:absolute;font-family:var(--mono);font-size:10px;letter-spacing:.16em;
   line-height:1.5;text-transform:uppercase;color:var(--ink-55);max-width:20ch}
#provenance .pvb-intro .carve b{color:var(--oxblood);font-weight:400}
#provenance .pvb-intro .c1{left:0;top:-1%}
#provenance .pvb-intro .c2{left:0;bottom:0}


/* ======== from v0.3 sections/collection.css ======== */
/* ==========================================================================
   §4 · THE COLLECTION — dense parallax collage field
   markup: sections/collection.eta   ·   copy: 06-collection.json

   Twenty-nine objects, each with its own depth (--d), drift (--r), tilt and
   opacity, laid out in four tiers. .obj does the placing, .pmot does the
   moving — the same split as .anchor / .layer, and for the same reason: an
   element that centres itself must not also carry the parallax transform.
   ========================================================================== */
#provenance .pv-collage{--ground:#ece0c9;background:#ece0c9}
#provenance .pv-collage .field{position:absolute;inset:0;overflow:hidden;opacity:calc(.5 + var(--p)*.5)}
#provenance .pv-collage .obj{position:absolute;left:var(--x);top:var(--y);width:var(--w);
   transform:translate(-50%,-50%);z-index:var(--z)}
#provenance .pv-collage .pmot{will-change:transform;
   transform:translate(calc(var(--mx)*var(--d)*1px),
                        calc(var(--my)*var(--d)*1px + var(--p)*var(--r)*1vh))
             rotate(var(--tilt,0deg))}
#provenance .pv-collage .obj img{width:100%;height:auto;mix-blend-mode:multiply;opacity:var(--o)}
#provenance .pv-collage .scrim{position:absolute;inset:0;z-index:12;pointer-events:none;
   background:radial-gradient(56% 54% at 27% 50%, #ece0c9, #ece0c9b0 40%, transparent 74%)}
#provenance .pv-collage .copy{position:absolute;top:50%;left:0;transform:translateY(-50%);z-index:20}
#provenance .pv-collage h2{font-size:clamp(2.2rem,5.6vw,4.2rem)}

/* The named details. A rule per item and nothing else — the list is meant to
   read as an inventory, not as prose with bullets. No markers: a disc would
   make four sentences look like features. */
#provenance .pv-collage .named{list-style:none;margin-top:1.9rem;max-width:40ch;
   border-top:1px solid var(--rule-2)}
#provenance .pv-collage .named li{padding:.85rem 0;border-bottom:1px solid var(--rule-2);
   font-size:1.02rem;line-height:1.5;color:#1b2436c8}


/* ======== from v0.3 sections/sky1877.css ======== */
/* ==========================================================================
   §7 · 1877 — the brass plate in the floor
   markup: sections/sky1877.eta   ·   copy: 08-sky1877.json   ·   data: plate-1877.json

   THIS IS THE FLOOR. §2.5 is the ceiling. They are the same room in opposite
   directions, separated in the scroll so they never appear on screen together.

   What is underfoot is brass inlaid into parquet, recording the sky over 12th
   & Walnut in 1877. It is metal, not sky: warm, fixed, documentary, and it does
   NOT move — floors don't. The room moves
   across it, and the frame it looks through is the shape of the bar.

   Beside it is the instrument: the same sky right now, computed, never written.
   The plate says what the sky was; the readout says what it is.

   Not a disc. What you get standing in the bar is a RECTANGLE cut out of a sky
   that keeps going, so the star field is full-bleed and the circle's edge is
   never in frame. The room travels once — in, then back out — which is one
   piece of evidence that the sky continues past the frame without the section
   becoming a tour.

   Nothing about the astronomy is touched by any of this. Every position is the
   fitted projection; the travel only moves the window.
   ========================================================================== */

/* No overflow:hidden here — it makes this element a scroll container, which
   silently kills position:sticky on .sticky inside it and parked the whole star
   map off-screen. .sticky already clips its own contents. */
#provenance .pv-sky{--ground:var(--night);position:relative;background:var(--night);color:var(--bone)}

/* the room: the whole frame, not an object centred inside it. It NEVER moves —
   it is the window. Scaling the room itself would shrink the crop rectangle and
   put a hard edge of stars in the middle of the night field; the zoom belongs
   to the sky behind it. */
#provenance .pv-sky .room{position:absolute;inset:0;overflow:hidden}

/* a vignette so the copy always has ground under it, whatever the sky is doing
   behind — the travel drags bright stars across the caption otherwise */
#provenance .pv-sky .vig{position:absolute;inset:0;z-index:3;pointer-events:none;
   background:radial-gradient(120% 90% at 62% 34%,transparent 34%,#0b102099 76%,#0b1020e0 100%),
              linear-gradient(0deg,#0b1020cc 0%,#0b102055 26%,transparent 52%)}

/* ---- the sky itself ----
   One SVG placed by the fitted projection. left/top/width/height are set by
   fitRoom() in js/sections/sky1877.js — the plate overflows the room on every
   side and is clipped by it.

   NO transition. The zoom is driven straight off scroll position every frame,
   so the sky moves exactly as much as the wheel turned and stops exactly when
   you stop. A transition here is what made it lurch: the scroll set a target
   and the sky travelled there on its own clock. */
#provenance .pv-sky .sky{position:absolute;overflow:visible;
   transform:translate3d(var(--dx,0px),var(--dy,0px),0) scale(var(--z,1));
   transform-origin:50% 50%;will-change:transform}

/* ---- the field arrives by magnitude ----
   --r runs 0→1 across the zoom. The brightest stars land first and the faint
   ones fill in behind them, which is the order your eye actually adapts to a
   night sky in. Real magnitudes, from the plate; the cascade is only the order
   of arrival, not the values.

   Radii are set in CSS, not as attributes, so they can track --kz — the
   scale-compensation for the live zoom. Without compensation the transform
   would scale a first-magnitude star 3.3× on the way in and it would stop being
   a star and start being a blob. Fully compensated, nothing changes size at all
   and the zoom reads as a flat spread. --kz splits the difference: apparent
   size grows as the SQUARE ROOT of the zoom, so stars swell about 1.8× while
   the field opens 3.3×. That is the ratio that feels like approach. */
#provenance .pv-sky .sky .sr{fill:#efe7d7;r:calc(.0030px*var(--kz,1));
   opacity:calc(var(--o,.1)*var(--r,0))}
#provenance .pv-sky .sky .st{fill:#efe7d7;opacity:0}
#provenance .pv-sky .sky .st.m0{r:calc(.0115px*var(--kz,1))}
#provenance .pv-sky .sky .st.m1{r:calc(.0082px*var(--kz,1))}
#provenance .pv-sky .sky .st.m2{r:calc(.0060px*var(--kz,1))}
#provenance .pv-sky .sky .st.m3{r:calc(.0042px*var(--kz,1))}
#provenance .pv-sky .sky .st.m4{r:calc(.0028px*var(--kz,1))}
#provenance .pv-sky .sky .st.m0{opacity:calc(.98*clamp(0,(var(--r,0) - .00)*7,1))}
#provenance .pv-sky .sky .st.m1{opacity:calc(.80*clamp(0,(var(--r,0) - .10)*7,1))}
#provenance .pv-sky .sky .st.m2{opacity:calc(.58*clamp(0,(var(--r,0) - .22)*7,1))}
#provenance .pv-sky .sky .st.m3{opacity:calc(.42*clamp(0,(var(--r,0) - .36)*7,1))}
#provenance .pv-sky .sky .st.m4{opacity:calc(.28*clamp(0,(var(--r,0) - .50)*7,1))}
#provenance .pv-sky .sky .m0{filter:drop-shadow(0 0 .012px #efe7d7)}

/* the brightest stars breathe. scale, not opacity — opacity is already carrying
   the magnitude cascade and the two would fight. */
@keyframes skytw{0%,100%{transform:scale(1)}50%{transform:scale(.68)}}
#provenance .pv-sky .sky .st.m0,#provenance .pv-sky .sky .st.m1{
   transform-box:fill-box;transform-origin:50% 50%;
   animation:skytw var(--td,5s) ease-in-out infinite;animation-delay:var(--tw,0s)}

/* depth: near planes swim further under the pointer than far ones. Values are
   in user units (1 = plate radius), scaled by --kz to stay constant.

   NO transition — same mistake as the zoom had. The pointer value is ALREADY
   eased in JS every frame; a .6s CSS transition on top of it is a second
   smoother running on its own clock, chasing a target that moves before it
   arrives. The two never agree, and the disagreement is the jerk. One easing,
   in JS, where the timing is known. */
#provenance .pv-sky .mg{will-change:transform}
#provenance .pv-sky .mg0{transform:translate(calc(var(--mx,0)*.020px*var(--kz,1)),calc(var(--my,0)*.016px*var(--kz,1)))}
#provenance .pv-sky .mg1{transform:translate(calc(var(--mx,0)*.015px*var(--kz,1)),calc(var(--my,0)*.012px*var(--kz,1)))}
#provenance .pv-sky .mg2{transform:translate(calc(var(--mx,0)*.010px*var(--kz,1)),calc(var(--my,0)*.008px*var(--kz,1)))}
#provenance .pv-sky .mg3{transform:translate(calc(var(--mx,0)*.006px*var(--kz,1)),calc(var(--my,0)*.005px*var(--kz,1)))}
#provenance .pv-sky .mg4{transform:translate(calc(var(--mx,0)*.003px*var(--kz,1)),calc(var(--my,0)*.002px*var(--kz,1)))}

/* ---- the figures ----
   Each one draws itself, but NOT on a timer and NOT when something arrives.
   Every figure carries its own threshold --t, and its draw progress is a pure
   function of --r, which is a pure function of scroll:

       drawn = clamp(0, (--r - --t) * 4, 1)

   So the line grows out of its own stars exactly as fast as you scroll,
   reverses if you scroll back, and freezes if you stop. Nothing has a clock of
   its own — that was the jerk.

   Thresholds run OUTSIDE-IN (computed per figure at render time): the
   constellations at the edge of the plate draw while they are still in frame,
   and the centre of the chart is the last thing to resolve, right as you
   arrive on top of it. */
#provenance .pv-sky .sky .cf{--d:clamp(0,(var(--r,0) - var(--t,0))*4,1);cursor:default}
/* a generous invisible target so the hairlines are actually hoverable */
#provenance .pv-sky .sky .cf .hit{fill:none;stroke:transparent;
   stroke-width:calc(.055*var(--kz,1));
   stroke-linecap:round;stroke-linejoin:round;pointer-events:stroke}
#provenance .pv-sky .sky .cf .ln{fill:none;stroke:#e9c46a;
   stroke-width:calc(.0022*var(--kz,1));
   stroke-linecap:round;stroke-linejoin:round;pointer-events:none;
   stroke-dasharray:var(--L,1);
   stroke-dashoffset:calc(var(--L,1)*(1 - var(--d)));
   opacity:calc(.40*var(--d));
   transition:opacity .3s ease,stroke-width .3s ease}
#provenance .pv-sky .sky .cf.hv .ln{
   opacity:calc(.95*var(--d));stroke-width:calc(.0042*var(--kz,1))}

/* Labels are OFF in the wide state and fade in as you come close. At full
   pull-back all sixteen names sit inside one screen height and collide into an
   unreadable mat. They are annotation, not the picture: the chart has to read
   as a chart first. --lab is driven off the zoom (0 wide → 1 close), so the
   names arrive only once there is room for them. Hover still overrides, at any
   zoom, so nothing is unreachable. */
#provenance .pv-sky .sky .cflabel{fill:#e9c46a;font-family:var(--mono);
   font-size:calc(.030px*var(--kz,1));letter-spacing:calc(.006px*var(--kz,1));
   text-transform:uppercase;text-anchor:middle;
   opacity:calc(.55*var(--lab,0)*clamp(0,(var(--d) - .72)*4,1));
   transition:opacity .3s ease;paint-order:stroke;
   stroke:#0b1020;stroke-width:calc(.010px*var(--kz,1))}
#provenance .pv-sky .sky .cf.hv .cflabel{opacity:.98}

/* ---- the copy ----
   1877 is the headline and stays put the whole way. It doesn't animate, shrink
   or swap: it's a heading, and it's the one fixed thing while the sky opens
   behind it. */
/* width:auto and padding:0 UNDO .copy, which this element also carries.
   .copy is `width:100%; padding:0 var(--pad-r) 0 var(--pad)` — written for a
   static block in the flow. On an absolutely positioned box with both `left`
   and `right` set, a definite width over-constrains it: the browser drops
   `right` and the caption becomes a full viewport wide starting at the left
   inset, i.e. hanging one --pad off the right edge. .copy's own padding then
   pushed the text back to exactly the viewport edge, which is why the standing
   line clipped at 390px. It was doing this at every width; the phone is only
   where the column got narrow enough to see it.
   Insets alone position this box. Nothing else should. */
#provenance .pv-sky .cap{position:absolute;left:var(--pad);right:var(--pad-r);bottom:8vh;z-index:5;
   width:auto;padding:0;pointer-events:none;text-shadow:0 2px 40px #0b1020}
/* the same h2 scale as every other section — this is a heading on the page, not
   a poster.

   `1877` is digits, so §D's text-transform passes straight over it — but the
   tracking still has to match, and it now does (+.015em, not the -.01em that was
   here). Instrument Serif's figures are LINING: they are drawn to cap height and
   sit in the same box as the caps every other title is now set in, so 1 and 7
   need the same air between them for this heading to read as one family with
   "GERMANIA." rather than a tighter-set exception. */
#provenance .pv-sky .yr{font-family:var(--display);font-weight:400;
   font-size:clamp(2.6rem,7.5vw,6rem);line-height:1;letter-spacing:.015em;
   margin:0;color:var(--bone);
   text-shadow:0 0 70px #0b1020,0 0 22px #0b1020cc}
/* and the site's .sub, in bone rather than ink */
#provenance .pv-sky .sub{font-family:var(--serif);font-style:italic;
   font-size:clamp(1.02rem,1.7vw,1.35rem);line-height:1.3;
   margin:.7rem 0 0;color:#efe7d7cc}
/* One standing line under the subtitle. It does not swap, fade or queue — no
   readout, no stop counter, no caption changing under you. */
#provenance .pv-sky .stop{margin-top:clamp(1rem,2.4vh,1.9rem);max-width:31rem}
#provenance .pv-sky .stop .ln{margin:0;font-size:1rem;line-height:1.5;color:#efe7d7cc}

/* ---- the instrument ----
   Every value comes from src/lib/astronomy.js at the real current moment; none
   is written by hand and none is placeholder. It sits in the OPPOSITE corner
   from the caption — the caption is the etching's own legend (bottom-left), the
   instrument is the room reading the live sky (top-right, under the header
   links).

   Deliberately small and quiet. This is a plaque on a wall, not a dashboard:
   you should be able to ignore it entirely, and it rewards the one person who
   stops to check whether the numbers are real. They are.

   It fades in with the section's own --p so it doesn't announce itself at the
   boundary — the sky arrives, then the instrument that measures it. */
#provenance .pv-sky .inst{position:absolute;right:var(--pad-r);top:clamp(88px,13vh,132px);
   z-index:6;pointer-events:none;text-align:right;
   font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;
   line-height:1.9;text-transform:uppercase;color:#efe7d780;
   text-shadow:0 2px 30px #0b1020;
   opacity:calc(clamp(0,(var(--p,0) - .06)*6,1))}
#provenance .pv-sky .inst b{font-weight:400;color:#efe7d7c8}
/* the one live line — gold when the real sun is far enough down that the bar
   would be open, oxblood when it isn't. The hours are never written anywhere on
   this site; this is the only thing that states them, and it states them as an
   observation rather than a schedule. */
#provenance .pv-sky .inst .live{color:var(--gold);letter-spacing:.16em}
#provenance .pv-sky .inst .live.shut{color:#c8705c}
/* the plate's own epoch, set apart from the live block — 1877 is the one number
   here that is NOT now, and the rule makes that legible. */
#provenance .pv-sky .inst .plate{margin-bottom:.55rem;padding-bottom:.5rem;
   border-bottom:1px solid #efe7d72e;color:#efe7d766}


/* ======== from v0.3 sections/origin.css ======== */
/* ==========================================================================
   §6 · THE NAME — two columns beside the figure, no stage.
   markup: sections/origin.eta   ·   copy: 02-origin.json

   This section follows the room details, once the reader has met the physical
   evidence of the building's influence. The two names remain separate: The Muse
   is the bar inside the Germania Building.
   ========================================================================== */
/* #f3edde separates the origin section from the denser collection before it and
   the night-ground sky plate after it. Two adjacent sections sharing a ground
   have no seam between them and read as one long section.

   #f3edde is not a new colour: it is one of the four creams the tokens header
   lists as AA-verified against every text token, and it is already the top stop
   of #contact's radial. It is the lightest of the three, which also suits the
   position — the page opens pale and gets progressively deeper.

   It is also the best of them here, measured rather than picked. Max channel
   delta against each neighbour, for every approved cream:

     #f3edde   7 vs hero · 23 vs intro     ← this one
     #ede2ce   9 vs hero ·  7 vs intro     (ties on the worst seam)
     #ece0c9  14 vs hero ·  5 vs intro
     #efe7d7   0 vs hero                   ← IS hero
     #e7dcc7                 0 vs intro    ← IS intro

   7 is the faintest seam on the page but not a new kind of faint: #history |
   #contact already ships at 9, and the four other cream-to-cream seams sit at
   14–23. The two grounds are flat fills, so those numbers are arithmetic, not
   samples — recompute them, don't re-eyeball them.

   If either neighbour moves, re-check this as a PAIR of seams, not one line. */
#provenance .pvb-origin{--ground:#f3edde;background:#f3edde;padding:17vh var(--frame-end) 15vh var(--frame-start);position:relative}
/* The portrait is a COLUMN, not a background. Putting that much black ink under
   the paragraphs would make the copy depend on a scrim; keeping it in the grid
   gives the figure a real place and keeps every line on the section ground.

   The art spans both rows: the arch becomes the visual spine on the left while
   heading and copy stay related on the right. At 1050px responsive.css stacks
   the three named areas before the nested copy grid gets squeezed. */
#provenance .pvb-origin .wrap{max-width:var(--content-max);margin:0 auto;display:grid;
   grid-template-columns:clamp(210px,22vw,300px) minmax(0,1fr);
   grid-template-areas:"art head" "art copy";
   column-gap:clamp(2.5rem,6vw,6rem);align-items:start}
#provenance .pvb-origin .head{grid-area:head}
#provenance .pvb-origin h2{font-size:clamp(2.4rem,5.4vw,4.2rem);margin-bottom:clamp(2.4rem,5vw,3.6rem)}
#provenance .pvb-origin .portrait{grid-area:art;align-self:center;margin:0}
/* The arch figure drifts with the pointer — the section's one woodcut, given
   the same living-under-the-cursor motion the menu relics and the star map
   have, so this section is not dead to the mouse while its neighbours move. The
   two columns of prose stay put; only the figure floats. */
#provenance .pvb-origin .portrait img{display:block;width:100%;height:auto;mix-blend-mode:multiply;opacity:.92;
   will-change:transform;transform:translate3d(calc(var(--mx,0)*7px),calc(var(--my,0)*7px),0)}
#provenance .pvb-origin .cols{grid-area:copy;display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,5vw,4.5rem)}
#provenance .pvb-origin p{font-size:1.06rem;line-height:1.66;color:var(--ink-70);margin-bottom:1.2rem;max-width:46ch}
#provenance .pvb-origin p:last-child{margin-bottom:0}
#provenance .pvb-origin p b{color:var(--ink);font-weight:500}
#provenance .pvb-origin .col:last-child p:last-child{color:var(--ink)}


/* ======== from v0.3 sections/history.css ======== */
/* ==========================================================================
   §8 · THE BUILDING — real history; the one chapter that stops moving
   markup: sections/history.eta   ·   copy: 10-history.json

   Static. Everything before it moves; this one is a page of prose with a
   timeline, and it is last of the story beats because building history is
   background, not the pitch.

   The reader is buried at the foot of the section and rises out of it as you
   scroll deeper into the past. --hp (0..1) is written by the engine. She is
   uncaptioned and stays that way.
   ========================================================================== */
#provenance .pvb-history{--ground:var(--bone);background:var(--bone);padding:18vh var(--frame-end) 14vh var(--frame-start);position:relative;overflow:hidden}

#provenance .pvb-history .buried{position:absolute;left:50%;bottom:0;width:clamp(200px,22vw,330px);z-index:0;
   pointer-events:none;opacity:calc(var(--hp,0)*.22);
   transform:translate(-50%,calc(26% - var(--hp,0)*15%))}
#provenance .pvb-history .buried img{width:100%;height:auto;mix-blend-mode:multiply}

#provenance .pvb-history .intro,#provenance .pvb-history .building,#provenance .pvb-history .time,#provenance .pvb-history .src{position:relative;z-index:1}
#provenance .pvb-history .intro{max-width:var(--content-max);margin:0 auto 10vh;display:grid;grid-template-columns:1fr 1fr;
   gap:clamp(2rem,7vw,6rem);align-items:end}
/* All three clamp terms come down for caps (was 2.6rem/6vw/5rem). This is the
   only heading on the page living in a half-width grid column — 542px at 1440 —
   and "HAPPENED HERE FIRST." in caps is ~15% wider than the mixed case it
   replaced, so every term was overflowing that column and wrapping the authored
   two lines into three or four.

   Every term is solved against the box the h2 actually gets, not guessed and not
   measured against the grid column — the column is wider than the heading, since
   .lede and the gap take their share. The widest size that fits was measured at
   eight viewports from 901 to 1600: it tracks the viewport linearly at
   3.77–3.96vw, so 3.7vw holds everywhere, with 3.8rem as the ceiling it reaches
   around 1640px.

   The 2.1rem floor is set by the WORST case, 901px — one pixel above the
   breakpoint, where the two-column grid still applies and the heading box is at
   its narrowest 301px. 2.3rem overflowed there by 19px. Below 900 .intro
   collapses to a single column, the box roughly doubles and the floor has slack
   again, so this number is protecting a 1px-wide window: don't "simplify" it
   upward without re-measuring at exactly 901.

   NOTE the 6vw term was ALREADY overflowing this column before the switch to
   caps — mixed case wrapped to three lines at 1024–1200 too. Caps made it worse
   and this fixes both. */
#provenance .pvb-history .intro h2{font-size:clamp(2.1rem,3.7vw,3.8rem)}
#provenance .pvb-history .intro .lede{font-size:clamp(1.1rem,1.7vw,1.35rem);line-height:1.55;color:var(--ink-70)}
#provenance .pvb-history .intro .lede b{color:var(--ink);font-weight:500}

#provenance .pvb-history .building{max-width:var(--content-max);margin:0 auto 10vh;text-align:center}
/* This is the full façade woodcut, 3:2 rather than the portrait crop this rule
   used to size. Its height at 960px is almost the same as the old 560px portrait,
   so the chapter keeps its rhythm while the drawing gets the width it needs. */
/* `height:auto` IS NOT DECORATION HERE. The markup now carries width="4614"
   height="3069" so the browser can reserve the box before the lazy SVG loads
   (see the note in history.eta). Those attributes are also a presentational
   hint, so without this the drawing would render 3069px tall — the pair only
   works as a ratio when CSS sets one dimension and yields the other. */
/* PARALLAX ON A SECTION THAT CANNOT PIN. This chapter is deliberately static —
   it is a three-screen timeline and there is no 100vh stage to hold it. That is
   why it read as the one dead stretch after the long run of pinned set-pieces:
   nothing here moved except the buried reader. The fix is not to force a pin
   (the runway note in sections.js explains why a pin that isn't paid for makes
   the whole page feel cheaper) — it is to give the section depth AS IT SCROLLS.
   The engine already writes --hp here (0 as the top enters, 1 as the foot
   passes the top — see history.js); the façade rides it, drifting up and
   swelling a touch as you read deeper, the same motion language as the reader
   surfacing beneath it. Pointer parallax is on the FIGURE, not the img, so the
   two transforms never collide (the .layer/.motion split in chapter.css, done
   with the elements this section already has). Transform-only: no reflow, so
   the box the width/height pair reserves above is untouched. */
#provenance .pvb-history .building{transform:translate3d(calc(var(--mx,0)*5px),calc(var(--my,0)*4px),0)}
#provenance .pvb-history .building img{width:min(100%,960px);height:auto;margin:0 auto;mix-blend-mode:multiply;
   will-change:transform;
   transform:translateY(calc(var(--hp,0)*-58px)) scale(calc(1 + var(--hp,0)*.04))}
#provenance .pvb-history .building figcaption{margin-top:1.4rem;font-family:var(--mono);font-size:10.5px;
   letter-spacing:.12em;text-transform:uppercase;color:var(--ink-40)}

#provenance .pvb-history .time{max-width:820px;margin:0 auto;position:relative}
#provenance .pvb-history .time::before{content:"";position:absolute;left:0;top:.5rem;bottom:.5rem;width:1px;background:var(--rule)}
#provenance .pvb-history .ev{position:relative;padding:0 0 clamp(2.6rem,5vw,4rem) clamp(2.2rem,5vw,3.4rem)}
#provenance .pvb-history .ev::before{content:"";position:absolute;left:-3.5px;top:.55rem;width:8px;height:8px;
   border-radius:50%;background:var(--oxblood)}
#provenance .pvb-history .ev:last-child{padding-bottom:0}
#provenance .pvb-history .ev .yr{font-family:var(--mono);font-size:11px;letter-spacing:.2em;color:var(--oxblood);margin-bottom:.7rem}
#provenance .pvb-history .ev h3{font-size:clamp(1.35rem,2.6vw,1.9rem);margin-bottom:.7rem;font-weight:500;line-height:1.15}
#provenance .pvb-history .ev p{font-size:1.04rem;line-height:1.64;color:var(--ink-70);max-width:62ch}
#provenance .pvb-history .ev p em{font-style:italic;color:var(--ink)}
/* One line of verse in the timeline. Set small and italic, uncredited, no
   explanation offered. */
#provenance .pvb-history .ev .verse{margin-top:1.1rem;font-style:italic;font-size:1.02rem;
   color:var(--ink-60);max-width:62ch}
/* the figure in the music beat — uncaptioned, off to the side, small */
#provenance .pvb-history .ev.has-fig{padding-right:clamp(0px,16vw,210px)}
/* Faint pointer parallax so the timeline figures float a little under the
   cursor — the same living-under-the-pointer quality the menu relics and the
   collection have, and the second half of giving this static section depth.
   They drift further than the façade (8px vs 5) because they are smaller and
   read as nearer. #provenance .pvb-history has overflow:hidden, so any drift past the edge
   clips rather than adding a scrollbar. */
#provenance .pvb-history .ev-fig{position:absolute;right:0;top:-1.5rem;width:clamp(110px,14vw,190px);
   opacity:.5;pointer-events:none;will-change:transform;
   transform:translate3d(calc(var(--mx,0)*8px),calc(var(--my,0)*8px),0)}
#provenance .pvb-history .ev-fig img{width:100%;height:auto;mix-blend-mode:multiply}

#provenance .pvb-history .src{max-width:820px;margin:5vh auto 0;padding-left:clamp(2.2rem,5vw,3.4rem);
   font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--ink-40);
   text-transform:uppercase;line-height:1.7}



/* ==========================================================================
   THE EDITORIAL LAYER — last, so it wins
   ========================================================================== */

/* ---- the fresco, the one chapter --------------------------------------- */
#provenance .pv-fresco{position:relative;display:flex;align-items:center;
   height:100vh;overflow:hidden}

/* ---- the screen everything else shares --------------------------------- */
#provenance .pv-body{padding:clamp(7vh,11vh,14vh) var(--pad-r) clamp(7vh,11vh,14vh) var(--pad);
   max-width:var(--content-max);margin-inline:auto}
#provenance .pv-head h2{font-size:clamp(2.2rem,5vw,3.6rem);line-height:1;margin-top:.4rem}

#provenance .pv-story{margin-top:clamp(2rem,4vw,3rem);
   display:grid;grid-template-columns:minmax(0,.3fr) minmax(0,1fr);
   gap:clamp(1.8rem,4vw,4rem);align-items:start}
#provenance .pv-germania{margin:0}
#provenance .pv-germania img{width:100%;height:auto;display:block;mix-blend-mode:multiply}
#provenance .pv-cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
   gap:clamp(1.4rem,3vw,2.6rem)}
#provenance .pv-col p{font-size:1rem;line-height:1.62;color:var(--ink-70);max-width:44ch}
#provenance .pv-col p + p{margin-top:.9rem}
/* The line the section walks toward — the name. */
#provenance .pv-lede{color:var(--ink) !important;font-size:1.08rem !important}
/* The design philosophy, set apart from the story it follows: it answers a
   different question (how the room was made, not who she is). */
#provenance .pv-phil{margin-top:1.4rem !important;padding-top:1.1rem;
   border-top:1px solid var(--rule);font-size:.96rem !important}

/* ---- the two figures ----------------------------------------------------
   THE CROP IS WHAT MAKES A CHAPTER A FIGURE. Both of these are full-screen
   compositions that bring their own furniture — #sky1877 has an instrument, a
   vignette, its own caption and a cue; #collection has a copy column and a cue.
   Inside .pv-crop the drawing is kept and the furniture is hidden, so the
   figure is the test site's own artwork at a size a caption can sit beside. */
#provenance .pv-pair{margin-top:clamp(2.4rem,5vw,3.6rem);
   display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
   gap:clamp(1.8rem,4vw,3.4rem)}
#provenance .pv-fig{margin:0}
#provenance .pv-crop{position:relative;aspect-ratio:5/4;overflow:hidden;
   display:flex;align-items:center}
#provenance .pv-crop .copy,
#provenance .pv-crop .cue,
#provenance .pv-crop .inst,
#provenance .pv-crop .vig{display:none !important}
/* .room is `position:absolute;inset:0` inside a 100vh chapter; in the crop it
   fills the figure instead. */
#provenance .pv-sky .pv-crop{background:var(--night)}
#provenance .pv-collage .pv-crop{background:var(--bone-2)}

#provenance .pv-fig figcaption{margin-top:1rem}
#provenance .pv-fig ul{list-style:none;margin-top:.6rem}
#provenance .pv-fig li{padding:.5rem 0;border-top:1px solid var(--rule);
   font-size:.94rem;line-height:1.45;color:var(--ink-70)}
#provenance .pv-fig li:last-child{border-bottom:1px solid var(--rule)}
#provenance .pv-year{font-family:var(--display);font-size:clamp(1.4rem,2.6vw,2rem);
   line-height:1;color:var(--ink)}
#provenance .pv-year span{font-family:var(--serif);font-style:italic;
   font-size:.62em;color:var(--ink-70)}
#provenance .pv-sky figcaption p{margin-top:.6rem;font-size:.94rem;line-height:1.5;
   color:var(--ink-70)}

/* ---- the building headlines --------------------------------------------
   A ROW, NOT A CHRONOLOGY. v0.3's #history runs six dated entries with bodies
   down a vertical rule and calls itself "The full story", which is a different
   brief from "a very brief story dotted with building headlines". Four dates
   and their titles across one line is the dotting. */
#provenance .pv-heads{list-style:none;margin-top:clamp(2.4rem,5vw,3.4rem);
   padding-top:clamp(1.2rem,2vw,1.6rem);border-top:1px solid var(--ink);
   display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(1rem,2.4vw,2rem)}
#provenance .pv-heads .yr{display:block;font-family:var(--mono);font-size:10.5px;
   letter-spacing:.2em;color:var(--oxblood)}
#provenance .pv-heads .ttl{display:block;margin-top:.45rem;font-family:var(--serif);
   font-size:1rem;line-height:1.3;color:var(--ink)}

/* THE GROUND BELONGS TO THE CROP, NOT THE FIGURE. Re-rooting `#sky1877` to
   `#provenance .pv-sky` moved that chapter's own `background:var(--night)` onto
   the whole <figure>, caption included — so the 1877 caption was printing dark
   ink on a night ground under the plate, and #collection did the same in bone-2.
   The chapters were full-screen and their ground WAS the section; here the
   ground is the picture only.

   Stated after the two crop rules above so it cannot be undone by them, and it
   restores the page's own ground and ink to everything outside the picture. */
#provenance .pv-fig{background:none;color:var(--ink)}
#provenance .pv-sky figcaption .pv-year{color:var(--ink)}
#provenance .pv-sky figcaption p,
#provenance .pv-collage figcaption li{color:var(--ink-70)}

/* THE SCRIM GOES WITH THE CHAPTER. #collection fades its collage out under a
   bottom-up gradient so its copy column has somewhere to sit — right on a
   full screen, and wrong on a 358px figure, where the same gradient covers most
   of the picture and leaves a pale smear where the woodcuts were. The figure's
   caption is underneath it now, on paper, so nothing needs fading. */
#provenance .pv-crop .scrim{display:none !important}


/* ==== sections/glass.css ==== */
/* ==========================================================================
   §5 · THE MENU — bill of fare
   markup: sections/menu.eta   ·   copy: 04-menu.json

   A card that rises into place as the runway scrolls. Two columns, leader dots
   between name and price — the typographic convention for a bill of fare, which
   is doing the work a heading would otherwise have to do.
   ========================================================================== */
#glass{--ground:#e7dcc7;background:#e7dcc7}
/* Desktop placement of the two relics. This used to be an inline style on the
   markup, which meant responsive.css could never move them — see menu.eta. */
#glass .relic:first-child{left:8%;top:20%;width:min(12vw,150px)}
#glass .relic:last-child{right:7%;bottom:16%;width:min(13vw,160px)}
#glass .relic img{width:100%;height:auto;mix-blend-mode:multiply}
/* Same correction as the visit plate: both terms measure the VIEWPORT, but the
   parent .copy is inset by --pad / --pad-r. At 1075px the 80vw term overtook the
   860px cap, the card grew past its container, and the leader-dot prices — the
   right edge of a bill of fare — overran the right inset. `100%` is the term
   that respects it. */
#glass .card{position:relative;z-index:20;width:min(80vw,860px,100%);margin:0 auto;
   transform:translateY(calc((1 - var(--p))*40px));opacity:calc(.62 + var(--p)*.38)}
#glass .head{margin-bottom:2.4rem}
/* The clamp MINIMUM comes down 2.4rem → 2rem for caps, and it is the floor that
   matters here rather than the vw term: .card is min(80vw,860px), so this
   heading's box tracks the viewport, and 6vw only overtakes 2rem above 533px.
   The floor therefore governs the whole 375–533px band, where "AND SOMETHING TO
   EAT." in caps needs 32px — 2.4rem (38.4px) wrapped the authored second line
   into a third. Below 375px responsive.css takes over with a smaller value
   again. Ceiling and vw term unchanged; this heading was never the problem on a
   wide screen. */
#glass h2{font-size:clamp(2rem,6vw,4.4rem)}
#glass .cols{display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,6vw,5rem);text-align:left}
#glass .col h3{font-family:var(--mono);font-size:12px;letter-spacing:.3em;text-transform:uppercase;
   color:var(--oxblood);padding-bottom:.9rem;margin-bottom:1.1rem;border-bottom:1px solid var(--rule-2)}
#glass .item{display:flex;align-items:baseline;gap:.55rem;margin:.85rem 0}
#glass .item .nm{font-size:1.08rem;color:#1b2436d8}
#glass .item .dots{flex:1;border-bottom:1.5px dotted var(--rule-2);transform:translateY(-.28em)}
#glass .item .pr{font-family:var(--mono);font-size:.92rem;color:var(--ink-60);letter-spacing:.02em}

/* --------------------------------------------------------------------------
   THE BILL WRITES ITSELF — scroll reveal, CSS only, no module.

   §5b reveals its rows by dealing them up from below, one after another. This
   section is deliberately NOT that. Here the card is already in place (it rises
   as a whole, above) and what the runway does is DRAW the bill: each column's
   rule runs out from the left, then the leader dots stretch from the name
   toward the price, and the price lands last — the order a hand would write a
   line of a menu in. So both sections stagger, but one moves paper and this one
   moves ink, and the two read as different mechanics rather than one repeated.

   Nothing here is design. No size, weight, colour, grid or spacing is touched:
   every rule below animates a transform or an opacity that finishes at its
   authored value. --rp is the per-item progress, 0 → 1, driven off the stage's
   --p with a per-column head start (--c) and a per-row step (--i), so the left
   column is well down its list before the right one starts.

   The two constants are set against the LONGEST column, not against the count
   the section happens to ship with: at eight rows the left column's last row
   opens at .21 and the whole bill has settled by .57, so there is headroom for
   more drinks without the tail running off the end of the runway. If a column
   ever grows past about twenty rows, drop the .03 step rather than the .24
   column offset — the offset is what makes this read column by column.

   --p is seeded to 0 in tokens.css and pinned to 1 under Reduce Motion, so
   1 is the settled state and first paint is valid. Below 900px the columns
   stack but the chapter stays pinned, so --p is still live and the deal simply
   runs top to bottom — no override needed.
   -------------------------------------------------------------------------- */
#glass .item{--rp:clamp(0, calc((var(--p) - (var(--c,0) * .24 + var(--i,0) * .03)) / .3), 1)}
/* The column rule is drawn rather than faded: the real border goes transparent
   and an identical 1px line is scaled out from the left in its place. At
   --rp 1 the two are pixel-identical, so this changes when the rule appears
   and nothing about what it looks like. */
#glass .col h3{--rp:clamp(0, calc((var(--p) - var(--c,0) * .24) / .26), 1);
   position:relative;border-bottom-color:transparent}
#glass .col h3::after{content:"";position:absolute;left:0;right:0;bottom:-1px;
   border-bottom:1px solid var(--rule-2);
   transform:scaleX(var(--rp));transform-origin:left center}
/* Names ink in from the left. A 6px horizontal nudge, not §5b's vertical
   drop — same family of motion, different axis, which is the whole point. */
#glass .item .nm{opacity:var(--rp);
   transform:translateX(calc((1 - var(--rp)) * -6px))}
/* The leader stretches out of the name. transform-origin is left because the
   dots belong to the dish, not to the price. The translateY is the authored
   baseline correction from the rule above and has to be carried along. */
#glass .item .dots{transform:translateY(-.28em) scaleX(var(--rp));transform-origin:left center}
/* The price arrives once the leader has reached it — a second window that only
   opens over the back half of the row's own reveal. */
#glass .item .pr{opacity:clamp(0, calc((var(--rp) - .55) / .45), 1)}

/* ==========================================================================
   THE LIST IS GONE; THE PICTURE AND THE PARAGRAPH REPLACE IT
   --------------------------------------------------------------------------
   Everything above is v0.3's #menu — the card, the kicker, the display heading,
   the drifting relics — and it is kept. What went is the two-column bill of
   fare, because the brief asks this section to "tease with simple imagery and
   concept" and a two-column list with leaders and DRINKS/FOOD heads reads as a
   menu before anyone reads a word of it. The rules for .cols/.col/.item/.dots
   /.pr are left in place above rather than deleted: they are the full site's
   menu and this section becomes it again at Phase 2.
   ========================================================================== */

/* "APOTHECARY ILLUSTRATION BLENDING WITH PHOTO" — the brief's own words, and
   `multiply` is the whole mechanism. The woodcut's ink takes the fresco's cloud
   texture through it, so the object sits IN the surface rather than on top of
   it: a print rather than a paste-up. A drop shadow, or any opacity below 1 on
   the object, would undo exactly that.

   The photograph is a crop of our own ceiling and is deliberately NOT a picture
   of a drink — we do not have those yet, and a stock one would be the most
   off-brand object on the page. Bleached hard (grayscale, low contrast, lifted
   brightness, .34) so it reads as a SURFACE and not as a second picture
   competing with the first. The 1.5 scale runs the cloud forms off all four
   edges, so there is no composition left in it to read. */
#glass .blend{position:relative;margin:clamp(2rem,4vw,3rem) auto 0;
   width:min(100%,30rem);aspect-ratio:4/3;overflow:hidden}
#glass .blend .bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
   transform:scale(1.5);
   filter:grayscale(1) contrast(.55) brightness(1.2);
   opacity:.34;mix-blend-mode:multiply}
#glass .blend .obj{position:absolute;left:50%;top:50%;height:82%;width:auto;max-width:78%;
   mix-blend-mode:multiply;will-change:transform;
   transform:translate3d(calc(-50% + var(--mx,0) * 12px),calc(-50% + var(--my,0) * 12px),0)}

/* The Expo. One paragraph, centred under the picture at a real measure — the
   brief gives this section exactly one and it carries the whole argument, so it
   is set as body copy rather than as the small print the .tag beneath it is. */
#glass .expo{margin:clamp(1.8rem,3.4vw,2.6rem) auto 0;max-width:52ch;
   font-size:1.06rem;line-height:1.68;color:var(--ink-70);text-align:center}
#glass .tag{margin-top:1.6rem;text-align:center}

/* position:relative because this is a STATIC section now and no longer gets the
   .chapter class (page.eta) — .stage is `position:absolute;inset:0` and would
   otherwise hang off the nearest positioned ancestor, which is the viewport. */
#glass{position:relative;padding:clamp(9vh,13vh,15vh) 0 clamp(8vh,11vh,13vh)}

/* Same correction as §4: these percentages were solved against a 200vh chapter
   and the section is 1.35 screens now. Held to the outer margins, level with
   the figure rather than with a stage that no longer exists. */
#glass .relic:first-child{left:5%;top:30%}
#glass .relic:last-child{right:5%;top:34%;bottom:auto}

/* SIZED BY HEIGHT, NOT WIDTH, and the ice bucket is why. At `width:62%` of a
   4:3 frame a portrait woodcut resolves to something taller than the box: the
   bottle's neck and the bucket's foot were both cut off by the figure's own
   edges, which on a picture whose entire job is to be the one image in the
   section is not a crop, it is a mistake. Height binds now and `max-width`
   catches the landscape case, so either proportion of object fits inside the
   same frame — §4's cherries are wider than they are tall and use the identical
   rule. */


/* ==== sections/table.css ==== */
/* ==========================================================================
   §5b · FOOD FOR THE FINGERS — the hors d'œuvres board
   markup: sections/hors.eta   ·   copy: 05b-hors.json

   Reads as the second page of the bill of fare next door, on a ground half a
   step lighter than §5's so the boundary between them is a page turn rather
   than a seam. #glass is #e7dcc7; this is #efe6d2, which sits between it and
   --bone (#efe7d7) — the same family, decisively not the same field.

   THE SHAPE IS DIFFERENT FROM §5 ON PURPOSE. §5 is a centred card of one-line
   rows: name, leader dots, price. Eight dishes that each need a line of
   description cannot be that card — it would be sixteen lines of alternating
   weight down the middle of the screen. So this is a two-column BOARD, left
   aligned, with the head in its own column on desktop and the plates filling
   the other. Same leader-dot convention on each row, because that convention
   is what says "menu"; everything around it moves.

   No local colour, size or family is invented here. Everything is a token or a
   shared class from base/type.css — .kicker, h2, .body, .tag — with the same
   two departures §5 already makes: the column headings and the prices are the
   mono face at the sizes menu.css uses, so the two sections set numbers
   identically.
   ========================================================================== */
#table{--ground:#efe6d2;background:#efe6d2}

/* ---- the relics ---------------------------------------------------------
   Placed HERE and not inline on the markup. An inline style beats every rule
   short of !important, which is how §5's mobile overrides spent a while being
   dead code that still looked correct in the source — see the note in
   menu.eta. Only --d stays inline there and here.

   Both are pushed well outside the board's own column: at 1440 the board is
   1180px in a viewport whose margins are 8vw, so there is roughly 60px of true
   margin at each side and these sit past it, reading in the corners of the
   frame rather than behind the type. */
#table .relic{width:min(11vw,132px)}
/* BOTH SIT LOW, and the left one is the reason. The head column is the tallest
   thing on the left of this section but it does not fill it: the title, the
   label and four lines of body end around 55% of the stage, leaving the bottom
   half of that column empty. Placed at top:14% the relic was not in the margin
   at all — it was directly behind "FOOD FOR", 50px from the left edge at 1440,
   which is inside the title's own box. The empty half is where it goes. */
#table .r-a{left:4%;bottom:14%}
#table .r-b{right:4%;bottom:11%}
#table .relic img{width:100%;height:auto;mix-blend-mode:multiply;opacity:.5}

/* ---- the board ----------------------------------------------------------
   width uses the same three-term min() as #glass .card, and for the same reason
   documented there: 100% is the term that respects the parent .copy's own
   insets rather than the raw viewport. Drop it and the prices — the right edge
   of a bill of fare — overrun the right inset in the 820–1180px band. */
#table .board{position:relative;z-index:20;width:min(92vw,1180px,100%);margin:0 auto;
   display:grid;grid-template-columns:minmax(0,.78fr) minmax(0,1.22fr);
   gap:clamp(2rem,6vw,5.5rem);align-items:start}

#table .head{align-self:start}
/* The head gives more width to the actual menu than to itself. Its title scale
   was solved against that narrower column at 901, 1180 and 1440px; 4.2vw keeps
   the authored two lines intact without making the left side heavier than the
   eight-item board. The 901–1050 band gets a second, smaller solution in
   responsive.css, where the menu needs still more of the available width. */
#table .head h2{font-size:clamp(2rem,4.2vw,4rem)}

/* The French, under the English title. The display serif's italic is a real
   cut (fonts.css §B), so this is set in it rather than in --serif: the title
   above is the same family and the label reads as its subtitle rather than as
   a different voice. Not .sub — that class is sized for a sentence, and this
   is two words doing the work of a category heading. */
#table .alt{font-family:var(--display);font-style:italic;font-size:clamp(1.25rem,2.1vw,1.8rem);
   color:var(--oxblood);margin-top:.85rem;letter-spacing:.01em}

#table .head .body{max-width:34ch}

/* ---- the plates ---------------------------------------------------------
   An <ol> because the board has an order — it runs from the one-bite pick to
   the thing you eat with a spoon — and a screen reader saying "list, 8 items"
   is the correct description of a menu. list-style is off; the numbering is
   drawn below from the same counter, in the mono face, as a menu number rather
   than as a browser bullet. */
#table .plates{list-style:none;margin:0;padding:0;counter-reset:plate}
/* Eight rows is the one number this section cannot change, so the vertical
   rhythm responds to viewport HEIGHT as well as width. At 900px tall this lands
   at the authored .78rem; on a 760px stage it tightens before either the kicker
   reaches the fixed chrome or the footnote reaches the cue. The short-viewport
   block in responsive.css makes the second, deliberate compression below
   800px. */
#table .plate{counter-increment:plate;position:relative;
   padding:clamp(.4rem,.95vh,.78rem) 0 clamp(.4rem,.95vh,.78rem) 2.6rem;
   border-bottom:1px solid var(--rule)}
#table .plate:first-child{border-top:1px solid var(--rule)}

/* The menu number. --ink-45 is the smallest-type token and clears AA on every
   ground on the page; do not lighten it to make these recede — if they need to
   recede they need to be smaller, not fainter. */
#table .plate::before{content:counter(plate,decimal-leading-zero);position:absolute;
   left:0;top:1rem;font-family:var(--mono);font-size:10px;letter-spacing:.12em;
   color:var(--ink-45)}

/* THE STAGGER. Each row's own progress is the chapter's --p remapped so that
   row 0 finishes first and row 7 last: (p - i*STEP) / SPAN, clamped 0..1. The
   clamp is what makes eight rows deal in sequence out of one number, and it is
   arithmetic in the stylesheet — no JS, no IntersectionObserver, nothing that
   schedules a frame of its own (see the note at the top of main.js).

   --p is 0 until the engine's first tick, and tokens.css seeds it globally for
   exactly that reason. At 0 a row is 18px low at 0 opacity, which is the state
   the stagger starts from — correct on first paint, and correct with the
   engine still loading.

   SPAN .34 and STEP .055 together mean the last row starts at --p .385 and is
   settled by .725, so the whole board has finished dealing well before the
   runway ends and the reader is never scrolling into type that is still
   arriving. Under Reduce Motion the engine pins --p to 1, so every row lands
   at its settled state — the finished board, not an empty one.

   clamp() and not min/max chained: this is one value with a floor and a
   ceiling, and writing it as such is what makes the intent legible. */
#table .plate{--rp:clamp(0, calc((var(--p) - var(--i) * .055) / .34), 1);
   opacity:var(--rp);
   transform:translateY(calc((1 - var(--rp)) * 18px))}

#table .line{display:flex;align-items:baseline;gap:.6rem}
/* Same three values as #glass .item — name, leader, price are set identically
   in both sections, which is what makes them one menu in two parts. */
#table .line .nm{font-size:1.08rem;color:#1b2436d8}
#table .line .dots{flex:1;border-bottom:1.5px dotted var(--rule-2);transform:translateY(-.28em)}
#table .line .pr{font-family:var(--mono);font-size:.92rem;color:var(--ink-60);letter-spacing:.02em}

/* The description. --ink-55 rather than the .body token's #1b2436c0: this is
   set at .95rem, and the type-scale note in tokens.css is explicit that small
   text takes the darker alphas. 4.86:1 on this ground. */
#table .what{margin:.22rem 0 0;font-size:clamp(.85rem,1.55vh,.95rem);line-height:1.45;
   color:var(--ink-55);max-width:52ch}

/* THE FOOTNOTE IS A GRID ITEM, and left to itself auto-placement put it in
   column 1 — under the head, in the empty half of the left column, 700px away
   from the list it annotates and reading as an orphaned caption on the head
   instead. It belongs under the plates: grid-column 2 puts it there, and
   justify-self is not needed because the column is already its width.

   Explicitly placed rather than moved into .plates' own wrapper in the markup,
   because it is a note on the board and not the ninth item in an <ol> of eight
   dishes — the list's semantics are the reason it is an ordered list at all. */
#table .tag{grid-column:2;margin-top:1.3rem;max-width:56ch}

/* ---- proximity ----------------------------------------------------------
   --near is 0..1, written per row by sections/hors.js from the pointer's
   distance to that row's centre. It is a REINFORCEMENT of :hover, not a
   replacement: the row under the cursor gets the full effect from :hover
   below, which needs no JavaScript, and --near only adds the falloff to its
   neighbours. With the module absent or failed, --near is unset, var()'s
   fallback is 0, and the section is a plain hover list — which is the whole
   reason it is written as a fallback rather than as a class the module adds.

   The properties that move are the number's colour and a small horizontal
   nudge. Nothing changes size: eight rows that each grow on approach is a list
   that reflows under the pointer, and reflow-on-hover is a layout thrash the
   reader can feel. transform and color only. */
#table .plate{transition:opacity .3s ease}
#table .plate .line,#table .plate .what{
   transform:translateX(calc(var(--near,0) * 6px));
   transition:transform .35s cubic-bezier(.2,.7,.3,1)}
#table .plate::before{
   color:color-mix(in srgb, var(--oxblood) calc(var(--near,0) * 100%), var(--ink-45));
   transition:color .35s ease}

/* The leader dots brighten toward the pointer — the one element in the row
   whose whole job is to be a texture, so it is the one that can carry the
   emphasis without any word on the page changing weight. --rule-2 → --rule-2
   at full strength is not a change, so this mixes toward the ink token the
   dots already sit beside. Non-text, so 3:1 is the bar and both ends clear it. */
#table .plate .dots{
   border-bottom-color:color-mix(in srgb, var(--oxblood) calc(var(--near,0) * 55%), var(--rule-2));
   transition:border-color .35s ease}

/* :hover is the floor under all of the above and works with JS off. Scoped to
   a fine pointer: on a touch screen :hover latches on the last thing tapped
   and stays there, which would leave one row permanently emphasised. */
@media (hover:hover) and (pointer:fine){
  #table .plate:hover .line,#table .plate:hover .what{transform:translateX(8px)}
  #table .plate:hover::before{color:var(--oxblood)}
  #table .plate:hover .dots{border-bottom-color:var(--oxblood)}
}

/* ==========================================================================
   THE BOARD IS GONE; THE PICTURE AND TWO PARAGRAPHS REPLACE IT
   --------------------------------------------------------------------------
   Same surgery as §3 and the same reason — see the banner at the foot of
   glass.css. The <ol> of plates went with the dishes that were in it; the
   board's frame, the kicker, the display heading and the italic .alt line are
   v0.3's and are kept. The .plate/.line/.nm/.what rules above stay for Phase 2.
   ========================================================================== */

/* The mirror of §3's figure, and DELIBERATELY IDENTICAL in treatment. The brief
   asks for the same thing in both sections ("Apothecary illustration blending
   with photo"), and two different treatments would read as two different ideas
   rather than as one pair. Only the object differs. */
#table .blend{position:relative;margin:clamp(2rem,4vw,3rem) 0 0;
   width:min(100%,28rem);aspect-ratio:4/3;overflow:hidden}
#table .blend .bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
   transform:scale(1.5);
   filter:grayscale(1) contrast(.55) brightness(1.2);
   opacity:.34;mix-blend-mode:multiply}
#table .blend .obj{position:absolute;left:50%;top:50%;height:82%;width:auto;max-width:78%;
   mix-blend-mode:multiply;will-change:transform;
   transform:translate3d(calc(-50% + var(--mx,0) * 12px),calc(-50% + var(--my,0) * 12px),0)}

/* TWO Expos here where §3 gets one — that asymmetry is the brief's, not a
   layout accident, and it is why this section is the wider of the pair. */
#table .expos{margin-top:clamp(1.8rem,3.4vw,2.6rem)}
#table .expo{max-width:52ch;font-size:1.06rem;line-height:1.68;color:var(--ink-70)}
#table .expo + .expo{margin-top:1.1rem}
#table .expos .tag{margin-top:1.8rem}

/* THE BOARD IS STILL TWO COLUMNS — the second one just holds the Expos now
   instead of a list of plates. Stacked in a single left-hand column (the first
   attempt) the section left the entire right half of a 1440px screen empty
   while §3 next door filled its frame edge to edge, so the pair read as one
   finished section and one unfinished one.

   The head spans both, then the picture and the two paragraphs sit side by
   side. THIS IS ALSO THE MIRROR THE PAIR NEEDS: §3 is centred on its axis, §4
   is asymmetric across it, so the two are one idea read twice rather than the
   same slide shown twice — which is what made them work as a pair in v0.3. */
#table .board{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1fr);
   column-gap:clamp(2rem,5vw,4.5rem);row-gap:clamp(1.6rem,3vw,2.4rem);
   align-items:start;max-width:66rem}
#table .head{grid-column:1 / -1;max-width:none}
#table .blend{grid-column:1;margin-top:0;width:100%}
#table .expos{grid-column:2;margin-top:0}

/* Same as §3 — static section, so it needs its own containing block for .stage.
   The top padding is shorter than §3's and that is the pairing expressed in
   space: two sections that belong together sit closer than two that don't. */
#table{position:relative;padding:clamp(7vh,10vh,12vh) 0 clamp(9vh,13vh,15vh)}

/* THE RELICS MOVE UP, because the section is a third of the height it was.
   `bottom:14%` of a 210vh chapter put them in a 30vh band of empty stage; the
   same percentage of a 1.4-screen static section puts them level with the foot
   — the lips landed on top of "↑ BACK TO THE TOP", which page.eta appends to
   every static nav section and which did not exist here when these numbers
   were chosen.

   Anchored to the picture's own band instead: the figure occupies the left
   column between roughly 35% and 80% of the section, and the outer margins
   beside it are the only real gutters this layout has. */
#table .r-a{left:3%;bottom:auto;top:46%}
#table .r-b{right:3%;bottom:auto;top:38%}


/* ==== sections/tidings.css ==== */
/* ==========================================================================
   §5 · TIDINGS — press & announcements
   markup: sections/tidings.eta   ·   copy: 05-tidings.json

   THE PLAINEST SECTION ON THE PAGE, ON PURPOSE. Somebody who arrives here has a
   deadline: they want a link they can paste, the spelling of a name, and a
   phone number. Atmosphere costs them time. So there is no photograph, no
   parallax and no scroll runway — one drawing, two lists and an address.

   It is still the site's type on the site's ground. Plain is not the same as
   unstyled, and a press page that looked like a different website would say
   something untrue about how the rest of it was made.
   ========================================================================== */
#tidings{--ground:#ece0c9;background:#ece0c9;
   padding:clamp(9vh,13vh,15vh) 0 clamp(8vh,11vh,13vh)}

.td{padding-inline:var(--frame-start) var(--frame-end)}

.td-head{max-width:36rem}
.td-head h2{font-size:clamp(2.4rem,5.4vw,4.2rem)}
.td-lede{margin-top:1.3rem;max-width:44ch;font-size:1.06rem;line-height:1.66;
   color:#1b2436c0}

/* The lists take the wider column and the contact block the narrower one: there
   will eventually be a dozen clippings and there will only ever be one press
   officer. 1.55/1 rather than 2/1 so the aside stays a column of running text
   rather than a caption. */
.td-body{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
   gap:clamp(2.4rem,6vw,5.5rem);margin-top:clamp(3rem,6vw,4.5rem);
   align-items:start}

.td-group + .td-group{margin-top:clamp(2.4rem,4vw,3.4rem)}
.td-lbl{font-family:var(--mono);font-weight:400;font-size:9.5px;
   letter-spacing:.26em;text-transform:uppercase;color:var(--oxblood);
   margin-bottom:1.1rem}

.td-list{list-style:none}
.td-item{border-top:1px solid var(--rule)}
.td-item:last-child{border-bottom:1px solid var(--rule)}

/* The whole row is the target, not just the title — a link the width of the
   list is far easier to hit than three words inside it, and the padding is the
   44px minimum without a single explicit height. `display:block` on the anchor
   is what makes the row clickable at all; the <div> in the pending branch takes
   the identical padding so a published item and an unpublished one sit on the
   same rhythm and the list does not jump as links are filled in. */
.td-item > a,
.td-item > div{display:block;padding:1.2rem 0;text-decoration:none;color:inherit}
.td-item > a{transition:background .3s ease,padding-left .3s ease}
/* The hover moves the row 10px right, into the margin the section's own inset
   already provides — an indent rather than a highlight, which is the register
   the rest of this page hovers in. The tint is under 3% so it reads as the row
   lifting rather than as a coloured band. */
.td-item > a:hover{background:#1b243609;padding-left:10px}
.td-item > a:focus-visible{outline:2px solid var(--oxblood);outline-offset:-2px}

.td-outlet{display:block;font-family:var(--mono);font-size:9.5px;
   letter-spacing:.24em;text-transform:uppercase;color:var(--oxblood)}
.td-title{display:block;margin-top:.5rem;
   font-family:var(--display);text-transform:uppercase;font-weight:400;
   font-size:clamp(1.1rem,2.2vw,1.6rem);line-height:1.06;letter-spacing:.02em;
   color:var(--ink)}
.td-meta{display:block;margin-top:.6rem;font-family:var(--mono);font-size:10px;
   letter-spacing:.1em;text-transform:uppercase;color:var(--ink-45)}

/* An item with no URL yet. It is held back rather than hidden — the commitment
   is real and a reporter should be able to see it is coming — so the title
   greys off while the `tk` mark in the meta line carries the actual message.
   The row is NOT an <a> in this state (see the markup): a link that goes
   nowhere is worse than a line of text that says so. */
.td-item.is-pending .td-title{color:var(--ink-55)}

/* ---- the press contact --------------------------------------------------
   A plate rather than a column, because it is the one thing on this page
   somebody needs to find without reading anything first. The ink hairline and
   the section's own paler cream are enough to lift it; a shadow would make it a
   card, and this page has no cards. */
.td-press{border:1.5px solid var(--ink);background:#f3edde;
   padding:clamp(1.6rem,3vw,2.4rem)}

/* The reader, drawn — the one figure in the section, and the only ornament it
   gets. Small, and pushed to the right, so it signs the block rather than
   heading it. */
.td-fig{display:block;width:clamp(64px,9vw,96px);height:auto;margin:0 0 1.4rem auto;
   mix-blend-mode:multiply;opacity:.9}

.td-note{margin-top:.2rem;font-size:.99rem;line-height:1.6;color:var(--ink-70)}

.td-contact{margin-top:1.2rem;font-style:normal;display:flex;flex-direction:column;
   align-items:flex-start;gap:.15rem}
.td-name{font-family:var(--display);text-transform:uppercase;
   font-size:clamp(1.15rem,2.2vw,1.5rem);line-height:1.1;letter-spacing:.03em;
   margin-bottom:.5rem}
/* ONE CONTROL, NOT TWO PRINTED STRINGS. The phone number is off the page on
   instruction and the address is a button rather than the address itself — see
   the note in tidings.eta.

   A BUTTON AND NOT AN UNDERLINED LINE, because the two say different things: an
   underlined address is a copyable string that happens to be clickable, and this
   is now a control whose whole job is to open a message. It reads as one.

   44px min-height is the tap target this always had. Oxblood ground rather than
   ink so it is the one thing in this column that asks to be pressed; cream on
   oxblood measures 7.4:1. */
.td-btn{display:inline-flex;align-items:center;justify-content:center;
   min-height:44px;padding:0 clamp(1.1rem,2.4vw,1.6rem);
   background:var(--oxblood);color:var(--bone);
   font-family:var(--mono);font-size:11px;letter-spacing:.16em;
   text-transform:uppercase;text-decoration:none;
   transition:background .3s,color .3s}
.td-btn:hover{background:var(--ink);color:var(--bone)}
.td-btn:focus-visible{outline:2px solid var(--ink);outline-offset:3px}

.td-kit{margin-top:1.4rem;padding-top:1.1rem;border-top:1px solid var(--rule);
   font-size:.92rem;line-height:1.6;color:var(--ink-55)}


/* ==== sections/visit.css ==== */
/* ==========================================================================
   §6 · VISIT — engraved map, details & mailing list (the close)
   markup: sections/visit.eta + partials/signup.eta   ·   copy: 06-visit.json

   The plate rises into place as the runway scrolls, and everything inside it is
   drawn: the map is an SVG engraving, not a tile service, so this page loads no
   third-party script and hands nobody a visitor log. The plate is now a LINK to
   Google Maps — which is the brief's "make the map active", done without any of
   the cost of embedding one. Nothing is requested from Google until somebody
   asks to go there.
   ========================================================================== */
#visit{--ground:#ede2ce;background:radial-gradient(120% 100% at 50% 0%, #f3edde 0%, #e7dcc7 72%)}
#visit .relic img{width:100%;height:auto;mix-blend-mode:multiply;opacity:.13}

/* Both terms of that min() are measured against the VIEWPORT, and the parent
   .copy is not the viewport — it is inset by --pad on the left and --pad-r on
   the right. So the plate could be wider than the space it was given, and below
   ~1200px it was: `margin:auto` resolves to zero when a box is over-constrained,
   so the surplus all went to the right and pushed the address, the heading and
   the signup button off centre and past the right inset.

   `100%` is the third term, and it is the only one that knows about the
   parent's insets, because .copy already accounts for them. */
#visit .plate{position:relative;z-index:20;
   width:min(94vw,1060px,100%);margin:0 auto;
   display:grid;grid-template-columns:1.08fr .92fr;
   background:#efe7d7;border:1.5px solid var(--ink);
   box-shadow:0 34px 70px -34px #1b243577, 0 2px 0 #1b243622;
   transform:translateY(calc((1 - var(--p))*36px));opacity:calc(.62 + var(--p)*.38)}
#visit .plate::before{content:"";position:absolute;inset:7px;border:1px solid var(--rule-2);
   pointer-events:none;z-index:3}
#visit .mapwrap{position:relative;border-right:1.5px solid var(--ink);min-width:0}

/* ---- the map, as a link -------------------------------------------------
   The anchor is the whole map cell, so the target is a 400px square rather than
   a line of text — no 44px minimum to think about, and no doubt about what is
   clickable. The padding moved from .mapwrap onto the link so the hit area
   includes the plate's inner margin instead of stopping at the drawing's edge.

   A DRAWN MAP THAT NAVIGATES HAS TO SAY SO. Nothing about an engraving suggests
   it is a control, so a hint sits under it in the site's mono, and the whole
   plate lifts very slightly on hover — enough to answer "is this a button?",
   not enough to make the map look like one. The link's aria-label carries the
   action ("Open in Google Maps") while the <svg>'s own label still describes
   the picture, because those are two different questions and a screen reader
   asks both. */
/* A FLEX COLUMN, CENTRED, RATHER THAN A BLOCK. The plate is a two-column grid
   and the details side is the taller of the two, so the map cell is stretched to
   match it — as a block the drawing sat at the top of that cell with ~250px of
   empty paper under it, which read as the map having failed to fill its frame.
   Centred, the square drawing sits on the plate's own optical middle and the
   surplus is split evenly above and below it. */
#visit .maplink{display:flex;flex-direction:column;justify-content:center;
   height:100%;padding:18px;text-decoration:none;color:inherit;
   transition:background .35s ease}
#visit .maplink:hover{background:#1b24360a}
#visit .maplink:focus-visible{outline:2px solid var(--oxblood);outline-offset:-6px}
#visit .maplink svg{display:block;width:100%;height:auto;
   transition:transform .5s ease}
#visit .maplink:hover svg{transform:translateY(-2px)}
#visit .maphint{display:block;margin-top:.85rem;text-align:center;
   font-family:var(--mono);font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;
   color:var(--ink-45);transition:color .35s}
#visit .maplink:hover .maphint{color:var(--oxblood)}

#visit .info{padding:clamp(1.8rem,3.4vw,3rem);display:flex;flex-direction:column;justify-content:center}
#visit .info .kicker{margin-bottom:.5rem}
#visit .info h2{font-size:clamp(2.1rem,4.4vw,3.3rem)}
#visit .rows{margin-top:1.6rem;border-top:1px solid var(--rule-2)}
#visit .row{display:grid;grid-template-columns:82px 1fr;gap:1rem;align-items:baseline;
   padding:.95rem 0;border-bottom:1px solid var(--rule-2)}
#visit .row .lbl{font-family:var(--mono);font-size:10px;letter-spacing:.22em;
   text-transform:uppercase;color:var(--oxblood)}
#visit .row .val{font-size:1.05rem;color:#1b2436d8;line-height:1.5}

/* The second half of "make the map active": the same destination as a plain
   text link, in the details column, for anyone who does not think to click a
   picture and for anyone reading this page as a list of links. It is a separate
   line rather than a phrase inside the address, so it is findable at a glance
   and cannot be mistaken for part of the street name. */
#visit .dirlink{display:inline-flex;align-items:center;min-height:44px;margin-top:.15rem;
   font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--ink-70);text-decoration:none;
   border-bottom:1px solid var(--rule-2);transition:color .35s,border-color .35s}
#visit .dirlink::after{content:"\2197";margin-left:.5em;font-size:1.1em;line-height:0}
#visit .dirlink:hover{color:var(--oxblood);border-bottom-color:currentColor}
#visit .dirlink:focus-visible{outline:2px solid var(--oxblood);outline-offset:3px}

/* ---- the signup, and the only joke on the page ----
   The crane and the mask are the two states of this form. Get it right and she
   releases the paper crane; mistype and she is holding a mask over her own
   face. Nothing is labelled; it pays off only for people who mistype. */
#visit .siglabel{display:block;margin-top:1.7rem;margin-bottom:.55rem;
   font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--ink-55)}
#visit .signup{display:flex;gap:.55rem;flex-wrap:wrap;position:relative}
#visit .signup input{flex:1;font-family:var(--serif);font-size:1rem;padding:.72rem .95rem;min-width:170px;
   background:#f6f0e2;border:1px solid var(--rule-2);color:var(--ink);border-radius:2px;
   min-height:44px}
#visit .signup input::placeholder{color:var(--ink-45)}
#visit .signup input:focus-visible,#visit .signup button:focus-visible{
   outline:2px solid var(--oxblood);outline-offset:2px}
#visit .signup input[aria-invalid="true"]{border-color:var(--oxblood)}
/* min-height 44, not the 36 the padding alone produced. The one control on the
   page that converts anything should not be the hardest thing on it to hit. */
#visit .signup button{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
   padding:.72rem 1.3rem;min-height:44px;background:var(--oxblood);color:var(--bone);border:none;border-radius:2px;
   cursor:pointer;transition:filter .3s}
#visit .signup button:hover{filter:brightness(1.12)}

/* The crane or the mask, swapped by a class the SERVER writes. Whole figures
   rather than a square emblem, so this is sized off HEIGHT and sits on the
   baseline of the form — she stands next to it. Same multiply treatment as
   every other woodcut on a bone ground; no inversion, the source art is already
   ink. The two poses are not the same aspect ratio, which is fine precisely
   because height is what's pinned. */
#visit .mask{position:absolute;right:-4px;bottom:calc(100% - 2px);
   height:clamp(92px,11vw,132px);opacity:0;transform:translateY(8px) scale(.96);
   transform-origin:100% 100%;
   transition:opacity .25s ease,transform .25s ease;pointer-events:none}
#visit .mask img{height:100%;width:auto;display:block;mix-blend-mode:multiply}
#visit .signup.is-comedy .mask,#visit .signup.is-tragedy .mask{
   opacity:.9;transform:translateY(0) scale(1)}

/* the message is real text, not colour alone (WCAG 1.4.1) */
#visit .sigmsg{margin-top:.6rem;font-size:.94rem;color:var(--oxblood);min-height:1.2em}
#visit .sigmsg:empty{min-height:0}
/* success is not an error — say it in ink, not in the error colour */
#visit .signup.is-comedy ~ .sigmsg{color:var(--ink-70)}

/* ---- socials + sister venues --------------------------------------------
   Same mono register as the signoff, one step brighter so the links read as
   links. */
#visit .fmeta{margin-top:1.6rem;display:flex;flex-direction:column;gap:.1rem}
#visit .frow{display:flex;align-items:center;flex-wrap:wrap;gap:0 .85rem;
   font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase}
#visit .frow .lbl{color:var(--ink-45)}
/* 10px of ink, 44px of target — see the note on .topnav a. The underline is
   drawn on an inner span-height box rather than on the padded one, so growing
   the hit area does not drop the rule away from the word. */
#visit .frow a{color:var(--ink-70);text-decoration:none;
   display:inline-flex;align-items:center;min-height:44px;transition:color .35s}
#visit .frow a>span{border-bottom:1px solid var(--rule);padding-bottom:1px;
   transition:border-color .35s}
#visit .frow a:hover{color:var(--oxblood)}
#visit .frow a:hover>span{border-bottom-color:currentColor}
#visit .frow a:focus-visible{outline:2px solid var(--oxblood);outline-offset:-4px;border-radius:2px}
#visit .frow .sep{color:var(--rule-2)}

/* A social channel and its glyph. `.soc` is used by BOTH branches of the
   markup — the <a> when there is a URL and a <span> when there isn't — so the
   two states sit on exactly the same baseline and the row does not reflow when
   somebody pastes the Facebook address in. */
#visit .frow .soc{display:inline-flex;align-items:center;gap:.45em}
#visit .frow .soc .ic{width:12px;height:12px;display:block;flex:none}
/* The pending state: present, greyed, and carrying the `tk` mark. Facebook is a
   commitment with no page yet, and the honest rendering of that is a row that
   says so — not an <a href=""> that reloads the page, and not a silent gap that
   makes the omission look deliberate. `min-height` matches the anchor so the
   two channels align whatever state they are in. */
#visit .frow .soc.is-pending{color:var(--ink-45);min-height:44px}
#visit .frow .soc.is-pending .tk{margin-left:.15em}

#visit .signoff{margin-top:1.7rem;font-family:var(--mono);font-size:10px;letter-spacing:.2em;
   text-transform:uppercase;color:var(--ink-45)}

/* The return link inside the plate. It is the last thing in the last section,
   so it takes the plate's own inset rather than the page's — the shared
   .backtop margins are stated against --pad, which inside a bordered card would
   push it a whole page-inset away from the edge it belongs to. */
#visit .backtop{margin:1.9rem 0 0}

/* ---- map internals ------------------------------------------------------ */
#visit .map .blk{fill:url(#mapHatch);stroke:var(--ink);stroke-width:1}
#visit .map .park{fill:url(#mapDot);stroke:var(--ink);stroke-width:1}
#visit .map .canal{fill:none;stroke:var(--ink);stroke-width:8;opacity:.32;stroke-linecap:round}
#visit .map .canal-c{fill:none;stroke:var(--ink);stroke-width:1;opacity:.5;stroke-dasharray:2 4}
#visit .map .stlbl{font-family:var(--mono);font-size:8px;letter-spacing:.12em;fill:var(--ink-45)}
#visit .map .cface{font-family:var(--mono);font-size:9px;letter-spacing:.1em;fill:var(--oxblood)}
#visit .map .scl{font-family:var(--mono);font-size:8px;letter-spacing:.06em;fill:var(--ink-45)}
#visit .map .ctitle{font-family:var(--serif);font-style:italic;font-size:20px;fill:var(--ink)}
#visit .map .csub{font-family:var(--mono);font-size:8px;letter-spacing:.26em;fill:var(--ink-45)}
#visit .map .mkr{font-family:var(--mono);font-size:9px;letter-spacing:.1em;fill:var(--ink);font-weight:600}
#visit .map .mkr2{font-family:var(--serif);font-style:italic;font-size:9.5px;fill:var(--ink-60)}
#visit .map .pin{fill:var(--oxblood)}
#visit .map .pinring{fill:none;stroke:var(--oxblood);stroke-width:1.4;
   transform-box:fill-box;transform-origin:center;animation:pingp 3.4s ease-out infinite}
@keyframes pingp{0%{transform:scale(.45);opacity:.8}75%{opacity:0}100%{transform:scale(2.6);opacity:0}}


/* ==== sections/footer.css ==== */
/* ==========================================================================
   §7 · THE FOOT
   markup: sections/footer.eta   ·   copy: 07-footer.json

   The ground the scroll lands on. See the note in the markup for why this is a
   foot and not a second contact block.

   --ground is declared like every other section's because the fixed chrome
   borrows it (base/chrome.css); without it the wordmark and the header links
   would carry §6's cream ellipse down onto ink. The registry's `dark: 0` flips
   the chrome's own colour — this is what its backing plate is painted in.
   ========================================================================== */
#footer{--ground:var(--night);background:var(--night);color:var(--bone);
   padding:11vh var(--pad-r) 7vh var(--pad)}

/* Centred, and narrow. Every other section on the page is a left-aligned column
   because it is carrying an argument; this one is carrying a signature, and a
   signature sits under the middle of the page it signs. */
#footer .fwrap{max-width:44rem;margin:0 auto;text-align:center}

#footer .fmark img{width:66px;height:auto;
   /* the wordmark is drawn in ink for cream grounds — same inversion the fixed
      mark gets in .dark, and for the same reason */
   filter:invert(1) brightness(1.6);opacity:.92;
   /* The lightest pointer drift on the page (4px). The foot is a resting place
      and its authored motion is the hover arrow, not an ambience — but a
      signature that ignores the cursor entirely, alone among seven sections, is
      the inconsistency that reads as jank. Present, but the quietest. */
   will-change:transform;transform:translate3d(calc(var(--mx,0)*4px),calc(var(--my,0)*4px),0)}

#footer .fline{margin:1.7rem 0 0;font-family:var(--display);text-transform:uppercase;
   font-size:clamp(1.25rem,2.5vw,1.85rem);line-height:1.15;letter-spacing:.015em;
   color:#f2ecdd}
#footer .fwhere{margin:.85rem 0 0;font-family:var(--serif);font-style:italic;
   font-size:1.02rem;color:#efe7d7bb}

/* The three phases, once more and in one line. It is the last thing the page
   says because it is the thing a pre-launch site exists to say: this opens in
   pieces, and the first piece is the bar. Gold, because it is the only sentence
   here that is information rather than signature. */
#footer .fphase{margin:1.4rem 0 0;font-family:var(--mono);font-size:9.5px;
   letter-spacing:.22em;text-transform:uppercase;color:var(--gold);opacity:.86;
   line-height:1.9}


/* ==== base/responsive.css ==== */
/* ==========================================================================
   Z · RESPONSIVE & REDUCED MOTION

   MUST STAY LAST IN THE BUNDLE. Several rules here re-declare selectors from
   the section files at equal specificity and win only on source order. The
   concatenation order is src/lib/assets.js → CSS_ORDER, where this is the final
   entry; moving it breaks the small-screen layout silently, which is the worst
   kind of breakage because everything still renders.

   All media queries live here rather than beside the rules they override, so
   there is exactly one place to look when a breakpoint misbehaves.
   ========================================================================== */

/* ---- short viewports ----------------------------------------------------
   THE HERO IS THE ONLY THING ON THIS SITE THAT A SHORT SCREEN CAN BREAK, and
   width breakpoints cannot reach it: at 1440×720 the viewport is comfortably
   wide and the copy stack — 330px of wordmark, two lines of tagline, the
   address, the note and the feature line — is 620px tall inside a 720px
   sticky stage with nothing left for air.

   So its second rhythm is keyed to HEIGHT. The wordmark yields first, because
   it is the one element whose size is pure presence rather than legibility;
   the type follows; the feature line's air goes last, since it carries three
   facts and is already the smallest thing in the frame. */
/* THE CLASS NAMES CHANGED WITH THE PORT. §1 is v0.3's prologue now and its copy
   stack is .wordmark / .wordmark-fig / .hero-line / .hero-where / .hero-sub /
   .phasing — not the .tagline / .subtag / .sub-note the dark photographic hero
   had. Rules left pointing at the old names would have been silently inert,
   which is the worst way for a breakpoint to fail: nothing errors, the page
   just stops answering a short screen. */
@media (max-height:800px){
  #hero .wordmark{width:min(44vw,400px)}
  #hero .wordmark-fig{width:min(14vw,130px)}
  #hero .hero-line{font-size:clamp(1.05rem,2vw,1.4rem);margin-top:1.3rem}
  #hero .phasing{margin-top:1.9rem}
}
@media (max-height:680px){
  #hero .wordmark{width:min(38vw,330px)}
  /* The muse figure goes before any type does — it is the one thing in the
     stack that is pure presence and carries no information. */
  #hero .wordmark-fig{display:none}
  #hero .hero-where{font-size:10px}
  #hero .hero-sub{margin-top:.55rem;line-height:1.7}
  #hero .phasing{margin-top:1.4rem}
  /* The cue is the first thing to go: it invites a scroll the reader on a
     short screen is already being invited to make by the copy running close to
     the bottom of the frame. */
  #hero .cue{display:none}
}

/* ---- the header at narrow desktop widths --------------------------------
   FIVE LINKS, NOT THREE. The row is ~347px at 1440 and the clamped gap holds it
   to ~311px at 761 — which fits, but only just, and it fits by leaving 61px to
   the viewport edge. Tightening the tracking and the gap one step below 1024px
   buys ~40px and keeps the outer link clear of both the edge and the feather.

   NOT hiding a link. The five toplinks are the brief's table of contents; a
   header that drops "Provenance" at 900px is a different site at 900px. */
@media (max-width:1024px){
  .topnav{gap:.85rem}
  .topnav a{font-size:9.5px;letter-spacing:.15em}
}

/* ---- tablet / narrow desktop --------------------------------------------
   ONE BREAKPOINT STACKS EVERY TWO-COLUMN LAYOUT ON THE PAGE, and they all
   deserve it for the same reason rather than by coincidence: each is a text
   column beside something, and below ~900px the text column drops under about
   42 characters, which is where a measure stops being comfortable and starts
   being a list of fragments. */
@media (max-width:900px){
  /* .tease / .pv-cols / .pv-figs are GONE from this block along with the three
     sections that owned them — the old §2 provenance and the §3/§4 tease pair
     are replaced by the ported chapters, whose own breakpoints live at the foot
     of this file. .td-body is the one survivor: §8 is still the section it
     always was. */
  .td-body{grid-template-columns:1fr}

  /* The press plate is no longer a sidebar, so it stops being a plate. A
     bordered box at full width with a column of running text above it reads as
     a form; the top rule alone says the same thing more quietly. */
  .td-press{border:none;border-top:1.5px solid var(--ink);background:none;
     padding:1.8rem 0 0}
  .td-fig{margin-inline:0 auto}
}

/* ---- phone -------------------------------------------------------------- */
@media (max-width:760px){
  :root{--pad:7vw}

  /* The chrome is fixed, so long copy scrolls underneath it, and a scrim only
     hides that. Instead the chrome scrolls away with the page at this width —
     it is a 390px screen and the header links are reachable from the foot of
     the page, where the return link and the footer both sit. */
  .mark,.topnav{position:absolute}
  /* the washes exist because fixed chrome has no ground; here it scrolls with
     the page and has the section's own, so they would be a smudge over the art */
  /* The chrome stops being fixed here and sits on the section's own ground,
     so there is nothing scrolling under it to cover. */
  body::before{display:none}
  .mark img{width:44px}

  /* THE HEADER LINKS WRAP TO TWO ROWS AT THIS WIDTH, and that is why the topnav
     stops being a fixed-height flex row. Five labels are ~250px at the phone
     size and a 390px screen has ~330px between the insets — so "Visit" fell off
     the end. `flex-wrap` plus an explicit `height:auto` (the base rule pins 36px,
     which would have clipped the second row to nothing) lets it become two rows
     of tidy mono under the mark. */
  .topnav{top:88px;left:var(--pad);right:var(--pad);height:auto;
     flex-wrap:wrap;justify-content:flex-start;gap:0 .9rem}
  .topnav a{font-size:9.5px;letter-spacing:.14em;min-height:34px}

  /* THE HERO'S OWN MARK IS THE ONE THAT SHOWS ON A PHONE. With the chrome now
     absolutely positioned it scrolls with the page, so the corner mark and the
     centred one are on screen together for the whole first section — the exact
     duplication base/chrome.css exists to prevent, arriving by a different
     route. Hidden here rather than in the module, because this is a layout
     consequence and the module knows nothing about layout. */
  body.at-hero .mark{display:none}

  /* THE COPY CLEARS THE HEADER. At this width the chrome is `position:absolute`
     (see the rule above) so it scrolls with the page and sits in the first
     100px of it — and #hero .copy is `align-self:flex-start` now, so the
     wordmark started at 105px and printed straight through "TABLE". */
  #hero .copy{padding-top:clamp(15vh,19vh,23vh)}
  #hero .wordmark{width:74vw}
  #hero .hero-line{font-size:clamp(1.05rem,4.6vw,1.35rem);max-width:20ch;margin-inline:auto}
  #hero .hero-where{font-size:9.5px}
  /* v0.3 faded this to .08 here because at phone width the drawing sits
     directly behind the copy rather than beside it. HOME needs it visible at
     every width — the brief opens on "large striking background image of
     building" and a phone is where most people will read that line — so the
     strength holds and the wash under .lede does the protecting, exactly as it
     does on desktop. 96vw is v0.3's, and it is right: something sized for a
     1440px stage is a smudge at 390. */
  /* AND IT HAS TO BE PULLED UP, or raising the opacity buys nothing. The germ
     is anchored `top:100%` and pushed back up by 56% of its own height, which
     at 1440 leaves most of the facade on screen. At 390 the drawing is 374px
     wide and about 409 tall, so the same 56% leaves 229px of it below the fold
     and the frame shows its top edge only. 88% puts the building in the lower
     half of the screen where the copy is not.

     The pointer drift is dropped with it, which costs nothing: there is no
     pointer here, and .layer's transform and this one cannot compose. */
  #hero .germ{width:112vw;opacity:calc(.17 - var(--p)*.05);
     transform:translate(-50%,-60%)}


  /* Two plates of illustration in a row is a lot of scrolling for annotation;
     the woodcut is the one that carries information (she is over the door), so
     it keeps its size and the star chart takes the shorter box. */

  /* The niche is centred and capped above; on a phone the shoulder objects have
     nowhere to break out to without leaving the screen. Tuck them onto the
     frame itself. */

  /* §6 · THE CHAPTER UNPINS HERE.
     Once the plate stacks to one column it is taller than the screen, and a
     sticky stage is `height:100vh; overflow:hidden` — so the map would be cut
     off at the top and the mailing-list form at the bottom, with no scroll
     position that showed either. The plate IS the section; a frame it cannot
     fit inside is the wrong frame.

     The rise-into-place transform goes with it: it is driven by --p, which only
     a sticky stage is given, and a plate stuck at .62 opacity would be worse
     than no animation at all.

     The !important is against page.eta's inline `style="height:190vh"`, which
     comes from the section registry. Registry data is not overridable from a
     stylesheet any other way, and the alternative — dropping `height` from the
     registry — would cost the desktop animation to fix a phone. */
  #visit{height:auto !important}
  #visit .sticky{position:static;height:auto;overflow:visible;padding:13vh 0 11vh}
  #visit .copy{position:static}
  #visit .plate{grid-template-columns:1fr;transform:none;opacity:1}
  #visit .mapwrap{border-right:none;border-bottom:1.5px solid var(--ink)}
  /* the two relics are placed against the sticky stage; with the stage now as
     tall as the plate they stretch down the whole section, so they are dimmed
     rather than left floating mid-copy */
  #visit .relic{opacity:.5}

  /* THE HERO IS THE ONE CHAPTER THAT STAYS PINNED ON A PHONE. Its content is a
     single centred stack that fits any screen once the height queries above
     have had their say, and the push-in is the first thing anyone sees — it is
     the whole argument for the page having a runway at all. */
}

/* ---- the feature line on a narrow phone ---------------------------------
   Three equal columns need ~100px each, and "Delicatessen" at the clamp floor
   is 92px of caps. At 375px the columns are 108px and it fits; at 320px they
   are 93px and it does not — the middle cell wraps to two lines and the row
   stops reading as three parallel facts.

   So below 520px the schedule turns on its side: one row per phase, the pair
   held together on a line, with the rules running between rows instead of
   between columns. Same three facts, same order, same pairing — the axis is the
   only thing that changed, which is exactly why the pairing had to be one <li>
   rather than two aligned rows. */
@media (max-width:520px){
  /* THE PHASE ROW WRAPS, IT NO LONGER TURNS INTO A GRID. The block that stood
     here set `grid-template-columns:1fr` and a 20rem cap on #hero .phasing —
     right for the three-column <ul> the old hero used, and inert-to-harmful on
     v0.3's row, which is an inline-flex of three <span>s. The cap was the live
     half: it squeezed a row already handling its own wrapping.

     v0.3's rule needs no override — .phasing is flex-wrap:wrap and its
     separators draw on the LEFT of every item but the first, so a wrapped line
     can never end on a dangling hairline. Only the gutter tightens. */
  #hero .phasing .ph{padding:0 .8rem}
}

/* ---- narrow phones ------------------------------------------------------
   THE ONLY THING THIS BREAKPOINT DOES IS LOWER CLAMP FLOORS FOR CAPS.

   Section titles are set in caps, which are about 15% wider than the mixed case
   the sizes were originally chosen for. Every heading's vw term was solved
   against the box it actually gets and holds down to 320px — but the floors do
   not scale, so below ~372px an authored two-line title wraps to three.

   Measured at 320px, the widest that fits: "SMALL THINGS, EATEN STANDING UP."
   is the longest authored line on the page and sets the number. */
@media (max-width:374px){
  .td-head h2,#table .head h2{font-size:1.85rem}
  #visit .info h2{font-size:1.9rem}
  #hero .hero-line{font-size:1.05rem}
}

/* ---- reduced motion -----------------------------------------------------
   Everything stops. There is no deliberate exception: a device that animates
   for people who asked for no animation is a WCAG 2.3.3 failure, not a joke.

   The blanket rule is exhaustive and the known stragglers are named explicitly
   after it — `.drift` and the map's ping are both infinite animations that a
   duration override alone leaves running at a millisecond a cycle, which is
   worse than the original.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
  .drift,#visit .map .pinring{animation:none !important}

  /* THE ENGINE PINS --p TO 1 UNDER REDUCE MOTION, which is the END of each
     runway, and every section's CSS is written so that 1 is the settled state.
     For §6 that is exactly right: the plate is arrived, opaque and square.

     FOR §1 IT IS NOT, and this is the one place on the site where the two
     chapters disagree about what "at rest" means. At --p 1 the plate has risen
     5vh, grown 9% and deepened to its full .19 — which is not a still of the
     hero, it is the hero at the moment the page turns, with the building at its
     heaviest directly behind the wordmark.

     So §1 is pinned to its own frame 0 instead: the plate unscaled, unrisen and
     at its .14 watermark, the copy where it was authored. Stated here as fixed
     values rather than by changing what the engine writes, because --p is also
     what §6 reads and the engine cannot hand out two different answers.

     THE COPY NEEDS NO `opacity:1` OVERRIDE ANY MORE and the absence is the
     point: it no longer has an opacity to override. The rule that used to be
     here was undoing the same fade this rebuild removed — see hero.css. */
  #hero .germ{transform:translate(-50%,-58%) !important;opacity:.13 !important}
  #hero .motion-germ{transform:none !important}
  #hero .cue{opacity:1 !important}
  #hero .wordmark{transform:none !important}
}



/* ==========================================================================
   §2 PROVENANCE — the test site's own breakpoints, re-rooted with the CSS
   --------------------------------------------------------------------------
   #provenance .pvb-ceiling, #provenance .pvb-intro, #provenance .pvb-collection, #provenance .pvb-sky1877, #provenance .pvb-origin and #provenance .pvb-history are v0.3's
   files byte-for-byte, so these are v0.3's rules for them, moved across rather
   than re-derived. They were solved against these exact layouts.

   Selectors that named a chapter Phase 1 does not carry (#night, #rooms,
   #facade, #contact) had that half removed; a rule belonging only to one of
   those is gone entirely.
   ========================================================================== */
@media (max-width:1050px){
  #provenance .pvb-origin .wrap{grid-template-columns:1fr;grid-template-areas:"head" "art" "copy"}
  #provenance .pvb-origin .portrait{width:clamp(180px,34vw,250px);justify-self:center;margin:0 0 clamp(2.6rem,6vw,4rem)}
}
@media (max-width:900px){
  #provenance .pvb-intro .row,#provenance .pvb-history .intro,#provenance .pvb-origin .cols{grid-template-columns:1fr}
  #provenance .pvb-history .ev.has-fig{padding-right:0}
  #provenance .pvb-history .ev-fig{position:relative;right:auto;top:auto;margin-top:1.4rem;width:clamp(96px,26vw,150px)}
}
@media (max-width:760px){
  #provenance .pvb-intro{padding-top:13vh}
  #provenance .pvb-intro .figs .muse{position:relative;left:auto;right:auto;top:auto;bottom:auto;width:52%}
  #provenance .pvb-intro .figs .m2,#provenance .pvb-intro .figs .m3{align-self:flex-end;width:44%}
  #provenance .pvb-intro .carve{position:relative;left:auto;right:auto;top:auto;bottom:auto;max-width:none}
  #provenance .pvb-collection .named{max-width:none}
  #provenance .pvb-origin{padding-top:13vh}
  #provenance .pvb-sky1877 .cap h2,#provenance .pvb-sky1877 .cap .body{max-width:100%}
  #provenance .pvb-sky1877 .stop{margin-top:.8rem}
  #provenance .pvb-sky1877 .stop .ln{font-size:.92rem;line-height:1.45}
  #provenance .pvb-sky1877 .inst .plate,#provenance .pvb-sky1877 .inst .coords{display:none}
  #provenance .pvb-history .ev p{max-width:none}
}
@media (max-width:374px){
  #provenance .pvb-history .intro h2{font-size:1.85rem}
}
@media (prefers-reduced-motion:reduce){
  #provenance .pvb-ceiling .cscene{transform:none !important}
  #provenance .pvb-sky1877 .sky .st{animation:none !important}
  #provenance .pvb-sky1877 .sky .cf .ln{transition:none !important}
}

/* ==========================================================================
   §2 PROVENANCE, §3 GLASS, §4 TABLE
   --------------------------------------------------------------------------
   WRITTEN FOR WHAT THESE SECTIONS ARE, not inherited from what they were. The
   block that stood here was v0.3's forty rules for #provenance .pvb-ceiling, #provenance .pvb-collection,
   #provenance .pvb-sky1877, #provenance .pvb-origin, #menu and #hors, carried across with those chapters. Four
   of the six are now one static section with its own stylesheet, and the other
   two lost the lists those rules were almost entirely about — .plate, .line
   .nm, .what, .cols, and a `#table .sticky` override for a stage that no longer
   exists. None of it errored. All of it was dead.

   What §3 and §4 need at a narrow width is small, because what they contain is
   small: a heading, one picture and one or two paragraphs.
   ========================================================================== */
@media (max-width:900px){
  /* §2's three compositions all stack. The story's woodcut goes to a capped,
     centred figure above the copy rather than a 34% column beside it — at 900px
     that column is 300px and the two text columns beside it are 28 characters
     each, which is a list of fragments rather than a measure. */
  .pv-story{grid-template-columns:1fr;gap:clamp(1.8rem,4vw,2.6rem)}
  .pv-germania img{width:min(52vw,260px);margin-inline:auto}
  .pv-cols{grid-template-columns:1fr;gap:1.2rem}
  .pv-col p{max-width:60ch}
  .pv-pair{grid-template-columns:1fr;gap:clamp(2.2rem,5vw,3rem)}
  /* Stacked, the room's cell is full width and the woodcuts behind it no longer
     have an empty right third to sit in — they would be behind the list. */
  .pv-relics{display:none}
  .pv-named li{max-width:none}
  .pv-fresco{aspect-ratio:4/3}

  /* §4 stops being two columns for the same reason §2's story does. */
  #table .board{grid-template-columns:1fr;row-gap:clamp(1.6rem,4vw,2.2rem)}
  #table .blend,#table .expos{grid-column:1}
  #table .blend{width:min(100%,26rem)}
}
@media (max-width:760px){
  /* The relics are margin furniture and there are no margins left. Both
     sections' pictures are doing the work at this width. */
  #glass .relic,#table .relic{display:none}
  #glass .blend,#table .blend{width:100%}
  #glass .expo,#table .expo{font-size:1rem}
  /* The fresco caption is the whole lower half of a 4:3 frame at this width;
     let the picture be taller so the type has a band of its own to sit on. */
  .pv-fresco{aspect-ratio:3/4}
  .pv-fresco-cap h2{font-size:clamp(1.7rem,7vw,2.4rem)}
  .pv-fresco-cap .body{max-width:none}
  .pv-sky p{max-width:none}
}
@media (max-width:374px){
  #glass h2,#table .head h2{font-size:1.6rem}
}
