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


/* ==== 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;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/pair.css ==== */
/* ==========================================================================
   P · THE WIPE — a painting that becomes a photograph (or the reverse)
   used by: sections/arrival, glass, table, making

   Two images of the same frame are stacked in one box. The top one is masked
   by a gradient twice the box's size, and the mask's POSITION is driven by the
   chapter's --p. As --p runs 0→1 the mask's opaque half slides across the box,
   so the top image arrives through a soft edge — a few vh of feather — rather
   than a hard cut. Only mask-position changes per frame, which is a repaint of
   one layer, not a regenerated gradient.

   Three directions, as modifier classes on .wipe:
     .wipe-up     reveals from the bottom edge upward   (the building lights up)
     .wipe-down   reveals from the top edge downward
     .wipe-right  reveals from the left edge rightward

   --wp is the progress the wipe reads. It defaults to the stage's --p (which
   the engine writes on .sticky and the box inherits); a static section can
   point it at --hp instead. :root{--p:0} covers first paint.

   REDUCE MOTION: the engine pins --p to 1, so the top image is simply shown.
   With JS off --p stays at the :root 0 and the top image would never arrive —
   so the box carries a `nojs` fallback written by main.js: body gets `.js`
   on boot, and without it the mask is removed and the photograph shows.
   ========================================================================== */
.wipe{position:relative;overflow:hidden}
.wipe>img,.wipe>picture,.wipe>picture>img{display:block;width:100%;height:100%;object-fit:cover}
.wipe>.under{position:absolute;inset:0}
.wipe>.over{position:absolute;inset:0}
.wipe>.over img{width:100%;height:100%;object-fit:cover}
/* --wp lives on the FIGURE (.wipefig), so the image and the two labels under
   it read the same number. A chapter re-maps it here, e.g. to finish early. */
.wipefig{--wp:var(--p,0)}

/* THE MASK IS THREE TIMES THE BOX. With a 200% mask the feather sat across the
   box's edge at rest and leaked a strip of photograph at --p 0. At 300% the
   box covers the mask's first third at 0 (all transparent) and its last third
   at 1 (all opaque), and the 4%-of-mask feather — about 12% of the box — only
   ever crosses the box between .2 and .75. */
.wipe-up>.over{
  -webkit-mask-image:linear-gradient(to bottom,transparent 0%,transparent 46%,#000 50%,#000 100%);
          mask-image:linear-gradient(to bottom,transparent 0%,transparent 46%,#000 50%,#000 100%);
  -webkit-mask-size:100% 300%;mask-size:100% 300%;
  -webkit-mask-position:0 calc(var(--wp)*100%);mask-position:0 calc(var(--wp)*100%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.wipe-down>.over{
  -webkit-mask-image:linear-gradient(to bottom,#000 0%,#000 50%,transparent 54%,transparent 100%);
          mask-image:linear-gradient(to bottom,#000 0%,#000 50%,transparent 54%,transparent 100%);
  -webkit-mask-size:100% 300%;mask-size:100% 300%;
  -webkit-mask-position:0 calc((1 - var(--wp))*100%);mask-position:0 calc((1 - var(--wp))*100%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.wipe-right>.over{
  -webkit-mask-image:linear-gradient(to right,#000 0%,#000 50%,transparent 54%,transparent 100%);
          mask-image:linear-gradient(to right,#000 0%,#000 50%,transparent 54%,transparent 100%);
  -webkit-mask-size:300% 100%;mask-size:300% 100%;
  -webkit-mask-position:calc((1 - var(--wp))*100%) 0;mask-position:calc((1 - var(--wp))*100%) 0;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}

/* No script: no --p will ever be written, so show the finished state. */
body:not(.js) .wipe>.over{-webkit-mask-image:none;mask-image:none}

/* The two little labels under a pair — "as painted" / "as poured". They swap
   at the midpoint rather than crossfade: opacity outside 0–1 is clamped by the
   browser, so a steep calc is a switch with a 5%-of-runway edge. */
.wipe-tags{position:relative;height:1.6em;margin-top:.8rem}
.wipe-tags span{position:absolute;left:0;top:0;white-space:nowrap}
.wipe-tags .was{opacity:calc(1 - (var(--wp,0) - .45)*20)}
.wipe-tags .now{opacity:calc((var(--wp,0) - .5)*20)}
body:not(.js) .wipe-tags .was{opacity:0}
body:not(.js) .wipe-tags .now{opacity:1}
@media (prefers-reduced-motion:reduce){
  .wipe-tags .was{opacity:0}
  .wipe-tags .now{opacity:1}
}


/* ==== sections/arrival.css ==== */
/* ==========================================================================
   §1 · ARRIVAL — the building, drawn, becomes the building, photographed
   markup: sections/arrival.eta   ·   copy: 01-arrival.json
   ========================================================================== */
#arrival{--ground:var(--bone)}
#arrival .sticky{background:var(--bone);display:grid;
   grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);align-items:center;
   gap:4vw;padding:0 var(--pad)}

/* ---- the copy ---- */
#arrival .acopy{position:relative;z-index:20;padding-top:2vh}
.wordmark-h1{font-size:0;line-height:0;letter-spacing:0;margin:0}
/* Not oversized: the wordmark authenticates the page, it does not consume it.
   The rejected v6 drew it 1,200px wide; this is a third of the column. */
#arrival .wordmark{width:min(28vw,330px);height:auto;display:block;mix-blend-mode:multiply;
   will-change:transform;transform:translate3d(calc(var(--mx,0)*-2px),calc(var(--my,0)*-2px),0)}
#arrival .hero-line{margin-top:2rem;font-family:var(--display);font-style:italic;
   font-size:clamp(1.3rem,2.2vw,1.75rem);line-height:1.2;color:var(--ink);max-width:22ch}
#arrival .hero-where{margin-top:.9rem;font-family:var(--mono);font-size:12px;letter-spacing:.2em;
   text-transform:uppercase;color:var(--ink-55);font-weight:400;}
#arrival .hero-status{margin-top:.55rem;font-size:1.02rem;line-height:1.5;color:var(--ink-70);max-width:40ch}

/* the three phases, stated up front */
#arrival .phasing{margin-top:1.8rem;display:inline-flex;flex-wrap:wrap;align-items:center;gap:.4rem 0}
#arrival .ph{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
   color:var(--ink-55);padding:0 1.1rem 0 0;margin-right:1.1rem;position:relative;font-weight:400;}
#arrival .ph:not(:first-child)::before{content:"";position:absolute;left:-1.1rem;top:50%;width:1px;height:28px;
   background:var(--rule);transform:translateY(-50%)}
#arrival .ph b{color:var(--ink);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}
#arrival .ph .now{color:var(--oxblood)}
#arrival .actions{margin-top:1.9rem;display:flex;flex-wrap:wrap;gap:.7rem}

/* ---- the building ---- */
#arrival .abuild{position:relative;z-index:10;justify-self:center;
   height:84vh;aspect-ratio:1160 / 1986;margin:0;
   will-change:transform;transform:translate3d(calc(var(--mx,0)*5px),calc(var(--my,0)*4px),0)}
#arrival .wipe{height:100%}
#arrival .draw{object-fit:fill;mix-blend-mode:multiply;opacity:.9}
/* the wipe finishes a little before the stage releases, so the reader sees
   her stand there photographed before the night chapters begin */
#arrival .abuild{--wp:clamp(0,calc(var(--p,0)*1.2),1)}
#arrival figcaption{display:flex;justify-content:space-between;gap:1rem;margin-top:.8rem;
   white-space:nowrap}
#arrival .wipe-tags{height:auto;margin:0;min-width:9em;text-align:right}
#arrival .wipe-tags span{left:auto;right:0}

#arrival .cue{color:var(--ink-45)}

/* ---- short laptops ---- */
@media (max-height:760px){
  #arrival .abuild{height:80vh}
  #arrival .wordmark{width:min(24vw,280px)}
  #arrival .hero-line{margin-top:1.2rem}
  #arrival .phasing{margin-top:1.2rem}
  #arrival .actions{margin-top:1.3rem}
}
/* the scroll cue sits centre-bottom, which below ~1150px is where the
   building's caption lands; the caption is the more useful of the two */
@media (max-width:1150px){
  #arrival .cue{display:none}
}
/* ---- tablet ---- */
@media (max-width:900px){
  #arrival .sticky{grid-template-columns:1fr;grid-template-rows:auto 1fr;align-items:start;
     gap:2vh;padding-top:84px;padding-bottom:40px}
  #arrival .abuild{height:auto;width:min(44vw,36vh * 1160 / 1986);aspect-ratio:1160 / 1986;
     align-self:end;justify-self:end;margin-right:2vw}
  #arrival .acopy{padding-top:0}
  #arrival .wordmark{width:min(44vw,260px)}
}
/* ---- phone ---- */
@media (max-width:760px){
  #arrival .sticky{padding-top:76px;gap:1vh}
  #arrival .wordmark{width:52vw}
  #arrival .hero-line{margin-top:1rem;font-size:1.15rem;max-width:none}
  #arrival .hero-where{font-size:11px}
  #arrival .hero-status{font-size:.95rem}
  #arrival .phasing{margin-top:1rem;gap:.3rem .9rem}
  #arrival .ph{padding-right:0;margin-right:0}
  #arrival .ph::before{display:none}
  #arrival .ph b{font-size:.92rem;display:inline;margin:0 .35em 0 0}
  #arrival .actions{margin-top:1rem;gap:.5rem}
  #arrival .cta{font-size:10px;padding:0 .9rem;min-height:40px}
  #arrival .abuild{width:min(50vw,30vh * 1160 / 1986);justify-self:center;margin-right:0}
  #arrival figcaption{display:none}
  #arrival .cue{display:none}
}


/* ==== sections/glass.css ==== */
/* ==========================================================================
   §2 · GLASS and §3 · TABLE — the pairs (both styled here; table.css is empty)
   markup: partials/pair.eta via sections/glass.eta, table.eta
   copy: 02-glass.json, 03-table.json

   Night ground. Colour arrives with the photographs and leaves with them: the
   three chapters glass → table → ceiling are one deliberate dark interval, and
   each carries its own picture, so the beat between them is the picture
   changing, not the ground. The registry marks them `dark: 0`, which inverts
   the chrome and this type (`.ink` in type.css).
   ========================================================================== */
#glass,#table{--ground:var(--night)}
#glass .sticky,#table .sticky{background:var(--night)}

.pstage .const{position:absolute;width:min(44vw,520px);opacity:.14;pointer-events:none}
.pstage .const img{width:100%;height:auto}

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

.pfig{position:relative;justify-self:center;margin:0;height:78vh;aspect-ratio:var(--ar);max-width:100%}
.pfig .wipe{height:100%;background:#000;box-shadow:0 40px 80px -30px #000c}
/* the relic in the margin, cream on night: the same inversion every ink
   drawing gets on a dark ground (night.css, rooms.css in v5) */
.pfig .prelic{position:absolute;width:min(8vw,110px);right:-7%;bottom:7%;z-index:3;pointer-events:none;
   filter:invert(1) brightness(1.6);mix-blend-mode:screen;opacity:.85}
.pair-right .pfig .prelic{right:auto;left:-7%;bottom:auto;top:8%}
.pfig .prelic img{width:100%;height:auto;display:block}
.pfig figcaption{display:flex;justify-content:space-between;gap:1rem;margin-top:.8rem;white-space:nowrap}
.pfig .wipe-tags{height:auto;margin:0;min-width:8em}
.pfig .pcap{color:#efe7d77a}

.pcopy{max-width:46ch}
.pcopy h2{max-width:14ch}
.pcopy .sub{max-width:30ch}
.pcopy .pnote{margin-top:1.4rem;font-family:var(--mono);font-size:12px;letter-spacing:.12em;
   text-transform:uppercase;line-height:1.7;color:#efe7d79e;max-width:34ch;font-weight:400;}
.pcopy .psig{width:clamp(64px,6.5vw,92px);margin-top:1.8rem;opacity:.8;
   filter:invert(1) brightness(1.6);mix-blend-mode:screen}
.pcopy .psig img{width:100%;height:auto;display:block}

/* ---- short laptops ---- */
@media (max-height:760px){
  .pfig{height:72vh}
  .pcopy .psig{display:none}
}
/* ---- tablet ---- */
@media (max-width:900px){
  .pair{grid-template-columns:1fr;grid-template-rows:auto 1fr;align-items:start;gap:3vh;
     padding-top:84px;padding-bottom:40px}
  .pair-right .pfig{order:0}
  .pfig{height:auto;width:min(60vw,44vh * var(--ar));aspect-ratio:var(--ar);justify-self:start}
  .pfig figcaption{display:none}
  .pcopy h2{max-width:none}
  .pcopy .psig{display:none}
  .pstage .const{display:none}
}
/* ---- phone ---- */
@media (max-width:760px){
  .pair{padding-top:72px;gap:2vh}
  .pfig{width:min(74vw,40vh * var(--ar));justify-self:center}
  .pfig .prelic{display:none}
  .pcopy .body{margin-top:.9rem}
  .pcopy .pnote{margin-top:.9rem;font-size:11px}
  #glass .cue,#table .cue{display:none}
}


/* ==== sections/table.css ==== */
/* §3 · TABLE — styled with §2 in glass.css (the pair partial). */


/* ==== 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/making.css ==== */
/* ==========================================================================
   §5 · MAKING — four years, a thousand decisions
   markup: sections/making.eta   ·   copy: 05-making.json   ·   js: making.js
   ========================================================================== */
#making{--ground:var(--bone);background:var(--bone);position:relative;overflow:hidden;
   padding:16vh var(--frame-end) 14vh var(--frame-start)}
#making .row{max-width:var(--content-max);margin:0 auto;display:grid;
   grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(2rem,6vw,5rem);align-items:start}
#making h2{font-size:clamp(2rem,3.9vw,3.4rem);max-width:16ch}
#making .said .body{max-width:40ch;font-size:1.08rem}
#making .said .body+.body{margin-top:1rem}
#making .lbl{margin-top:2.2rem;font-family:var(--mono);font-size:11px;letter-spacing:.24em;
   text-transform:uppercase;color:var(--oxblood);font-weight:400;}
#making .named{list-style:none;margin-top:.6rem;max-width:44ch;border-top:1px solid var(--rule-2)}
#making .named li{padding:.75rem 0;border-bottom:1px solid var(--rule-2);font-size:1.02rem;line-height:1.5;color:#1b2436c8}
#making .credits{margin-top:.6rem;max-width:44ch;border-top:1px solid var(--rule-2)}
#making .credits div{display:grid;grid-template-columns:110px 1fr;gap:1rem;padding:.7rem 0;
   border-bottom:1px solid var(--rule-2);align-items:baseline}
#making .credits dt{font-family:var(--display);font-size:1.08rem;color:var(--ink)}
#making .credits dd{font-size:.98rem;line-height:1.5;color:var(--ink-70)}

#making .figs{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:1.6rem;align-items:end;padding-top:2vh}
#making figure{margin:0}
#making .f1{grid-column:1 / span 2;width:min(100%,32vw);justify-self:end}
#making .f1 .wipe{aspect-ratio:var(--ar);background:#000}
/* the photograph becomes the painting as the section crosses the viewport:
   --hp is 0 as the top enters and 1 as the foot leaves, and the wipe runs over
   the middle three fifths of that */
#making .f1{--wp:clamp(0,calc(var(--hp,0)*1.7 - .35),1)}
#making .f1 .wipe-tags{height:auto;margin:0;min-width:7em}
#making .f1 figcaption{display:flex;justify-content:space-between;gap:1rem;margin-top:.7rem}
#making .f2{grid-column:1 / span 2;width:min(100%,27vw);justify-self:start;margin-top:1.4rem}
#making .f2 img{width:100%;height:auto;display:block;aspect-ratio:var(--ar);box-shadow:0 30px 60px -30px #1b243599}
#making .f2 figcaption{margin-top:.7rem}
#making .muse{position:absolute;right:0;bottom:-2%;width:clamp(110px,10vw,150px);opacity:.4;
   mix-blend-mode:multiply;pointer-events:none;
   transform:translate3d(calc(var(--mx,0)*8px),calc(var(--my,0)*8px),0)}
#making .muse img{width:100%;height:auto}

@media (max-width:900px){
  #making .row{grid-template-columns:1fr}
  #making .figs{padding-top:1vh;gap:1.2rem}
  #making .f1{width:min(100%,60vw);justify-self:center}
  #making .f2{width:min(100%,72vw);justify-self:center;margin-top:1rem}
  #making .muse{display:none}
}
@media (max-width:760px){
  #making{padding-top:12vh}
  #making .f1{width:86vw}
  #making .f2{width:86vw}
  #making .credits div{grid-template-columns:90px 1fr}
}


/* ==== 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-v6/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/story.css ==== */
/* ==========================================================================
   §8 · THE BUILDING — real history; the one chapter that stops moving
   markup: sections/history.eta   ·   copy: 10-history.json

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

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

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

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

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

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

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

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

#story .time{max-width:820px;margin:0 auto;position:relative}
#story .time::before{content:"";position:absolute;left:0;top:.5rem;bottom:.5rem;width:1px;background:var(--rule)}
#story .ev{position:relative;padding:0 0 clamp(2.6rem,5vw,4rem) clamp(2.2rem,5vw,3.4rem)}
#story .ev::before{content:"";position:absolute;left:-3.5px;top:.55rem;width:8px;height:8px;
   border-radius:50%;background:var(--oxblood)}
#story .ev:last-child{padding-bottom:0}
#story .ev .yr{font-family:var(--mono);font-size:11px;letter-spacing:.2em;color:var(--oxblood);margin-bottom:.7rem;font-weight:400;}
#story .ev h3{font-size:clamp(1.35rem,2.6vw,1.9rem);margin-bottom:.7rem;font-weight:500;line-height:1.15}
#story .ev p{font-size:1.04rem;line-height:1.64;color:var(--ink-70);max-width:62ch}
#story .ev p em{font-style:italic;color:var(--ink);font-family:var(--display);font-weight:400;}
/* One line of verse in the timeline. Set small and italic, uncredited, no
   explanation offered. */
#story .ev .verse{margin-top:1.1rem;font-style:italic;font-size:1.02rem;
   color:var(--ink-60);max-width:62ch;font-family:var(--display);font-weight:400;}
/* the figure in the music beat — uncaptioned, off to the side, small */
#story .ev.has-fig{padding-right:clamp(0px,16vw,210px)}
/* Faint pointer parallax so the timeline figures float a little under the
   cursor — the same living-under-the-pointer quality the menu relics and the
   collection have, and the second half of giving this static section depth.
   They drift further than the façade (8px vs 5) because they are smaller and
   read as nearer. #story has overflow:hidden, so any drift past the edge
   clips rather than adding a scrollbar. */
#story .ev-fig{position:absolute;right:0;top:-1.5rem;width:clamp(110px,14vw,190px);
   opacity:.5;pointer-events:none;will-change:transform;
   transform:translate3d(calc(var(--mx,0)*8px),calc(var(--my,0)*8px),0)}
#story .ev-fig img{width:100%;height:auto;mix-blend-mode:multiply}
/* The animated crane is a transparent video (see history.eta), so it needs
   nothing the SVG figures don't: the .ev-fig opacity and drift apply as they
   are, and there is deliberately NO mix-blend-mode here — .ev-fig's opacity
   and .time's z-index are both stacking contexts, so a blend on the video
   would only ever meet transparency, never the ground. Under Reduce Motion
   the video goes and the poster frame stands in. */
#story .ev-vid video{display:block;width:100%;height:auto}
#story .ev-vid .still{display:none}
@media (prefers-reduced-motion:reduce){
  #story .ev-vid video{display:none}
  #story .ev-vid .still{display:block}
}

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

/* ---- tablet / phone ------------------------------------------------------ */
@media (max-width:900px){
  #story .intro{grid-template-columns:1fr}
  #story .ev.has-fig{padding-right:0}
  #story .ev-fig{position:relative;right:auto;top:auto;margin-top:1.4rem;width:clamp(96px,26vw,150px)}
}
@media (max-width:760px){
  #story .ev p{max-width:none}
}
@media (max-width:374px){
  #story .intro h2{font-size:1.85rem}
}


/* ==== 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/collection.css ==== */
/* ==========================================================================
   §9 · THE COLLECTION — the wall
   markup: sections/collection.eta   ·   copy: 09-collection.json   ·   js: collection.js

   THE TRANSLATE IS CSS. The stage is an inline-size container, so the wall can
   be told "move so your right edge meets the viewport's right inset at p=1":
   translateX(p × (100cqw − 2·pad − width)). `100%` inside translate is the
   element's OWN width, and cqw is the stage's, which avoids the scrollbar
   error of 100vw. Nothing is measured in script.
   ========================================================================== */
#collection{--ground:var(--bone-2)}
#collection .sticky{background:var(--bone-2);container-type:inline-size;display:block}

#collection .chead{position:absolute;left:var(--pad);top:clamp(96px,13vh,140px);z-index:5;max-width:44ch}
#collection .chead h2{font-size:clamp(2rem,4.2vw,3.6rem)}
#collection .chead .body{max-width:40ch;margin-top:1rem}
#collection .walk{margin-top:1.2rem;color:var(--oxblood)}
#collection .walk .arr{display:inline-block;animation:walk 2.6s ease-in-out infinite}
@keyframes walk{0%,100%{transform:translateX(0)}50%{transform:translateX(6px)}}

#collection .wall{position:absolute;left:var(--pad);bottom:7vh;display:flex;align-items:flex-end;
   gap:3.2vw;padding-right:var(--pad);will-change:transform;
   transform:translateX(calc(var(--p,0) * (100cqw - var(--pad) - var(--pad) - 100%)))}
#collection .series{writing-mode:vertical-rl;transform:rotate(180deg);align-self:center;margin-right:.6vw;white-space:nowrap}
#collection .work{margin:0;flex:none;width:calc(44vh * var(--ar));max-width:70vw}
#collection .worklink{display:block;background:#1b2436;box-shadow:0 26px 50px -26px #1b2436aa;
   transition:transform .5s ease}
#collection .worklink:hover{transform:translateY(-4px)}
#collection .worklink:focus-visible{outline:2px solid var(--oxblood);outline-offset:6px}
#collection .work img{display:block;width:100%;height:auto;aspect-ratio:var(--ar)}
#collection figcaption{margin-top:.8rem;display:grid;gap:.15rem}
#collection .wt{font-family:var(--display);font-size:1.05rem;color:var(--ink)}
#collection .wl{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--ink-45);line-height:1.5}

/* ---- 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:84vh;width:auto;height:auto;margin:0 auto;
   box-shadow:0 40px 90px -30px #000}
#collection .dcap{margin-top:1rem;text-align:center;font-family:var(--display);font-size:1.15rem;color:#efe7d7d8}
#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}

/* ---- short laptops ---- */
@media (max-height:760px){
  #collection .work{width:calc(38vh * var(--ar))}
  #collection .chead{top:84px}
  #collection .chead .body{display:none}
}
/* ---- tablet / phone: a native row ------------------------------------------
   The stage un-pins (the !important is against page.eta's inline runway
   height from the registry) and the wall scrolls sideways under the thumb. */
@media (max-width:900px){
  #collection{height:auto !important}
  #collection .sticky{position:static;height:auto;overflow:visible;padding:13vh 0 10vh}
  #collection .chead{position:static;padding:0 var(--pad);max-width:none}
  #collection .walk .arr{animation:none}
  #collection .wall{position:static;transform:none;margin-top:4vh;padding:0 var(--pad);
     overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;
     scrollbar-width:none;-webkit-overflow-scrolling:touch;gap:5vw}
  #collection .wall::-webkit-scrollbar{display:none}
  #collection .series{writing-mode:horizontal-tb;transform:none;align-self:flex-start;
     position:absolute;left:var(--pad);margin-top:-1.6rem}
  #collection .work{width:calc(42vh * var(--ar));max-width:78vw;scroll-snap-align:center}
  #collection .cue{display:none}
}
@media (max-width:760px){
  #collection .work{width:78vw;max-width:78vw}
  #collection .dclose{right:var(--pad)}
}


/* ==== sections/hire.css ==== */
/* ==========================================================================
   §9b · NOW HIRING — the boxed callout
   markup: sections/hire.eta   ·   copy: 09b-hire.json
   ========================================================================== */
#hire{--ground:#f3edde;background:#f3edde;padding:9vh var(--pad)}
#hire .hwrap{max-width:var(--content-max);margin:0 auto}
#hire .hbox{position:relative;overflow:hidden;border:1.5px solid var(--ink);background:var(--bone);
   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}
#hire .hbox::before{content:"";position:absolute;inset:6px;border:1px solid var(--rule-2);pointer-events:none}
#hire .kicker{margin:0}
#hire .hline{font-family:var(--display);font-size:clamp(1.25rem,2.1vw,1.7rem);line-height:1.2;color:var(--ink);max-width:30ch}
#hire .hfig{position:absolute;right:30%;top:50%;width:clamp(80px,8vw,120px);transform:translateY(-50%);
   opacity:.14;mix-blend-mode:multiply;pointer-events:none}
#hire .hfig img{width:100%;height:auto}
@media (max-width:760px){
  #hire{padding:7vh var(--pad)}
  #hire .hbox{grid-template-columns:1fr;gap:1rem;padding:1.5rem 1.3rem}
  #hire .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}
}
