/* ==== 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:"iA Writer Mono S",monospace;
  --body:"Amberes Grotesk",sans-serif;

  --display:"HV Fitzgerald",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 ==== */
/* MUSE: display, body, and utility fonts from Brand Book Sonnet. */
@font-face{
  font-family:"HV Fitzgerald";font-weight:400;font-style:normal;font-display:swap;
  src:url("/site-tests/site-v7/fonts/hv-fitzgerald-regular.woff2") format("woff2");
}
@font-face{
  font-family:"HV Fitzgerald";font-weight:400;font-style:italic;font-display:swap;
  src:url("/site-tests/site-v7/fonts/hv-fitzgerald-italic.woff2") format("woff2");
}
@font-face{
  font-family:"Amberes Grotesk";font-weight:300;font-style:normal;font-display:swap;
  src:url("/site-tests/site-v7/fonts/amberes-grotesk-light.woff2") format("woff2");
}
@font-face{
  font-family:"iA Writer Mono S";font-weight:400;font-style:normal;font-display:swap;
  src:url("/site-tests/site-v7/fonts/ia-writer-mono-s-regular.woff2") format("woff2");
}


/* ==== 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(--body);font-weight:300;
  overflow-x:hidden;
}
svg{display:block;color:currentColor}

/* Shared brand roles; section-specific labels can override heading defaults. */
:root{font-synthesis:none}
:where(h1,h2,h3,h4,h5,h6){font-family:var(--display);font-weight:400}
em,i{font-family:var(--display);font-weight:400}
button,input,select,textarea{font-family:var(--mono);font-weight:400}


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

   The `.dark` class is written on <body> by the engine when an ink-ground
   section owns the viewport, and every flip in this file hangs off it. That is
   the entire 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. Cheaper than it was
   now that the chapter rail is gone — three header links rather than fifteen —
   but a keyboard reader still shouldn't have to tab the header to reach 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;font-weight:400;}
.skip:focus-visible{top:26px;outline:2px solid var(--gold);outline-offset:2px}
/* the skip target. It takes focus so the NEXT tab continues from the content
   rather than from the top of the header, but it is a 20-screen-tall wrapper and
   a ring drawn around it would be a border around the whole page. */
#main:focus{outline:none}

/* ---------------- fixed mark ----------------
   The mark and the topnav are fixed and sit directly on whatever section is
   behind them — no plate, no scrim. body.dark flips their ink. */
.mark{position:fixed;top:26px;left:var(--pad);z-index:80;line-height:0}
.mark img{width:clamp(52px,4.4vw,68px);height:auto;mix-blend-mode:multiply;
   transition:filter .6s,opacity .6s}
/* on the ink-ground sections 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}

/* ---------------- header links (balance the mark) ----------------
   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(1.1rem,2.2vw,2rem)}
/* THE INK IS 10.5px; THE TARGET IS 44px. These are two different measurements
   and only the first one is a design decision. The links were 34×12 and 42×12
   — a third of the 44×44 minimum in both axes — because the box was the glyph.
   Padding grows the box without touching the type; the negative margin keeps
   the row's optical alignment where it was, so nothing moved on screen. */
/* THE CHROME'S INK IS ITS OWN TOKEN, because the chrome is the only place on
   the page where the ground is not a constant. --ink-45 is stated once in
   tokens.css and its comment gives its contrast as 4.65:1 — which is measured
   against bone, and true for every OTHER place it is used (the kicker numeral,
   the caption, the contact labels) because all of those sit on a section's own
   flat ground.

   The chrome does not. #night crossfades its ground, so the plate behind these
   links moves too, and the engine now clamps it at 30% of that fade (main.js,
   see the note beside ch.now). At the clamp the plate is rgb(171,166,160) and
   --ink-45 gives 3.05:1 on it — measured on the built page, not derived.

   Full-strength ink on the same plate is 6.43:1, and on ordinary bone it is
   12.63:1 rather than 4.89. So this is not "make it darker to pass a check":
   the 45% alpha exists to hold 10.5px of 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. There is nothing here for it to
   defer to. The weight it was buying is bought instead by the size, the
   letterspacing and the caps.

   Kept as a variable rather than inlined 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 .35rem;margin:0 -.35rem;font-weight:400;}
.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)}
/* On /press and /hiring the page's own name sits last in the row, current. */
.topnav a[aria-current="page"]{color:var(--oxblood)}
.topnav a[aria-current="page"]::before{content:"\00b7";margin-right:1rem;color:var(--rule-2)}

/* THE CHAPTER RAIL IS GONE. It was a fixed column of twelve links down the
   right-hand margin, and everything it needed is removed with it: the per-link
   feathered plates (each asking what was behind ITSELF, because a 418px column
   straddles chapter boundaries), the .on / .over-dark states main.js wrote per
   frame, and --rail-gutter, the 114px lane every content column had to leave
   empty from 761px up. The gutter token survives at 0 in tokens.css so the
   --pad-r / --frame-end names still resolve; see the note there. */

/* ---------------- the running head ----------------
   Centred between the mark and the links: the numeral and name of the
   section that owns the viewport, written by main.js from data-head. It is
   the thread of the walk. The span crossfades: main.js swaps the text under a
   short fade so a chapter boundary reads as a page turn, not a flicker. */
.runhead{position:fixed;top:26px;left:50%;transform:translateX(-50%);z-index:80;height:36px;
   display:flex;align-items:center;pointer-events:none}
.runhead span{font-family:var(--mono);font-size:10.5px;letter-spacing:.24em;text-transform:uppercase;
   color:var(--ink-45);font-weight:400;transition:opacity .35s ease,color .4s;white-space:nowrap}
.runhead.is-fading span{opacity:0}
.dark .runhead span{color:#efe7d7a6}
@media (max-width:900px){ .runhead{display:none} }


/* ==== 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 (§3 holds h2 down to 3.4rem because it carries a
   sentence, not a title) 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;font-weight:400;}
.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 topnav, 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 was a bug that only a phone could show. On desktop
   every dark chapter is a 100vh sticky stage, 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 took the viewport centre the bone #contact plate was still half
   visible — and every rule below fired on it. "COME IN." went cream on bone and
   effectively disappeared; the kicker went gold on bone beside it.

   `.ink` is per chapter and set from the same threshold, so a dark section
   inverts its own type whether or not it owns the middle of the screen, and a
   light section never inverts because of its neighbour.
   -------------------------------------------------------------------------- */
.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. Every other class here has an .ink pair below; .kicker
   had one for its .n child and none for itself, so oxblood stayed oxblood on
   ink. #ceiling and #rooms each patched it to gold locally and looked fine,
   which is exactly what hid the hole: #night never patched it, and its kicker
   sat at 4.42:1 halfway through the bone→night transition and 3.09:1 at the
   end of it. Both fail AA 4.5:1, measured off rendered pixels rather than off
   the declared token — the token was never the problem.

   Gold, not cream: the two sections that already solved this chose gold, it is
   the accent the dark half of the page uses everywhere else (the 1877
   constellations, .hand b), and it holds 9.3–10.8:1 on ink. A local
   override is still allowed — this is the floor, not a ceiling. */
.ink .kicker{color:var(--gold)}


/* SENTENCE CASE, NOT CAPS. v5 set every title in capitals at line-height .84.
   v6's titles are sentences — "Small things, eaten standing up." — and a
   sentence in caps reads as a sign. Mixed case at .98 leading, a hair tight.
   One italic phrase is allowed in at most two headings on the page; it is an
   emphasis, not a formula. */
h1,h2{font-family:var(--display);font-weight:400;text-transform:none;
   line-height:.98;letter-spacing:-.008em}
h1{font-size:clamp(3rem,9vw,7.5rem)}
h2{font-size:clamp(2.3rem,5.6vw,4.6rem)}
h2 em{font-style:italic;color:inherit}

.sub{font-style:italic;color:var(--ink-60);font-size:clamp(1.02rem,1.7vw,1.35rem);margin-top:.7rem;font-family:var(--display);font-weight:400;}
.body{max-width:34ch;margin-top:1.4rem;font-size:1.08rem;line-height:1.6;color:#1b2436c0}
/* An outlined or filled call to action, mono caps on a 44px target. Two on
   the whole page: Keep me posted and Directions, both in the arrival. */
.cta{display:inline-flex;align-items:center;gap:.6em;min-height:44px;padding:0 1.25rem;
   font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
   text-decoration:none;border:1px solid var(--ink);color:var(--ink);border-radius:2px;
   transition:background .3s,color .3s,border-color .3s;font-weight:400;}
.cta:hover{background:var(--ink);color:var(--bone)}
.cta:focus-visible{outline:2px solid var(--oxblood);outline-offset:3px}
.cta.fill{background:var(--oxblood);border-color:var(--oxblood);color:var(--bone)}
.cta.fill:hover{filter:brightness(1.12);background:var(--oxblood)}
.ink .cta{border-color:#efe7d7cc;color:#efe7d7}
.ink .cta:hover{background:#efe7d7;color:var(--ink)}
/* The maker's caption under a photograph: mono, small, never under 12px. */
.capt{font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;
   color:var(--ink-45);line-height:1.6;font-weight:400;}
.ink .capt{color:#efe7d7a6}
.tag{margin-top:1.1rem;font-family:var(--mono);font-size:10px;letter-spacing:.06em;
   color:var(--ink-45);text-transform:uppercase;font-weight:400;}

/* An unanswered fact, marked as one. Not a design element — these are meant to
   be conspicuous until someone fills them in. */
.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;font-weight:400;}

/* 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", "a hundred-voice singing
   society" and "twenty-eight rooms" all break at 320px and are correctly set
   that way — they 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;white-space:nowrap;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;font-weight:400;}
.ink .cue{color:#efe7d7c4}


/* ==== base/frame.css ==== */
/* ==========================================================================
   P · THE FRAME — one thing in the room, hung with its label
   used by: partials/frame.eta, and anything that hangs a picture

   A hairline mount at 1px, the picture, and a two-line label: what it is in
   the display face, then medium and year in mono caps. The same object hangs
   a photograph, a painting and a woodcut, which is what makes the page read
   as one wall rather than three kinds of page.

   TWO MOTIONS, AND ONLY THESE TWO
     .dolly   the picture comes closer inside its mount as the chapter's --p
              (or a static section's --hp) runs. Scale only; a frame on a
              wall does not slide.
     .hang    the frame settles into place — a few vh of travel and a fade —
              over the first half of the runway. Static sections do the same
              on arrival with data-hang (main.js writes .seen).
   Reduce Motion pins --p to 1 and kills transitions; with no script the
   finished state shows (body:not(.js)).
   ========================================================================== */
.frame{position:relative;margin:0}
.frame .pic{position:relative;overflow:hidden;background:var(--ink);
   aspect-ratio:var(--ar,3 / 2);
   box-shadow:0 0 0 1px var(--ink),0 0 0 5px var(--bone),0 0 0 6px var(--rule-2),
              0 30px 50px -30px #1b243677}
.frame .pic img{display:block;width:100%;height:100%;object-fit:cover}
.frame.dolly .pic img{will-change:transform;transform-origin:50% 60%;
   transform:scale(calc(1 + var(--dp,var(--p,0))*.12))}
.frame.hang{--hp2:clamp(0,calc(var(--p,0)*2),1);
   transform:translateY(calc((1 - var(--hp2))*6vh));opacity:calc(.1 + var(--hp2)*.9)}
body:not(.js) .frame.hang{transform:none;opacity:1}
body:not(.js) .frame.dolly .pic img{transform:scale(1.06)}

/* the label */
.lab{display:grid;gap:.18rem;margin-top:.9rem;font-weight:400}
.lab .lt{font-family:var(--display);font-size:1.02rem;line-height:1.25;color:var(--ink)}
.lab .lm{font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
   color:var(--ink-45);line-height:1.6}
.ink .lab .lt{color:#f2ecdd}
.ink .lab .lm{color:#efe7d7a6}
.ink .frame .pic{box-shadow:0 0 0 1px #efe7d7,0 0 0 5px var(--night),0 0 0 6px #efe7d766,0 30px 50px -30px #000}

/* ---- settling in on arrival: static sections --------------------------
   Anything carrying data-hang starts a little low and faint and settles
   once it crosses into the viewport. --i staggers siblings. With no script
   nothing is ever hidden. */
.js [data-hang]{opacity:0;transform:translateY(22px);
   transition:opacity .9s ease,transform .9s cubic-bezier(.2,.7,.2,1);
   transition-delay:calc(var(--i,0)*90ms)}
.js [data-hang].seen{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  .js [data-hang]{opacity:1;transform:none;transition:none}
}


/* ==== sections/door.css ==== */
/* ==========================================================================
   §I · THE DOOR — the corner at night
   markup: sections/door.eta   ·   copy: 01-door.json

   A PLACE, SO FULL-BLEED AND DARK. The photograph is the ground. The copy
   sits in the lower left on a feathered wash, and the runway dollies the
   building closer about its ground floor — the walk up to the door.
   ========================================================================== */
#door{--ground:var(--night)}
#door .sticky{background:var(--night);display:block}

#door .dstage{position:absolute;inset:0;z-index:1;overflow:hidden}
#door .dphoto{position:absolute;inset:0;display:block}
#door .dphoto img{display:block;width:100%;height:100%;object-fit:cover;object-position:50% 40%;
   will-change:transform;transform-origin:52% 78%;
   transform:scale(calc(1.02 + var(--p,0)*.16)) translate3d(calc(var(--mx,0)*-6px),calc(var(--my,0)*-4px),0)}
body:not(.js) #door .dphoto img{transform:scale(1.06)}
/* the wash the copy sits on. Measured: the lower-left of the photograph is
   wet asphalt and lamp reflections, 40–120 levels; cream type needs ground
   under 60 to clear AA, hence the ellipse at full strength under the column. */
#door .dgrade{position:absolute;inset:0;pointer-events:none;
   background:
     radial-gradient(70% 80% at 18% 72%,#0b1020f0 0%,#0b1020c0 40%,#0b102040 75%,transparent 100%),
     linear-gradient(0deg,#0b1020e6 0%,#0b102066 28%,transparent 55%),
     linear-gradient(180deg,#0b102099 0%,transparent 26%)}

#door .dcopy{position:absolute;left:var(--pad);bottom:12vh;z-index:20;max-width:46ch;color:var(--bone)}
.wordmark-h1{font-size:0;line-height:0;letter-spacing:0;margin:0}
#door .wordmark{width:min(26vw,300px);height:auto;display:block;
   filter:invert(1) brightness(1.6);
   will-change:transform;transform:translate3d(calc(var(--mx,0)*-2px),calc(var(--my,0)*-2px),0)}
#door .hero-line{margin-top:1.8rem;font-family:var(--display);font-style:italic;
   font-size:clamp(1.3rem,2.3vw,1.85rem);line-height:1.2;color:#f2ecdd;max-width:24ch}
#door .hero-where{margin-top:.9rem;font-family:var(--mono);font-size:12px;letter-spacing:.2em;
   text-transform:uppercase;color:#efe7d7d0;font-weight:400}
#door .hero-status{margin-top:.5rem;font-size:1.02rem;line-height:1.5;color:#efe7d7bb;max-width:40ch}

#door .phasing{margin-top:1.6rem;list-style:none;display:flex;flex-wrap:wrap;gap:.4rem 0}
#door .ph{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
   color:#efe7d7b0;padding-right:1.1rem;margin-right:1.1rem;position:relative;font-weight:400}
#door .ph:not(:last-child)::after{content:"";position:absolute;right:0;top:50%;width:1px;height:28px;
   background:#efe7d73a;transform:translateY(-50%)}
#door .ph b{color:#f2ecdd;font-weight:400;font-family:var(--body);font-style:normal;
   font-size:1.02rem;letter-spacing:0;text-transform:none;display:block;margin-bottom:.22rem}
#door .ph .now{color:var(--gold)}
#door .actions{margin-top:1.8rem;display:flex;flex-wrap:wrap;gap:.7rem}
#door .cta{border-color:#efe7d7cc;color:#efe7d7}
#door .cta:hover{background:#efe7d7;color:var(--ink)}
#door .cta.fill{background:var(--oxblood);border-color:var(--oxblood);color:var(--bone)}

/* the label, bottom right, like every other picture's */
#door .dlab{position:absolute;right:var(--pad-r);bottom:12vh;z-index:20;margin:0;text-align:right;max-width:30ch}
#door .dlab .lt{color:#f2ecdd}
#door .dlab .lm{color:#efe7d7a6}
#door .cue{color:#efe7d7c4}

/* ---- short laptops ---- */
@media (max-height:760px){
  #door .dcopy{bottom:9vh}
  #door .wordmark{width:min(22vw,240px)}
  #door .hero-line{margin-top:1.1rem}
  #door .phasing{margin-top:1.1rem}
  #door .actions{margin-top:1.2rem}
  #door .dlab{bottom:9vh}
}
@media (max-width:1150px){ #door .cue{display:none} }
/* ---- tablet / phone ---- */
@media (max-width:900px){
  #door .dlab{display:none}
  #door .dgrade{background:
     linear-gradient(0deg,#0b1020f2 0%,#0b1020b0 40%,#0b102030 70%,transparent 100%),
     linear-gradient(180deg,#0b102099 0%,transparent 26%)}
}
@media (max-width:760px){
  #door .dcopy{left:var(--pad);right:var(--pad);bottom:7vh;max-width:none}
  #door .dphoto img{object-position:46% 50%}
  #door .wordmark{width:48vw}
  #door .hero-line{margin-top:1rem;font-size:1.2rem;max-width:none}
  #door .hero-where{font-size:11px}
  #door .hero-status{font-size:.95rem}
  #door .phasing{margin-top:1rem;gap:.3rem .9rem}
  #door .ph{padding-right:0;margin-right:0}
  #door .ph::after{display:none}
  #door .ph b{font-size:.92rem;display:inline;margin:0 .35em 0 0}
  #door .actions{margin-top:1rem;gap:.5rem}
  #door .cta{font-size:10px;padding:0 .9rem;min-height:40px}
}


/* ==== sections/glass.css ==== */
/* ==========================================================================
   §II · THE BAR and §III · THE TABLE — things in the room (both styled here)
   markup: partials/thing.eta via sections/glass.eta, table.eta
   copy: 02-glass.json, 03-table.json

   Cream wall, two hung frames and a column of copy. The grounds are a half
   step apart (bone → bone-2) so the boundary between the two reads as a
   page turn rather than a cut; the pictures swap sides so the pair reads as
   a spread rather than a repeat.
   ========================================================================== */
#glass{--ground:var(--bone)}
#glass .sticky{background:var(--bone)}
#table{--ground:var(--bone-2)}
#table .sticky{background:var(--bone-2)}
#table .frame .pic{box-shadow:0 0 0 1px var(--ink),0 0 0 5px var(--bone-2),0 0 0 6px var(--rule-2),0 30px 50px -30px #1b243677}

.thing{position:relative;z-index:20;width:100%;height:100%;display:grid;
   grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);align-items:center;
   gap:5vw;padding:0 var(--pad-r) 0 var(--pad)}
.thing-right .pics{order:2}

.thing .pics{position:relative;justify-self:center;width:100%;max-width:min(46vw,64vh);
   padding-bottom:5vh}
.thing .big{width:min(84%,calc(70vh * var(--ar)));max-width:100%}
.thing-right .big{margin-left:auto}
.thing .small{position:absolute;right:0;bottom:0;width:min(42%,calc(26vh * var(--ar)));z-index:3}
/* when the pictures stand on the right the large frame's label sits at its
   left edge, so the small frame goes over the top-left corner instead */
.thing-right .small{right:auto;left:0;bottom:auto;top:-4vh}
/* a landscape picture: the column widens and the frame fills it */
.thing.has-land .pics{max-width:min(50vw,82vh);padding-top:6vh}
.thing.has-land .big{width:min(100%,calc(62vh * var(--ar)))}
.thing.has-land .small{width:min(34%,calc(24vh * var(--ar)))}
.thing .small .lab{display:none}
/* the relic in the margin, drawn in ink on the cream wall */
.thing .relic{position:absolute;left:-6%;top:6%;width:min(7vw,92px);opacity:.55;pointer-events:none;mix-blend-mode:multiply}
.thing-right .relic{left:auto;right:-6%;top:auto;bottom:14%}
.thing .relic img{width:100%;height:auto;display:block}

.thing .tcopy{max-width:46ch}
.thing .tcopy h2{max-width:14ch}
.thing .tcopy .sub{max-width:30ch}
.thing .note{margin-top:1.4rem;font-family:var(--mono);font-size:12px;letter-spacing:.12em;
   text-transform:uppercase;line-height:1.7;color:var(--ink-45);max-width:34ch;font-weight:400}
.thing .sig{width:clamp(64px,6.5vw,92px);margin-top:1.8rem;opacity:.8;mix-blend-mode:multiply}
.thing .sig img{width:100%;height:auto;display:block}

/* ---- short laptops ---- */
@media (max-height:760px){
  .thing .big{width:min(84%,calc(62vh * var(--ar)))}
  .thing .sig{display:none}
}
/* ---- tablet / phone: the chapter unpins and the two frames stack ---- */
@media (max-width:900px){
  #glass,#table{height:auto !important}
  #glass .sticky,#table .sticky{position:static;height:auto;overflow:visible;padding:12vh 0 10vh}
  .thing{grid-template-columns:1fr;gap:4vh;height:auto}
  .thing-right .pics{order:0}
  .thing .pics{max-width:none;justify-self:stretch;padding-bottom:0}
  .thing .big{width:min(86%,calc(60vh * var(--ar)))}
  .thing .small{position:relative;width:40%;margin-top:-18%;margin-left:auto;top:auto}
  .thing.has-land .pics{max-width:none;padding-top:0}
  .thing.has-land .big{width:100%}
  .thing.has-land .small{width:38%;margin-top:-16%}
  .thing .relic{display:none}
  .thing .tcopy h2{max-width:none}
  .thing .sig{display:none}
  .frame.hang{transform:none;opacity:1}
  .frame.dolly .pic img{transform:scale(1.04)}
  #glass .cue,#table .cue{display:none}
}
@media (max-width:760px){
  .thing .big{width:100%}
  /* the small frame overlaps the big one's bottom-right corner here, so the
     label keeps to the left half and wraps rather than running under it */
  .thing .big .lab{max-width:54%}
  .thing .small{width:44%;margin-top:-14%}
  .thing .tcopy .body{margin-top:.9rem}
  .thing .note{margin-top:.9rem;font-size:11px}
}


/* ==== 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.
   ========================================================================== */
#ceiling{--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. */
#ceiling .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}
#ceiling .clayer{position:absolute;will-change:transform,filter;
   transform-origin:50% 50%;pointer-events:none;backface-visibility:hidden}
#ceiling .clayer img{display:block;width:100%;height:100%}

/* vignette + a warm bloom at the eye. Siblings — see the note above. */
#ceiling .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}
#ceiling .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. */
#ceiling .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%)}

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

/* the attribution, set as an engraved caption rather than body copy */
#ceiling .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;font-weight:400;}
#ceiling .hand b{color:var(--gold);font-weight:400}

/* ---- reduced motion ------------------------------------------------------
   The shells stop dead. NOT display:none and not a still frame of the
   animation — the nine bands are a partition of unity, so with every transform
   removed they recomposite to exactly the source photograph. */
@media (prefers-reduced-motion:reduce){
  #ceiling .clayer{transform:none !important;filter:none !important}
  #ceiling .cscene{transform:none !important}
}


/* ==== sections/collection.css ==== */
/* ==========================================================================
   §V · THE WALLS — the salon hang
   markup: sections/collection.eta   ·   copy: 05-collection.json   ·   js: collection.js

   OXBLOOD, FROM THE PALETTE. The one coloured wall on the page, because it is
   the one wall in the room that is hung edge to edge, and a salon wall is a
   colour. Gold frames on it, cream labels. The registry marks the section
   dark so the chrome goes cream over it.

   The grid is six columns; the three sizes span 3, 2 and 2 columns at
   different row heights, so the hang reads as a wall rather than a sheet.
   ========================================================================== */
#collection{--ground:#5e2219;color:var(--bone);
   background:radial-gradient(120% 90% at 50% 10%,#7a2d22 0%,#5e2219 55%,#471a13 100%);
   padding:13vh var(--pad-r) 12vh var(--pad)}
#collection .chead{max-width:var(--content-max);margin:0 auto;display:grid;
   grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);gap:3vw;align-items:end}
#collection .chead h2{color:#f2ecdd;font-size:clamp(2.2rem,4.8vw,4rem)}
#collection .chead .body{max-width:44ch;margin-top:0;color:#efe7d7c4;justify-self:end}
#collection .kicker{color:var(--gold)}
#collection .kicker .n{color:#efe7d7a6}

#collection .wall{position:relative;max-width:var(--content-max);margin:7vh auto 0;
   display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:clamp(1.4rem,3vw,3rem) clamp(1.2rem,2.6vw,2.6rem);
   align-items:end}
#collection .series{position:absolute;left:0;top:-3.4rem;margin:0}
#collection .series .lm{color:#efe7d77a}

#collection .work{margin:0}
#collection .sz-l{grid-column:span 3}
#collection .sz-m{grid-column:span 2}
#collection .sz-s{grid-column:span 2;padding:0 8%}
/* the hang: the twelve are placed by span and by the order in the content
   file; rows settle themselves. --i staggers the settle-in across a row. */
#collection .worklink{display:block;position:relative;background:#1b2436;
   box-shadow:0 0 0 1px #e9c46a,0 0 0 4px #5e2219,0 0 0 5px #e9c46a99,0 34px 50px -26px #000c;
   transition:transform .5s ease,box-shadow .5s ease}
#collection .worklink::after{content:"";position:absolute;inset:0;
   box-shadow:inset 0 0 0 1px #0b102066;pointer-events:none}
#collection .worklink:hover{transform:translateY(-4px);
   box-shadow:0 0 0 1px #e9c46a,0 0 0 4px #5e2219,0 0 0 5px #e9c46a,0 44px 60px -26px #000e}
#collection .worklink:focus-visible{outline:2px solid var(--gold);outline-offset:8px}
#collection .work img{display:block;width:100%;height:auto;aspect-ratio:var(--ar)}
#collection .lab .lt{color:#f2ecdd}
#collection .lab .lm{color:#efe7d79e;text-transform:none;letter-spacing:.04em;font-size:12px}

/* ---- the dialog: the whole painting, nothing else ---- */
#collection .artdialog{border:none;padding:0;background:transparent;max-width:min(96vw,1600px);max-height:96vh;
   margin:auto;overflow:visible;color:var(--bone)}
#collection .artdialog::backdrop{background:#0b1020f2}
#collection .artdialog img{display:block;max-width:min(96vw,1600px);max-height:82vh;width:auto;height:auto;margin:0 auto;
   box-shadow:0 0 0 1px #e9c46a,0 0 0 5px #0b1020,0 0 0 6px #e9c46a99,0 40px 90px -30px #000}
#collection .dcap{margin-top:1.3rem;text-align:center;display:grid;gap:.2rem}
#collection .dcap .dt{font-family:var(--display);font-size:1.2rem;color:#f2ecdd}
#collection .dcap .dl{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:#efe7d7a6}
#collection .dclose{position:fixed;top:22px;right:var(--pad);min-height:44px;padding:0 1rem;
   font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
   background:transparent;color:#efe7d7;border:1px solid #efe7d799;border-radius:2px;cursor:pointer}
#collection .dclose:hover{background:#efe7d7;color:var(--ink)}
#collection .dclose:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
#collection .cue{position:relative;left:auto;transform:none;margin-top:9vh;text-align:center;bottom:auto}

/* ---- tablet ---- */
@media (max-width:1000px){
  #collection .chead{grid-template-columns:1fr}
  #collection .chead .body{justify-self:start}
  #collection .wall{grid-template-columns:repeat(2,minmax(0,1fr))}
  #collection .sz-l,#collection .sz-m,#collection .sz-s{grid-column:span 1;padding:0}
  #collection .sz-l{grid-column:span 2}
}
/* ---- phone ---- */
@media (max-width:620px){
  #collection{padding:11vh var(--pad) 10vh}
  #collection .wall{grid-template-columns:1fr;gap:2.6rem}
  #collection .sz-l{grid-column:span 1}
  #collection .series{position:static;margin-bottom:-1rem}
  #collection .dclose{right:var(--pad)}
}


/* ==== 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. */
#sky1877{--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. */
#sky1877 .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 */
#sky1877 .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. */
#sky1877 .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. */
#sky1877 .sky .sr{fill:#efe7d7;r:calc(.0030px*var(--kz,1));
   opacity:calc(var(--o,.1)*var(--r,0))}
#sky1877 .sky .st{fill:#efe7d7;opacity:0}
#sky1877 .sky .st.m0{r:calc(.0115px*var(--kz,1))}
#sky1877 .sky .st.m1{r:calc(.0082px*var(--kz,1))}
#sky1877 .sky .st.m2{r:calc(.0060px*var(--kz,1))}
#sky1877 .sky .st.m3{r:calc(.0042px*var(--kz,1))}
#sky1877 .sky .st.m4{r:calc(.0028px*var(--kz,1))}
#sky1877 .sky .st.m0{opacity:calc(.98*clamp(0,(var(--r,0) - .00)*7,1))}
#sky1877 .sky .st.m1{opacity:calc(.80*clamp(0,(var(--r,0) - .10)*7,1))}
#sky1877 .sky .st.m2{opacity:calc(.58*clamp(0,(var(--r,0) - .22)*7,1))}
#sky1877 .sky .st.m3{opacity:calc(.42*clamp(0,(var(--r,0) - .36)*7,1))}
#sky1877 .sky .st.m4{opacity:calc(.28*clamp(0,(var(--r,0) - .50)*7,1))}
#sky1877 .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)}}
#sky1877 .sky .st.m0,#sky1877 .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. */
#sky1877 .mg{will-change:transform}
#sky1877 .mg0{transform:translate(calc(var(--mx,0)*.020px*var(--kz,1)),calc(var(--my,0)*.016px*var(--kz,1)))}
#sky1877 .mg1{transform:translate(calc(var(--mx,0)*.015px*var(--kz,1)),calc(var(--my,0)*.012px*var(--kz,1)))}
#sky1877 .mg2{transform:translate(calc(var(--mx,0)*.010px*var(--kz,1)),calc(var(--my,0)*.008px*var(--kz,1)))}
#sky1877 .mg3{transform:translate(calc(var(--mx,0)*.006px*var(--kz,1)),calc(var(--my,0)*.005px*var(--kz,1)))}
#sky1877 .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. */
#sky1877 .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 */
#sky1877 .sky .cf .hit{fill:none;stroke:transparent;
   stroke-width:calc(.055*var(--kz,1));
   stroke-linecap:round;stroke-linejoin:round;pointer-events:stroke}
#sky1877 .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}
#sky1877 .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. */
#sky1877 .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));font-weight:400;}
#sky1877 .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. */
#sky1877 .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}

#sky1877 .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 */
#sky1877 .sub{font-style:italic;
   font-size:clamp(1.02rem,1.7vw,1.35rem);line-height:1.3;
   margin:.7rem 0 0;color:#efe7d7cc;font-family:var(--display);font-weight:400;}
/* One standing line under the subtitle. It does not swap, fade or queue — no
   readout, no stop counter, no caption changing under you. */
#sky1877 .stop{margin-top:clamp(1rem,2.4vh,1.9rem);max-width:31rem}
#sky1877 .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. */
#sky1877 .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));font-weight:400;}
#sky1877 .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. */
#sky1877 .inst .live{color:var(--gold);letter-spacing:.16em}
#sky1877 .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. */
#sky1877 .inst .plate{margin-bottom:.55rem;padding-bottom:.5rem;
   border-bottom:1px solid #efe7d72e;color:#efe7d766}

/* ---- phone ---------------------------------------------------------------
   On a phone the whole plate fits the 320px width, so all sixteen names land
   in a third of the area they get on desktop and collide. The FIGURES still
   all draw — only the names go. The instrument keeps its live line and loses
   the coordinates, and sits at the top of the plate, which is empty sky on a
   narrow viewport. */
@media (max-width:760px){
  #sky1877 .sky .cflabel{display:none}
  #sky1877 .cap{bottom:12vh;gap:.9rem}
  #sky1877 .cap h2,#sky1877 .cap .body{max-width:100%}
  #sky1877 .stop{margin-top:.8rem}
  #sky1877 .stop .ln{font-size:.92rem;line-height:1.45}
  #sky1877 .inst{top:64px;right:var(--pad);left:var(--pad);font-size:8.5px;line-height:1.8}
  #sky1877 .inst .plate,#sky1877 .inst .coords{display:none}
}
/* ---- reduced motion ------------------------------------------------------
   No zoom, no drift, no breathing: the finished chart, at rest. .sky KEEPS its
   transform — that transform IS the fit. */
@media (prefers-reduced-motion:reduce){
  #sky1877 .mg{transform:none !important}
  #sky1877 .sky .st{animation:none !important}
  #sky1877 .sky .cf .ln{transition:none !important}
  #sky1877 .sky .cf:not(.hv) .cflabel{opacity:0 !important}
}


/* ==== sections/making.css ==== */
/* ==========================================================================
   §VII · BY HAND — how the room came together
   markup: sections/making.eta   ·   copy: 07-making.json   ·   js: making.js

   Static, on bone. The account on the left, two frames on the right: the
   large one dollies on --hp (the section's own progress across the viewport,
   written by making.js), the small one overlaps its corner.
   ========================================================================== */
#making{--ground:var(--bone);background:var(--bone);padding:14vh var(--pad-r) 12vh var(--pad)}
#making .row{max-width:var(--content-max);margin:0 auto;display:grid;
   grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:6vw;align-items:start}
#making .said{max-width:52ch}
#making h2{font-size:clamp(2.1rem,4.4vw,3.6rem);max-width:16ch}
#making .body{max-width:48ch}
#making .lbl{margin-top:2.4rem;font-family:var(--mono);font-size:11px;letter-spacing:.24em;
   text-transform:uppercase;color:var(--oxblood);font-weight:400;
   padding-bottom:.6rem;border-bottom:1px solid var(--rule-2)}
#making .named{list-style:none;margin:0}
#making .named li{padding:.8rem 0;border-bottom:1px solid var(--rule);font-size:1.02rem;line-height:1.5;color:#1b2436d8}
#making .credits{margin:0}
#making .credits div{display:grid;grid-template-columns:9rem 1fr;gap:1rem;padding:.8rem 0;border-bottom:1px solid var(--rule)}
#making .credits dt{font-family:var(--display);font-size:1.05rem;color:var(--ink)}
#making .credits dd{font-size:1rem;line-height:1.5;color:var(--ink-70)}

#making .figs{position:relative;padding-top:7vh;padding-bottom:4vh}
#making .f1{width:min(78%,calc(68vh * var(--ar)));margin-left:auto}
#making .f1.dolly .pic img{--dp:var(--hp,0)}
/* over the large frame's top-left corner, so its label underneath stays clear */
#making .f2{position:absolute;left:0;top:0;width:min(40%,calc(28vh * var(--ar)));z-index:3}
#making .f2 .lab{display:none}
#making .muse{position:absolute;right:-4%;bottom:-6vh;width:clamp(80px,9vw,130px);opacity:.75;mix-blend-mode:multiply;pointer-events:none}
#making .muse img{width:100%;height:auto;display:block}

@media (max-width:900px){
  #making{padding:12vh var(--pad) 10vh}
  #making .row{grid-template-columns:1fr;gap:6vh}
  #making .figs{order:-1;padding:5vh 0 0}
  #making .f1{width:min(86%,calc(60vh * var(--ar)));margin-left:auto}
  #making .f2{width:44%}
  #making .muse{display:none}
  #making .f1.dolly .pic img{transform:scale(1.04)}
}
@media (max-width:760px){
  #making .credits div{grid-template-columns:1fr;gap:.2rem}
}


/* ==== sections/facade.css ==== */
/* ==========================================================================
   §8b · THE ELEVATION — the facade comes apart, Germania stays
   markup: sections/facade.eta   ·   copy: 10b-facade.json   ·   js: facade.js
   geometry: public/data/facade-elements.json (derived, see tools/facade-elements.py)

   GROUND. #ece0c9, which 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 #collection's ground.

   It is measured for THIS pair of seams, per the note in origin.css:

     #rooms  is var(--ink) #1b2436 — max channel delta 209. Not a seam, a cut.
     #history is var(--bone) #efe7d7 — max channel delta 14 (0xc9 → 0xd7).

   14 sits inside the 14–23 band the page's other cream-to-cream seams already
   ship at, and #history | #contact ships at 9. The alternative creams score
   worse on the second seam: #f3edde is 7 vs history but that is FAINTER than
   the faintest currently on the page, and it is #origin's own ground four
   sections up. If either neighbour moves, recompute both numbers — the grounds
   are flat fills, so this is arithmetic, not a screenshot.

   NOT `dark`. The drawing is black ink on cream and the chrome sits on cream
   above it, exactly as on #collection.
   ========================================================================== */
#facade{--ground:#ece0c9}
#facade .sticky{background:#ece0c9}

/* The outlines. Out of flow and zero-sized, but NOT display:none — a clipPath
   inside a display:none subtree is not reliably resolvable, and a piece whose
   clip fails to resolve is not clipped at all, which is fifty copies of the
   whole elevation stacked on each other. */
#facade .fclips{position:absolute;width:0;height:0;overflow:hidden}

/* The drawing's box. Aspect is the artwork's own 1760×3577, declared here so
   the module can derive every measurement from the viewport height instead of
   reading a rect back off an element the camera is mid-scale on. If the source
   SVG is ever re-traced at another size, this and the `art` field in
   facade-elements.json are the two places that know it. */
#facade .fbuild{
  position:absolute;left:50%;top:50%;
  transform:translate3d(-50%,-50%,0);
  /* 84 and not 88: the shared .cue sits at bottom:28px, and the camera's
     opening drift pushes the drawing 2.5vh down — at 88 the shopfront and the
     cue were in the same 20px of screen, black type on black ink. */
  height:84vh;aspect-ratio:1760 / 3577;
  will-change:transform;
}

/* PERSPECTIVE LIVES ON .fwall, and it is load-bearing. A piece's motion out of
   the wall plane is a rotateY/rotateX and a translateZ and nothing else, and
   with no perspective on an ancestor those are a squash and a no-op.
   It is declared on the pieces' own parent rather than inherited down a
   `transform-style:preserve-3d` chain from .fbuild on purpose: every piece
   carries a clip-path, a clip flattens 3D exactly as a mask does, and a
   flattened chain would silently drop every translateZ.

   NO preserve-3d, which is also deliberate: the pieces are then composited in
   z-index order rather than sorted in 3D, so Germania stays in front of
   everything by declaring it once, below, instead of by out-running the
   pieces that are travelling toward the camera. */
#facade .fwall{
  position:absolute;inset:0;
  perspective:1900px;perspective-origin:50% 50%;
}

/* ONE ARCHITECTURAL ELEMENT — the segmental pediment, the finial, the name
   tablet, a corner pavilion, a string course, one window unit with its own
   hood and apron, a shopfront arch, the entrance.

   Its box is the element's own bounding box, and `clip-path` cuts it to the
   element's own outline, so what moves is the shape of the thing rather than a
   rectangle over it. Both come from public/data/facade-elements.json.

   Every piece paints the WHOLE drawing at the stage's size and shows only its
   own window onto it, so the browser rasterises the SVG once and all fifty
   share that one raster. The outlines carry no holes because they do not need
   any: the artwork is black on transparent, so every window opening and every
   white line inside a piece is transparent already. */
#facade .fpiece{
  position:absolute;
  background-image:url("/site-tests/site-v7/assets/germania-facade-elevation-woodcut.svg");
  background-repeat:no-repeat;
  will-change:transform,opacity;
  backface-visibility:hidden;
}
/* NO mix-blend-mode here, unlike #origin .portrait img. That drawing needed it;
   this one is traced with no paper behind it, so multiply against the ground is
   a no-op on every transparent pixel and identity on every black one — blend
   groups composited every frame for a result no one can tell from normal. */

/* THE AEDICULE — Germania, her arch and her flanking pilasters — is one of the
   fifty pieces and is marked out only by what happens to it. It is the piece
   that does not leave.

   It is laid out at its FINAL on-screen size and scaled DOWN to sit in the
   wall, never up: the obvious way round — a small element scaled up on the way
   out — rasterises the SVG at the size it starts and then magnifies that
   bitmap, so she is at her softest at the exact moment she is largest and is
   the only thing on screen. The module writes that size, because it depends on
   the viewport; with no script she is a piece of the wall like any other and
   the percentages the server wrote are already right. */
#facade .fpiece[data-kind="germania"]{z-index:5}
/* The cut edge, once she is free of the wall. Driven from the module's own
   lift, so the shadow does not exist while she is still part of the facade — a
   plate with a drop shadow sitting flush in a wall reads as a sticker. */
#facade .fpiece[data-kind="germania"]::before{
  content:"";position:absolute;inset:0;
  box-shadow:0 2.5vh 7vh #1b243626;
  opacity:var(--lift,0);pointer-events:none;
}

/* ---- the copy, split around her ----
   She finishes centred, so the type goes either side of her rather than into
   one margin: "The building came apart." on the left, "She did not." on the
   right, and Germania is the full stop in the middle. --fgap is the channel she
   lands in, written by the module from her final width; the fallback is the
   width of the drawing at rest so an unscripted page still keeps the columns
   off the elevation. */
#facade .fcopy{
  position:absolute;inset:0;
  display:grid;align-items:center;
  grid-template-columns:1fr var(--fgap,44vh) 1fr;
  padding:0 var(--pad-r) 0 var(--pad);
  z-index:20;opacity:0;will-change:opacity;
  pointer-events:none;
}
#facade .fcol{transform:translateY(var(--cy,3.2vh))}
/* Placed EXPLICITLY either side of the channel. Auto-placement puts two
   children in columns 1 and 2 — and column 2 is the channel, so the paragraph
   landed on top of her. */
#facade .fcol-a{grid-column:1;justify-self:end;text-align:right;max-width:34ch;padding-right:2.4vw}
/* The heading is set against the ragged left edge the column creates, so where
   it breaks is visible; balanced, it breaks in the middle rather than leaving
   "APART." alone. The kicker is one label and is never broken — the column is
   ~480px at 1440 and the kicker is ~250, so nowrap cannot overflow here, and
   the narrow branch below drops it. */
#facade .fcol-a h2{text-wrap:balance}
#facade .fcol-a .kicker{white-space:nowrap}
#facade .fcol-b{grid-column:3;justify-self:start;max-width:38ch;padding-left:2.4vw}
/* The copy is faded in by the module, so with no script it would never arrive
   and the section would lose its words. Hand them back — an unscripted reader
   gets the elevation and the copy, just not in the order they were meant in. */
@media (scripting:none){
  #facade .fcopy{opacity:1}
  #facade .fcol{transform:none}
}
#facade .fcopy h2{font-size:clamp(1.9rem,3.4vw,3rem);line-height:1.06;margin-top:.4rem}
#facade .fcopy .sub{font-size:clamp(1.4rem,2.2vw,2rem)}
#facade .fcopy .body{max-width:none;margin-top:1.1rem}

/* ---- narrow ----
   Below 900 there are no margins to put anything in, so she takes the upper
   part of the screen and the copy stacks underneath her in one column. The
   module reads the same 900 breakpoint for her final size and resting place;
   the two must not drift apart. */
@media (max-width:900px){
  #facade .fbuild{height:60vh}
  #facade .fcopy{
    grid-template-columns:1fr;align-content:end;
    padding:0 var(--pad-r) 7vh var(--pad);
  }
  #facade .fcol-a,#facade .fcol-b{
    grid-column:1;justify-self:stretch;text-align:left;max-width:none;padding:0;
  }
  #facade .fcol-b{margin-top:.6rem}
  #facade .fcol-a .kicker{white-space:normal}
  #facade .fcopy h2{font-size:clamp(1.7rem,7vw,2.2rem)}
  #facade .fcopy .sub{font-size:clamp(1.3rem,5vw,1.6rem)}
  #facade .fcopy .body{font-size:1rem;line-height:1.55}
}


/* ==== sections/provenance.css ==== */
/* ==========================================================================
   §IX · PROVENANCE — the building, the name, the emblem
   markup: sections/provenance.eta   ·   copy: 09-provenance.json

   Static, bone-2 (a half step from #facade's #ece0c9 so the seam reads as a
   page turn, and from #next's bone the same). Three movements, one column.
   ========================================================================== */
#provenance{--ground:var(--bone-2);background:var(--bone-2);padding:14vh var(--pad-r) 14vh var(--pad)}
#provenance .frame .pic{background:transparent;box-shadow:0 0 0 1px var(--ink),0 0 0 5px var(--bone-2),0 0 0 6px var(--rule-2)}
#provenance .frame .pic img{mix-blend-mode:multiply}

#provenance .intro{max-width:var(--content-max);margin:0 auto;display:grid;
   grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:4vw;align-items:end}
#provenance .intro h2{font-size:clamp(2.2rem,5vw,4.2rem)}
#provenance .lede{font-size:clamp(1.1rem,1.6vw,1.3rem);line-height:1.55;color:var(--ink-70);max-width:46ch;justify-self:end}

#provenance .building{max-width:var(--content-max);margin:7vh auto 0}
#provenance .building .pic{aspect-ratio:4614 / 3069;background:#f3edde}
#provenance .building .pic img{object-fit:contain;padding:2%;background:#f3edde}

/* ---- the five beats ---- */
#provenance .time{max-width:var(--content-max);margin:8vh auto 0;border-top:1px solid var(--rule-2)}
#provenance .ev{display:grid;grid-template-columns:9rem minmax(0,1fr) 11rem;gap:2rem;align-items:start;
   padding:2.2rem 0;border-bottom:1px solid var(--rule-2)}
#provenance .ev .yr{font-family:var(--display);font-size:clamp(1.6rem,2.6vw,2.2rem);line-height:1;color:var(--oxblood)}
#provenance .ev h3{font-family:var(--display);font-size:1.3rem;line-height:1.2;margin-bottom:.5rem}
#provenance .ev p{max-width:62ch;font-size:1.02rem;line-height:1.6;color:#1b2436d0}
#provenance .ev-fig{grid-column:3;justify-self:end;width:11rem;position:relative}
#provenance .ev-fig video,#provenance .ev-fig .still{display:block;width:100%;height:auto}
#provenance .ev-fig .still{display:none}
@media (prefers-reduced-motion:reduce){
  #provenance .ev-fig video{display:none}
  #provenance .ev-fig .still{display:block}
}
#provenance .src{max-width:var(--content-max);margin:1.2rem auto 0;font-family:var(--mono);font-size:11px;
   letter-spacing:.08em;color:var(--ink-45);line-height:1.6;font-weight:400}

/* ---- the name ---- */
#provenance .name{max-width:var(--content-max);margin:12vh auto 0;display:grid;
   grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:6vw;align-items:center}
#provenance .arch{width:min(100%,calc(56vh * 3 / 4))}
#provenance .arch .pic{aspect-ratio:3 / 4;background:#f3edde}
#provenance .arch .pic img{object-fit:contain;padding:4%;background:#f3edde}
#provenance .ncopy h2{font-size:clamp(2rem,4.2vw,3.4rem)}
#provenance .ncopy .body{max-width:48ch}

/* ---- the emblem, drawn, then painted five ways ---- */
#provenance .marks{max-width:var(--content-max);margin:12vh auto 0}
#provenance .mhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:.6rem 2rem;padding-bottom:1.2rem;border-bottom:1px solid var(--rule-2)}
#provenance .mhead .kicker{margin:0}
#provenance .mline{font-family:var(--display);font-style:italic;font-size:clamp(1.1rem,1.8vw,1.4rem);color:var(--ink-70)}
#provenance .mrow{list-style:none;margin:2.4rem 0 0;display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:1.6rem;align-items:end}
#provenance .mrow .frame .pic{aspect-ratio:3 / 2}
#provenance .mrow .drawn .pic{background:#f3edde}
#provenance .mrow .drawn .pic img{object-fit:contain;padding:8%;background:#f3edde}
#provenance .mrow .lab .lt{font-size:.95rem}

@media (max-width:900px){
  #provenance{padding:12vh var(--pad) 12vh}
  #provenance .intro{grid-template-columns:1fr;gap:1.4rem}
  #provenance .lede{justify-self:start}
  #provenance .ev{grid-template-columns:5.5rem minmax(0,1fr);gap:1.2rem;padding:1.6rem 0}
  #provenance .ev-fig{grid-column:1 / -1;justify-self:start;width:8rem}
  #provenance .name{grid-template-columns:1fr;gap:5vh}
  #provenance .arch{width:min(70%,calc(44vh * 3 / 4))}
  #provenance .mrow{grid-template-columns:repeat(3,minmax(0,1fr));gap:1.2rem}
}
@media (max-width:560px){
  #provenance .mrow{grid-template-columns:repeat(2,minmax(0,1fr))}
}


/* ==== sections/next.css ==== */
/* ==========================================================================
   §X · IN ORDER — the three phases, the hours, the hiring box
   markup: sections/next.eta   ·   copy: 10-next.json
   ========================================================================== */
#next{--ground:var(--bone);background:var(--bone);padding:14vh var(--pad-r) 12vh var(--pad)}
#next .nwrap{max-width:var(--content-max);margin:0 auto}
#next .nhead h2{font-size:clamp(2.2rem,5vw,4.2rem)}
#next .phases{list-style:none;margin:6vh 0 0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:3vw;
   border-top:1px solid var(--rule-2)}
#next .phase{position:relative;padding:2rem 0 0;min-height:52vh;display:flex;flex-direction:column}
#next .phase::before{content:"";position:absolute;left:0;top:-1px;width:3.2rem;height:1px;background:var(--ink)}
#next .pnum{font-family:var(--display);font-size:clamp(2rem,3.4vw,2.8rem);line-height:1;color:var(--ink-45)}
#next .is-now .pnum{color:var(--oxblood)}
#next .pwhen{margin-top:1rem;font-family:var(--mono);font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--oxblood);font-weight:400}
#next .phase h3{font-family:var(--display);font-size:clamp(1.5rem,2.3vw,2rem);line-height:1.1;margin-top:.5rem}
#next .phase p{margin-top:.9rem;font-size:1.02rem;line-height:1.55;color:var(--ink-70);max-width:34ch}
#next .pfig{margin-top:auto;padding-top:2rem;width:min(100%,calc(34vh * var(--ar)))}
#next .pdraw{margin-top:auto;padding-top:2rem;width:clamp(90px,9vw,130px);opacity:.8;mix-blend-mode:multiply;align-self:flex-start}
#next .pdraw.tall{width:min(100%,clamp(180px,20vw,300px))}
#next .pdraw img{width:100%;height:auto;display:block}

#next .hours{margin-top:5vh;padding-top:1.2rem;border-top:1px solid var(--rule-2);
   font-family:var(--display);font-style:italic;font-size:clamp(1.1rem,1.8vw,1.4rem);color:var(--ink-70)}
#next .hours .lbl{font-family:var(--mono);font-style:normal;font-size:11px;letter-spacing:.22em;
   text-transform:uppercase;color:var(--oxblood);margin-right:1rem;font-weight:400}

/* the boxed callout the prelaunch brief asks for */
#next .hbox{position:relative;overflow:hidden;margin-top:6vh;border:1.5px solid var(--ink);background:#f3edde;
   padding:clamp(1.6rem,3vw,2.6rem) clamp(1.6rem,3.4vw,3rem);
   display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:1.4rem 3rem}
#next .hbox::before{content:"";position:absolute;inset:6px;border:1px solid var(--rule-2);pointer-events:none}
#next .hbox .kicker{margin:0}
#next .hline{font-family:var(--display);font-size:clamp(1.25rem,2.1vw,1.7rem);line-height:1.2;color:var(--ink);max-width:30ch}
#next .hfig{position:absolute;right:30%;top:50%;width:clamp(80px,8vw,120px);transform:translateY(-50%);
   opacity:.14;mix-blend-mode:multiply;pointer-events:none}
#next .hfig img{width:100%;height:auto}

@media (max-width:900px){
  #next{padding:12vh var(--pad) 10vh}
  #next .phases{grid-template-columns:1fr;gap:0;border-top:none}
  #next .phase{min-height:0;padding:1.6rem 0 2.2rem;border-top:1px solid var(--rule-2)}
  #next .pfig{width:min(70%,calc(34vh * var(--ar)));padding-top:1.4rem}
  #next .pdraw{padding-top:1.2rem}
  #next .hbox{grid-template-columns:1fr;gap:1rem;padding:1.5rem 1.3rem}
  #next .hfig{display:none}
}


/* ==== sections/visit.css ==== */
/* ==========================================================================
   §10 · VISIT (site-v6: #visit) — engraved map, details & mailing list (the close)
   markup: sections/contact.eta + partials/signup.eta   ·   copy: 11-contact.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.
   ========================================================================== */
#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. (While the chapter rail
   existed they landed under its links, which is how this was found.)

   `100%` is the third term, and it is the only one that knows about the parent's
   insets, because .copy already accounts for them. Nothing subtracts
   --rail-gutter here: that would deduct it twice and pull the plate off centre. */
#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 18px padding moved here off .mapwrap so the hit area includes
   the plate's inner margin instead of stopping at the drawing's edge.

   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 a stripe
   of empty paper under it, which reads as the map having failed to fill its
   frame. Centred, the square drawing sits on the plate's optical middle.

   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. */
#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 .mapwrap 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;font-weight:400;}
#visit .maplink:hover .maphint{color:var(--oxblood)}

#visit .info{padding:clamp(1.4rem,2.6vw,2.2rem) 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(1.7rem,2.9vw,2.4rem);max-width:none}
/* the three phases, one line under the heading: bar first, then the rest */
#visit .phases{margin-top:1rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .6rem;
   font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-55);font-weight:400;}
#visit .phases .lbl{color:var(--oxblood);margin-right:.3rem}
#visit .phases .ph b{font-weight:400;color:var(--ink)}
#visit .phases .ph .now{color:var(--oxblood)}
#visit .phases .sep{color:var(--rule-2)}
#visit .rows{margin-top:1.2rem;border-top:1px solid var(--rule-2)}
#visit .row{display:grid;grid-template-columns:82px 1fr;gap:1rem;align-items:baseline;
   padding:.7rem 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);font-weight:400;}
#visit .row .val{font-size:1rem;color:#1b2436d8;line-height:1.45}

/* 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. Its own 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:flex;align-items:center;min-height:36px;margin-top:.1rem;width:fit-content;
   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;font-weight:400;}
#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.2rem;margin-bottom:.5rem;
   font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--ink-55);font-weight:400;}
#visit .signup{display:flex;gap:.55rem;flex-wrap:wrap;position:relative}
#visit .signup input{flex:1;font-family:var(--body);font-size:1rem;padding:.72rem .95rem;min-width:170px;
   background:#f6f0e2;border:1px solid var(--rule-2);color:var(--ink);border-radius:2px}
#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 button 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;font-weight:400;}
#visit .signup input{min-height:44px}
#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 the 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)}

/* Instagram + sister venues. Same mono register as the signoff, one step
   brighter so the links read as links. */
#visit .fmeta{margin-top:1.1rem;display:flex;flex-direction:column;gap:0}
#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;font-weight:400;}
#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 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:38px;
   box-shadow:inset 0 -1px 0 0 transparent;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}
/* 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 a
   missing address is filled in. (This replaced `.ig`, which named the one
   channel that existed rather than the shape it was.) */
#visit .frow .soc{display:inline-flex;align-items:center;gap:.45em}
#visit .frow .soc .ic{width:12px;height:12px;display:block;flex:none}
#visit .frow .soc.is-pending{color:var(--ink-45);min-height:44px}
#visit .frow .soc.is-pending .tk{margin-left:.15em}
#visit .frow .sep{color:var(--rule-2)}

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

/* ---- 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);font-weight:400;}
#visit .map .cface{font-family:var(--mono);font-size:9px;letter-spacing:.1em;fill:var(--oxblood);font-weight:400;}
#visit .map .scl{font-family:var(--mono);font-size:8px;letter-spacing:.06em;fill:var(--ink-45);font-weight:400;}
#visit .map .ctitle{font-style:italic;font-size:20px;fill:var(--ink);font-family:var(--display);font-weight:400;}
#visit .map .csub{font-family:var(--mono);font-size:8px;letter-spacing:.26em;fill:var(--ink-45);font-weight:400;}
#visit .map .mkr{font-family:var(--mono);font-size:9px;letter-spacing:.1em;fill:var(--ink);font-weight:400;}
#visit .map .mkr2{font-style:italic;font-size:9.5px;fill:var(--ink-60);font-family:var(--display);font-weight:400;}
#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}}

/* ---- phone: THE CHAPTER UNPINS HERE --------------------------------------
   Once the plate stacks to one column it is taller than a phone screen, and a
   sticky stage is `height:100vh; overflow:hidden`: the map was cut off at the
   top and the form at the bottom, with no scroll position that showed either.
   So on a phone this becomes an ordinary block as tall as its own content. The
   !important is against page.eta's inline runway height from the registry. */
/* The same un-pinning on a SHORT viewport: at 1024×768 or 1440×700 the plate
   is taller than the stage and its heading sits under the chrome. Only the
   pin goes; the two columns stay until the phone breakpoint below. */
@media (max-width:760px),(max-height:820px){
  #visit{height:auto !important}
  #visit .sticky{position:static;height:auto;overflow:visible;padding:14vh 0 12vh}
  #visit .copy{position:static}
  #visit .plate{transform:none;opacity:1}
  #visit .relic{opacity:.5}
}
@media (max-width:760px){
  #visit .plate{grid-template-columns:1fr}
  #visit .mapwrap{border-right:none;border-bottom:1.5px solid var(--ink)}
}
@media (prefers-reduced-motion:reduce){
  #visit .map .pinring{animation:none !important}
}


/* ==== sections/footer.css ==== */
/* ==========================================================================
   §11 · THE FOOT
   markup: sections/footer.eta   ·   copy: 12-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 §10's cream ellipse down onto ink. The registry's `dark: 0` is
   what 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 twelve sections,
      is the inconsistency that reads as jank. So: 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:none;
   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-style:italic;
   font-size:1.02rem;color:#efe7d7bb;font-family:var(--display);font-weight:400;}

/* the two practical pages, small, after the address */
#footer .flinks{margin:1.6rem 0 0;display:flex;justify-content:center;align-items:center;gap:.8rem;
   font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:#efe7d77a;font-weight:400;}
#footer .flinks a{color:#efe7d7c4;text-decoration:none;display:inline-flex;align-items:center;min-height:44px;
   border-bottom:1px solid #efe7d733;transition:color .3s,border-color .3s}
#footer .flinks a:hover{color:var(--gold);border-bottom-color:currentColor}
#footer .flinks a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}


/* ==== base/page.css ==== */
/* ==========================================================================
   Q · THE PRACTICAL PAGES — /press and /hiring
   markup: sections/press.eta, sections/hiring.eta

   One narrow editorial column with the site's type and nothing else: no stage,
   no runway. A reporter or an applicant came here for a name and an address,
   and the page gives it to them above the fold.
   ========================================================================== */
body.page{background:var(--bone)}
.pg{--ground:var(--bone);background:var(--bone);min-height:100vh;
   padding:clamp(120px,18vh,180px) var(--pad) 12vh}
.pg .wrap{max-width:760px;margin:0 auto}
.pg h1{font-size:clamp(2.3rem,5.6vw,4.2rem);line-height:.98;letter-spacing:-.008em;margin-top:.2rem}
.pg .lede{margin-top:1.4rem;font-size:clamp(1.1rem,1.7vw,1.3rem);line-height:1.55;color:var(--ink-70);max-width:52ch}
.pg .rows{margin-top:2.6rem;border-top:1px solid var(--rule-2)}
.pg .row{display:grid;grid-template-columns:150px 1fr;gap:1.2rem;align-items:baseline;
   padding:1.05rem 0;border-bottom:1px solid var(--rule-2)}
.pg .row .lbl{font-family:var(--mono);font-size:11px;letter-spacing:.22em;text-transform:uppercase;
   color:var(--oxblood);font-weight:400;}
.pg .row .val{font-size:1.06rem;line-height:1.55;color:#1b2436d8;max-width:58ch}
.pg .row .val a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--rule-2);transition:border-color .3s}
.pg .row .val a:hover{border-bottom-color:currentColor}
.pg .row .val .sub{display:block;margin-top:.2rem;font-family:var(--mono);font-size:11px;letter-spacing:.12em;
   text-transform:uppercase;color:var(--ink-45);font-style:normal}
.pg .row .val .tk{font-size:.8em}
.pg .fig{float:right;width:clamp(72px,9vw,110px);margin:0 0 1rem 2rem;mix-blend-mode:multiply;opacity:.9}
.pg .fig img{width:100%;height:auto}
.pg .back{display:inline-flex;align-items:center;min-height:44px;margin-top:2.6rem;
   font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
   color:var(--ink-70);text-decoration:none;border-bottom:1px solid var(--rule-2);font-weight:400;}
.pg .back:hover{color:var(--oxblood);border-bottom-color:currentColor}
.pg .note{margin-top:2rem;font-size:1rem;line-height:1.6;color:var(--ink-70);max-width:56ch}
@media (max-width:760px){
  .pg{padding-top:150px}
  .pg .row{grid-template-columns:1fr;gap:.3rem}
  .pg .fig{float:none;margin:0 0 1.4rem}
}


/* ==== sections/press.css ==== */


/* ==== sections/hiring.css ==== */


/* ==== base/responsive.css ==== */
/* ==========================================================================
   Z · RESPONSIVE & REDUCED MOTION — the GENERIC rules only

   MUST STAY LAST IN THE BUNDLE: a few rules here re-declare selectors from
   earlier files at equal specificity and win on source order.

   v5 kept every section's media queries in this one file, keyed by #id. When
   sections were renamed for v6 those rules would have died silently — nothing
   warns about a dead #id in base/. So each section's breakpoints now live at
   the END of its own sections/<id>.css, and this file carries only what is
   true of the whole page: the pad, the chrome, the wordmark, Reduce Motion.
   ========================================================================== */

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

  /* The chrome is fixed, so long copy scrolls underneath it. A scrim only
     hides that. Instead the chrome scrolls away with the page at this width —
     the header links are reachable again from the foot of the page. */
  .mark,.topnav{position:absolute}
  .mark img{width:44px}
  /* Six links on a page (five sections plus the page's own name) do not fit
     one row beside the mark at 390px; they wrap, right-aligned, under it. */
  .topnav{top:30px;gap:0 .8rem;flex-wrap:wrap;justify-content:flex-end;height:auto;
     max-width:calc(100vw - 2*var(--pad) - 64px)}
  .topnav a{font-size:9.5px;letter-spacing:.14em;min-height:36px}
  h2{font-size:clamp(1.9rem,8.4vw,2.6rem)}
  .body{font-size:1.02rem}
}

/* ---- reduced motion -----------------------------------------------------
   Everything stops. The engine pins --p to 1, so every chapter rests at its
   settled state; the per-section exceptions (the ceiling rests at 0, the
   chart is pre-drawn) live beside the rules they override. */
@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{animation:none !important}
}
