/* ==========================================================================
   Meet the Moment — main.css
   First draft. Design system derived from Website_research1.pdf.
   See README.md for the decisions taken where the deck offered alternatives.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   Special Elite is a Google webfont (not "web safe" — see README).
   Self-hosted here so there is no third-party request and no FOIT.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Special Elite";
  src: url("../fonts/special-elite.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  /* Trim the fallback's metrics toward Special Elite's so the hero doesn't
     jump when the webfont lands. Ignored by browsers that don't support it. */
  size-adjust: 100%;
  ascent-override: 90%;
  descent-override: 22%;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Ink is the warm near-black measured off the deck's wireframes.
     It is deliberately NOT #000 — the warmth is doing work against the white. */
  --ink: #231f20;
  --ink-90: #34302f;
  --ink-60: #6a635f;
  --ink-30: #b4aeaa;
  --paper: #ffffff;
  --paper-2: #f4f3f1;
  --rule: #ddd9d4;

  /* The only hue in the system, lifted from the placeholder photography.
     Reserved for interactive state and focus. Never decorative. */
  --sky: #1567b5;
  --sky-dark: #0f4f8c;
  --sky-soft: #e4eef8;

  /* Semantic — form feedback only */
  --ok: #1f6b45;
  --err: #a32b1f;

  --display: "Special Elite", "Courier New", Courier, monospace;
  --body: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* 8px spacing base */
  --sp-1: 0.5rem;
  --sp-2: 1rem;
  --sp-3: 1.5rem;
  --sp-4: 2rem;
  --sp-5: 3rem;
  --sp-6: 4rem;
  --sp-7: 6rem;
  --sp-8: 8rem;

  --maxw: 1440px;
  --gutter: 24px;
  --pad: 24px;

  --header-h: 108px;

  /* Derived so black blocks can overhang by exactly half a column.
     A percentage margin on a grid item resolves against its own grid AREA,
     not the container, so the overhang has to be computed from the viewport. */
  --content-w: min(calc(100vw - (var(--pad) * 2)), calc(var(--maxw) - (var(--pad) * 2)));
  --col-w: calc((var(--content-w) - (var(--gutter) * 11)) / 12);
  --half-col: calc((var(--col-w) + var(--gutter)) / 2);
}

/* The stepped assemblage needs room to read. Once the nav collapses, the
   header becomes a single block and no longer needs the extra height. */
@media (max-width: 900px) {
  :root { --header-h: 72px; }
}

@media (max-width: 640px) {
  :root {
    --pad: 20px;
    --gutter: 16px;
    --header-h: 64px;
  }
}

/* --------------------------------------------------------------------------
   3. Reset & base
   -------------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header */
  scroll-padding-top: calc(var(--header-h) + var(--sp-3));
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

/* --------------------------------------------------------------------------
   4. Typography

   TYPE RULE: Special Elite carries every heading level, down to 24px — which
   is how the deck uses it (h1 and quotes large; "Sub Title", "Smaller Title",
   "Act Now" at ~25px on p.8, p.14, p.18).

   Below 24px it goes to Roboto, because that is where the distressed outline
   stops resolving: nav, buttons, form labels and fields, table text, captions
   and the meta lines. In the deck those small items were also set in Special
   Elite; that is the one place this build deliberately departs from it.
   -------------------------------------------------------------------------- */

h1,
h2,
.quote {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0;
  text-wrap: balance;
  margin: 0 0 var(--sp-3);
}

h1 {
  /* min 32px */
  font-size: clamp(2rem, 5.2vw, 3.6rem);
  line-height: 1.08;
}

h2 {
  /* min 28px — the floor */
  font-size: clamp(1.75rem, 3.6vw, 2.6rem);
  line-height: 1.14;
}

/* Sub-headings — "Sub Title", "Smaller Title", "Act Now" in the deck.
   The wireframes set these in Special Elite at roughly 25px (p.8, p.14, p.18),
   sentence case, so they stay in the display face here too. That puts the
   practical floor at 24px rather than 28px; below this the distressed outline
   stops resolving, which is why nav, buttons, labels and table text are Roboto. */
h3,
.title-s {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1.2;
  letter-spacing: 0;
  text-transform: none;
  margin: 0 0 var(--sp-1);
}

.card h3,
.listing h3 {
  font-size: 1.5rem;
  margin-bottom: var(--sp-1);
}

/* A long headline set in a distressed face needs a shorter line than body copy */
.listing h3 { max-width: 34ch; }

h4 {
  font-family: var(--body);
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.4;
  margin: 0 0 var(--sp-1);
}

/* The deck sets body copy to roughly 60 characters (p.8, p.14, p.19), which is
   noticeably tighter than a 12-column span would give on its own. */
p {
  margin: 0 0 var(--sp-2);
  max-width: 62ch;
}

.lede {
  font-size: 1.1875rem;
  line-height: 1.55;
  max-width: 54ch;
}

.quote {
  font-size: clamp(1.75rem, 4vw, 2.9rem);
  line-height: 1.24;
  max-width: 20ch;
}

/* The closing couplet holds its two lines. At the 20ch measure above,
   "We meet it — together." breaks mid-phrase and leaves a bar ending on an
   em dash, which reads as a mistake rather than a device. */
.quote--couplet { max-width: 26ch; }

a {
  color: var(--sky);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

a:hover {
  text-decoration-thickness: 2px;
}

/* Sky-blue focus ring — the reason the palette needed an accent at all */
:focus-visible {
  outline: 3px solid var(--sky);
  outline-offset: 3px;
  border-radius: 1px;
}

ul,
ol {
  max-width: 62ch;
}

/* --------------------------------------------------------------------------
   5. Knockout headings — the p.19 device
   Restricted to h1 / h2 / .quote. Nothing smaller gets a bar.
   -------------------------------------------------------------------------- */

.knock > span {
  background: var(--ink);
  color: var(--paper);
  /* Asymmetric, as drawn on p.19 and p.20: a small lead-in and a long trailing
     overhang, so each line's bar ends at a different place and the stack reads
     as taped-together strips rather than a tidy highlight. */
  padding: 0.1em 0.62em 0.17em 0.2em;
  /* Without this, wrapped lines share one flush bar and the taped-together
     quality the logo depends on disappears. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* --------------------------------------------------------------------------
   6. Grid — 12 / 6 / 4 with half-column offsets for blocks
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  row-gap: 0;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
  /* Dense, because most decorative blocks are written BEFORE the copy they sit
     beside. Sparse placement never looks backwards, so a block declared at
     column 10 claimed row 1 and pushed the paragraph at column 3 down to row 2
     — leaving the block stranded on a row of its own and opening a band of
     empty page between every section. That is what made the blocks read as
     scattered rather than as marginalia.

     Dense backfills those holes, so the block lands beside its paragraph and
     `align-self: center` below does what it was written to do. Safe here
     because everything it reflows is aria-hidden and unfocusable: reading
     order and tab order are unaffected. */
  grid-auto-flow: row dense;
}

/* Span sets the END only. Using the `grid-column` shorthand here would set
   grid-column-start to `span N` and end to `auto`, so a later `.s-N` rule
   overwriting the start would silently collapse the item to a single track. */
.c-1 { grid-column-end: span 1; }
.c-2 { grid-column-end: span 2; }
.c-3 { grid-column-end: span 3; }
.c-4 { grid-column-end: span 4; }
.c-5 { grid-column-end: span 5; }
.c-6 { grid-column-end: span 6; }
.c-7 { grid-column-end: span 7; }
.c-8 { grid-column-end: span 8; }
.c-9 { grid-column-end: span 9; }
.c-10 { grid-column-end: span 10; }
.c-11 { grid-column-end: span 11; }
.c-12 { grid-column-end: span 12; }

.s-2 { grid-column-start: 2; }
.s-3 { grid-column-start: 3; }
.s-4 { grid-column-start: 4; }
.s-5 { grid-column-start: 5; }
.s-6 { grid-column-start: 6; }
.s-7 { grid-column-start: 7; }
.s-8 { grid-column-start: 8; }
.s-9 { grid-column-start: 9; }
.s-10 { grid-column-start: 10; }
.s-11 { grid-column-start: 11; }
.s-12 { grid-column-start: 12; }

/* 6 columns at tablet */
@media (max-width: 1024px) {
  .grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }

  /* Dense placement comes off here. It exists to pair a block with the
     paragraph it belongs beside, which needs the explicit column starts that
     the rule below turns off at this width. Left on, it has no positions to
     reconcile and simply drops each decorative block into whatever hole fits
     first — which, with the half-column overhang, lands them on the artwork. */
  .grid { grid-auto-flow: row; }
  .c-1 { grid-column-end: span 1; }
  .c-2, .c-3, .c-4, .c-5 { grid-column-end: span 3; }
  .c-6, .c-7, .c-8, .c-9, .c-10, .c-11, .c-12 { grid-column-end: span 6; }
  .s-2, .s-3, .s-4, .s-5, .s-6, .s-7, .s-8,
  .s-9, .s-10, .s-11, .s-12 { grid-column-start: auto; }
}

/* 4 columns at mobile — everything full width */
@media (max-width: 640px) {
  .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .grid > * { grid-column: 1 / -1 !important; }
}

/* --------------------------------------------------------------------------
   7. Sections & full-bleed
   -------------------------------------------------------------------------- */

.section {
  padding-block: var(--sp-7);
  position: relative;
}

.section--tight { padding-block: var(--sp-5); }
.section--flush-top { padding-top: 0; }

/* VERTICAL HIERARCHY.

   Six pages became one page of ~30 stacked sub-sections. Each carries 96px of
   padding top and bottom, so every neighbour sat ~192px from the next — the
   same distance whether the two belonged to the same nav section or not. About
   a quarter of the page was padding, and none of it told you anything. Copy
   that continues a thought read as unrelated to the copy above it, which is
   most of why the page felt like loose fragments rather than sections.

   The gap now carries the hierarchy. Sub-sections inside a nav section close
   up to a single 96px band; only a nav-addressable opener — or an interstitial,
   which brings its own — re-opens the full 192px. Same tokens, same rhythm,
   just applied where it means something. */
.section + .section,
.section + .module,
.module + .section,
.module + .module { padding-top: 0; }

/* Outweighs the pair rules above, so an opener always gets the full band even
   when it follows another section. */
main > .section[data-section] { padding-top: var(--sp-7); }

.bleed {
  width: 100%;
  max-width: none;
  padding-inline: 0;
}

.bleed > img,
.bleed > picture > img {
  width: 100%;
  max-height: 70vh;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   8. Black blocks — the censorship device
   RULES (see README): decorative only, aria-hidden, never over text,
   allowed to break the grid by half a column.
   -------------------------------------------------------------------------- */

.blk {
  background: var(--ink);
  height: var(--blk-h, 48px);
  align-self: center;
}

.blk--tall { --blk-h: 88px; }
.blk--thin { --blk-h: 22px; }

/* Deliberate half-column overhang — the anti-grid move from p.16 / p.17 */
.blk--out-l { margin-left: calc(var(--half-col) * -1); }
.blk--out-r { margin-right: calc(var(--half-col) * -1); }

/* Pull a block up into the module above it */
.blk--pull { margin-top: calc(var(--sp-4) * -1); }

/* A leading block, sitting in the section's own top padding rather than level
   with the copy. Blocks default to align-self:center, which parks them beside
   the text and leaves the band between two sections untouched.

   The pull is a fraction of the padding the section ACTUALLY has, never a fixed
   distance. It used to be a flat -64px, on the reasoning that half a section's
   top padding can never reach the section above — true when every section
   carried 96px. The vertical hierarchy then closed sub-sections up to
   padding-top:0, and a block still pulling 64px landed squarely on the credit
   line of the module above it. Tying the pull to the padding means a section
   with no top padding gets no pull, which is the only value that cannot
   collide. */
.section,
.module {
  --lead-pull: var(--sp-6);
  --trail-pull: var(--sp-5);
}

/* No top padding to sit in — see the vertical hierarchy in section 7. */
.section + .section,
.section + .module,
.module + .section,
.module + .module { --lead-pull: 0px; }

/* ...except an opener, which gets its padding back and so gets its pull back. */
main > .section[data-section] { --lead-pull: var(--sp-6); }

/* Half of --sp-5, matching the halving above. */
.section--tight { --trail-pull: var(--sp-3); }

.blk--lead {
  align-self: start;
  margin-top: calc(var(--lead-pull) * -1);
}

/* The same idea at the closing edge. */
.blk--trail {
  align-self: end;
  margin-bottom: calc(var(--trail-pull) * -1);
}

@media (max-width: 640px) {
  .section,
  .module {
    --lead-pull: var(--sp-3);
    --trail-pull: var(--sp-3);
  }
}

/* Inverted, for the black band and panel. The logo carries its mark both ways
   round — "Meet" is ink on paper, "the Moment" is paper on ink — so the bars
   invert with the ground rather than disappearing into it. Without this the
   dark sections are the only stretches of the site with no bars at all. */
.panel .blk,
.panel .blk-stack > i { background: var(--paper); }

/* Wedge. The logo's two blocks are not level — each is a shallow wedge whose
   top edge rises to the right by about a degree, and the footer strip is cut
   the same way. Every block in the content was perfectly horizontal, which is
   the flattest possible reading of "use logo concept as design principle".
   Used on some blocks, never all: the rhythm comes from the mix. */
.blk--wedge { clip-path: polygon(0 var(--blk-rise, 7px), 100% 0, 100% 100%, 0 100%); }
.blk--wedge-l { clip-path: polygon(0 0, 100% var(--blk-rise, 7px), 100% 100%, 0 100%); }

/* --------------------------------------------------------------------------
   Assemblage blocks — the second of the three treatments named on deck p.2,
   and the one the site never built. Two or three bars of different lengths,
   stacked flush and offset sideways so the silhouette steps. It is the
   logo's own construction, and the header's, brought down to content scale;
   the deck uses it in the left margin on p.16, p.17 and p.19.

   Children are <i> because they carry no meaning at all — the wrapper is
   aria-hidden and these are just three painted rectangles.
   -------------------------------------------------------------------------- */

.blk-stack { align-self: center; }

.blk-stack > i {
  display: block;
  background: var(--ink);
  height: var(--blk-h, 26px);
}

/* Lengths and offsets are irregular on purpose. Even steps read as a chart. */
.blk-stack > i:nth-child(1) { width: 64%; margin-left: 36%; }
.blk-stack > i:nth-child(2) { width: 100%; }
.blk-stack > i:nth-child(3) { width: 43%; margin-left: 9%; }

.blk-stack--tight > i { --blk-h: 18px; }
.blk-stack--loose > i { --blk-h: 38px; }

/* --------------------------------------------------------------------------
   A bar that passes behind an image and comes out the other side. Deck p.10,
   p.11 and p.12 all do this and nothing on the site did — it is the move that
   makes a block feel like it is punctuating the page rather than sitting next
   to it.

   The image is lifted above the bar rather than the bar pushed behind the
   page: a negative z-index here would escape to the nearest ancestor
   stacking context and paint under the section background.
   -------------------------------------------------------------------------- */

/* Corner blocks.

   This was one bar crossing behind the picture and exiting a side, which read
   as a rectangle parked next to the photo rather than as part of it. It is now
   a pair of blocks stepping out of two diagonally opposite corners — the same
   move the header's assemblage and the logo's two offset wedges make.

   Both sit BEHIND the picture, so only the overhang is visible: a bar escaping
   sideways, plus a shallower strip escaping above or below. The corner reads as
   a step rather than a rule, and the photograph is never covered.

   The sideways escape only ever goes toward the page edge — the side the module
   leaves empty. Sending it the other way would put a black block under the
   column of copy beside it. */

.media--corners .media__frame {
  position: relative;
  display: block;
  /* Clears the block that steps out below, so the credit never lands on it. */
  margin-bottom: var(--corner-drop);
}

.media--corners .media__frame > img {
  position: relative;
  z-index: 1;
}

.media--corners {
  --corner-h: 46px;      /* thickness of the bar escaping sideways */
  --corner-drop: 20px;   /* how far a block steps above / below the picture */
  --corner-out: var(--half-col);
  /* The second corner escapes toward the copy, so it goes a fixed short
     distance rather than half a column — enough to read as a step, never
     enough to reach the column of text beside it. */
  --corner-in: 26px;
}

.media--corners .media__frame::before,
.media--corners .media__frame::after {
  content: "";
  position: absolute;
  z-index: 0;
  background: var(--ink);
  height: var(--corner-h);
}

/* Widths are deliberately unequal — even steps read as a frame, and this is
   meant to read as two blocks that happen to meet the picture. */
.media--corners .media__frame::before {
  top: calc(var(--corner-drop) * -1);
  width: 46%;
}

.media--corners .media__frame::after {
  bottom: calc(var(--corner-drop) * -1);
  width: 33%;
}

/* Image sits on the LEFT of its module, so the free side is the left: the top
   block takes the long escape out toward the page edge, the bottom block takes
   the short one back under the copy. */
.media--corners-r .media__frame::before { left: calc(var(--corner-out) * -1); }
.media--corners-r .media__frame::after  { right: calc(var(--corner-in) * -1); }

/* Image sits on the RIGHT, so the free side is the right. Mirrored. */
.media--corners-l .media__frame::before { right: calc(var(--corner-out) * -1); }
.media--corners-l .media__frame::after  { left: calc(var(--corner-in) * -1); }

/* The credit paints above the blocks in every case. */
.media--corners .credit { position: relative; z-index: 2; }

/* The tablet grid drops to 6 columns and the module's free column disappears,
   so the sideways escape is capped at the page padding — an exact bound, since
   content right plus the padding is the viewport edge. */
@media (max-width: 1024px) {
  .media--corners { --corner-out: var(--pad); }
}

@media (max-width: 640px) {
  .media--corners {
    --corner-h: 34px;
    --corner-drop: 14px;
    --corner-out: var(--pad);
    --corner-in: 14px;
  }
}


/* --------------------------------------------------------------------------
   9. Header — replicated from deck p.6

   The header is not a bar, it is an assemblage of two offset black blocks,
   and the logo is KNOCKED OUT of it in reverse, straddling the seam between
   them. The step on the right forms the notch that ACT NOW sits in.

   Every figure below is measured off the p.6 wireframe, expressed as a
   percentage of viewport width (horizontal) or header height (vertical), so
   the composition holds at any width:

       page width in the wireframe .... 1967px  (≈ a 1440px viewport)
       header height ................. 147px   -> 108px css
       seam between the blocks ....... 60.5% of header height
       upper block ................... x   0%  -> 92.50%
       lower block ................... x 5.15% -> 87.65%
       logo .......................... x 6.66%, 12.86% wide,
                                       y 45.6% -> 83.7%  (crosses the seam)
       nav ........................... right edge at 85.36%, centre y 81%
       ACT NOW ....................... left edge at 89.27%, centre y 82%
   -------------------------------------------------------------------------- */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--paper);
  color: var(--ink);
  padding: var(--sp-1) var(--sp-2);
  font-weight: 700;
}

.skip:focus {
  left: var(--pad);
  top: var(--sp-1);
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-h);
  /* Transparent: only the black blocks are painted. The notch, the strip left
     of the lower block and everything right of the upper block let the page
     show through, so the header reads as loose bars laid over the page rather
     than a solid bar. Over the white page they read white; over a dark section
     they read black and the assemblage dissolves into it. */
  background: transparent;

  --seam: 60.5%;
  --upper-right: 92.5%;
  --lower-left: 5.15%;
  --lower-right: 87.65%;

  /* The upper block stops at --upper-right, leaving the top corner open. This
     carries the block's top edge the rest of the way as a hairline, so the
     header reads as one rule across the full width instead of stopping short
     of the right edge. Wide enough to read as a strip at a glance, still under
     the upper block's ~65px so it stays subordinate to it. */
  --rail-h: 21px;

  /* How far a hover bar bleeds past its label, resolved in the label's own
     em rather than the header's. Paired with the separator gap below: the
     two are set together so a lit bar leaves the hyphens either side of it
     about as much air as they have at rest. */
  --bar-bleed: 0.28em;
  --nav-gap: 0.58em;
}

/* The two offset blocks, drawn as one stepped silhouette */
.site-header__shape {
  position: absolute;
  inset: 0;
  background: var(--ink);
  clip-path: polygon(
    0 0,
    100% 0,
    100% var(--rail-h),
    var(--upper-right) var(--rail-h),
    var(--upper-right) var(--seam),
    var(--lower-right) var(--seam),
    var(--lower-right) 100%,
    var(--lower-left) 100%,
    var(--lower-left) var(--seam),
    0 var(--seam)
  );
}

.site-header__inner {
  position: relative;
  height: 100%;
}

/* Scroll progress — a hairline across the very top of the header.

   Six pages became one scroll, so the only remaining cue for how much is left
   is the scrollbar, which is a hairline on macOS and absent on touch. Drawn as
   one more horizontal bar, since that is the device the header's blocks, the
   bands and the footer strips all already use.

   It sits ABOVE the black shape rather than inside it: the shape's clip-path
   would cut the bar at the notch, and the bar has to read across the full
   width to mean anything. Paper on ink over the block, and it carries its own
   dark track so it stays legible where the header is transparent. */
.scroll-progress {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 2;
  background: rgba(35, 31, 32, 0.18);
}

.scroll-progress > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--paper);
  /* Set as a width by initScrollProgress(). Not transitioned: the value is
     already updated per frame, and easing it would make the bar lag the page
     it is reporting on. */
}

/* Logo — reverse (white ink) so the block reads through the script "the"
   and the counters. It deliberately crosses the seam between the blocks. */
.logo {
  position: absolute;
  left: 6.66%;
  top: 45.6%;
  height: 38.8%;
  display: block;
  text-decoration: none;
}

.logo:hover { text-decoration: none; }

.logo img {
  display: block;
  height: 100%;
  width: auto;
}

/* Nav sits low, in the lower block, right-aligned short of its right edge */
.site-nav {
  position: absolute;
  right: 14.64%;
  top: 81%;
  transform: translateY(-50%);
}

.site-nav__list {
  display: flex;
  align-items: baseline;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

.site-nav__list a {
  position: relative;
  /* Contains the bar's negative z-index. Without it the bar escapes to the
     header's stacking context and paints BEHIND .site-header__shape, i.e.
     invisibly, since nothing between here and the header is a context. */
  isolation: isolate;
  color: var(--paper);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1;
  padding: 6px 2px;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color 90ms linear;
}

/* Hover paints a bar. The one device this site repeats is the horizontal
   block — the header's stepped assemblage, the footer strips, the black
   bands — so hovering inflates the underline into one of those blocks and
   the label inverts inside it, instead of the rule merely changing colour.
   Growing from the bottom edge keeps the underline as the bar's resting
   state rather than replacing it with an unrelated gesture.

   Drawn as a pseudo-element that bleeds past the link box, so the row's
   widths and the hyphen separators never move on hover. */
.site-nav__list a::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* A 32px bar centred on the label. The link's box is bottom-heavy by the
     2px it reserves for the current-page rule, so the bar is nudged up by
     that much to sit on the text's centre rather than the box's. */
  top: -1px;
  bottom: 1px;
  left: calc(var(--bar-bleed) * -1);
  right: calc(var(--bar-bleed) * -1);
  background: var(--paper);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform 140ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.site-nav__list a:hover {
  color: var(--ink);
  /* Held until the bar is most of the way up, so the label is never white on
     white. On the way out the base rule's zero delay applies instead, which
     is what keeps it from going black on black as the bar drops. */
  transition-delay: 70ms;
}

.site-nav__list a:hover::before { transform: scaleY(1); }

.site-nav__list a[aria-current="page"],
.site-nav__list a[aria-current="location"] {
  border-bottom-color: var(--paper);
  font-weight: 500;
}

/* Where-you-are and where-you-could-go used to look identical. The bar now
   carries hover, so the rule is left to mean only "current page" — and it
   steps aside while the bar is up rather than stacking two markers. */
.site-nav__list a[aria-current="page"]:hover,
.site-nav__list a[aria-current="location"]:hover { border-bottom-color: transparent; }

/* The deck sets the nav as one hyphen-separated string. Kept visually, but
   the separators are decoration between discrete links, not part of them.
   The gap was 0.42em, which left a lit bar almost touching the hyphens on
   either side; opened up just enough to clear them. It still reads as one
   string, and the nav is right-anchored so the extra width grows into empty
   space rather than toward the logo. */
.site-nav__list li + li::before {
  content: "-";
  color: var(--paper);
  font-size: 0.9375rem;
  padding-inline: var(--nav-gap);
}

.site-header :focus-visible { outline-color: var(--paper); }

/* ACT NOW — black on the page, in the notch the step cuts out */
.cta-notch {
  position: absolute;
  left: 89.27%;
  top: 82%;
  transform: translateY(-50%);
  color: var(--ink);
  font-family: var(--body);
  font-weight: 400;
  font-size: 0.9375rem;
  letter-spacing: 0.02em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  /* Was 3px padding + a 2px reserved underline. Same 20px box, so the same
     optical centring under the translateY(-50%), minus a border nothing
     uses now. */
  padding-bottom: 5px;
  isolation: isolate;
  transition: color 90ms linear;
}

/* Same bar, opposite polarity: the CTA reads dark-on-page, so its bar is ink
   and the label goes white inside it.

   The offsets look arbitrary next to the nav's because the two boxes are
   measured differently: a nav link is inline, so its content box is the
   font's own ascent+descent (18px), while this one is blockified by
   position:absolute and takes its 15px from line-height. Different boxes,
   and this one is bottom-padded besides — these numbers are what put the
   same 32px bar on the same optical centre as the nav's. */
.cta-notch::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -8.5px;
  bottom: -3.5px;
  left: calc(var(--bar-bleed) * -1);
  right: calc(var(--bar-bleed) * -1);
  background: var(--ink);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform 140ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.cta-notch:hover {
  color: var(--paper);
  transition-delay: 70ms;
  text-decoration: none;
}

.cta-notch:hover::before { transform: scaleY(1); }

/* Polarity flips are instant. The 90ms fade below is right for hover, but on
   an inversion it travels through the mid-greys, and those read against
   neither ink nor paper — a brief blind spot every time the label flips.
   initHeaderContrast() hangs this on for exactly one style flush. */
.cta-notch--snap,
.cta-notch--snap::before { transition: none; }

/* The CTA is the only header element sitting over the transparent notch, so it
   is the only one that has to invert. JS measures what is actually behind the
   label and sets data-over on the header — see initHeaderContrast(). The label
   only ever changes colour; it is never given a block of its own to sit on, so
   the bar below stays a hover affordance and nothing else.
   The bar inverts with it: the rule is that hover flips the label against
   whatever is behind it right now, which over a dark band means the CTA
   borrows the nav's polarity rather than painting ink on ink.

   This used to be guarded by the breakpoint, because below it the CTA was its
   own solid white block and had no transparent ground to invert against. The
   notch is a real cutout at every size now, so the guard is gone: the label
   has to invert on a phone over the hero band for exactly the reason it has
   to on a desktop. */
.site-header[data-over="dark"] .cta-notch { color: var(--paper); }
.site-header[data-over="dark"] .cta-notch::before { background: var(--paper); }
.site-header[data-over="dark"] .cta-notch:hover { color: var(--ink); }

.cta-notch:focus-visible { outline-color: var(--sky); }

/* Mobile menu button — hidden until the nav collapses */
.nav-toggle {
  display: none;
  align-items: center;
  gap: 8px;
  background: none;
  border: 0;
  color: var(--paper);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding-inline: var(--sp-2);
  cursor: pointer;
}

.nav-toggle__bars {
  display: grid;
  gap: 3px;
  width: 20px;
}

.nav-toggle__bars span {
  height: 2px;
  background: var(--paper);
}

/* --------------------------------------------------------------------------
   Below 900px the step has no room to read, so the assemblage collapses to a
   single block. The notch survives the collapse: it is still a hole cut in
   that block, not a white rectangle painted on top of it.

   That distinction is the whole point of the device. On desktop the notch is
   transparent, so it reads white over the page and dark over a band, and the
   header sits on the page rather than on top of it. Painting the mobile notch
   solid --paper broke that — over the hero band, which is black, a white
   rectangle appeared out of nowhere and the header stopped dissolving into
   the section beneath it.

   So the block stops short of the notch instead of running the full width,
   and the CTA sits in the gap it leaves. --notch-w is the single source of
   truth for that gap: the shape reserves it on the right, the CTA fills it.
   Change one and the other follows; there is no second number to keep in
   step. The label inverts over dark ground exactly as it does on desktop,
   via data-over — see initHeaderContrast().
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  /* Wide enough for the label at this size plus the air the desktop notch
     gives it — the label is centred in it, not padded to it, so the two
     cannot drift apart. */
  .site-header { --notch-w: 8.5rem; }

  /* The block runs full height but stops --notch-w short of the right edge,
     except along the top, where a rail carries its edge the rest of the way.

     Same device as the desktop polygon, and for the same reason: without it
     the black stops dead partway across and the header reads as a shape that
     ran out of room rather than one rule laid across the page. The rail keeps
     the top edge unbroken while the notch below it stays open.

        ┌─────────────────────────────────────────┐  ← rail, full width
        │█████████████████████████████████████████│
        │███████████████████████████│             │
        │█████ logo ████████ menu ██│  ACT NOW    │  ← notch, transparent
        └───────────────────────────┴─────────────┘

     Thinner than the desktop rail's 21px: that one is measured against a
     108px header, and the same figure on a 72px one reads as a band rather
     than a rule. */
  .site-header { --rail-h: 12px; }

  .site-header__shape {
    inset: 0;
    clip-path: polygon(
      0 0,
      100% 0,
      100% var(--rail-h),
      calc(100% - var(--notch-w)) var(--rail-h),
      calc(100% - var(--notch-w)) 100%,
      0 100%
    );
  }

  .site-header__inner {
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    gap: var(--sp-2);
    padding-left: var(--pad);
  }

  .logo {
    position: static;
    height: auto;
    align-self: center;
    flex: none;
  }

  .logo img { height: 26px; }

  .nav-toggle { display: flex; margin-left: auto; }

  .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: auto;
    width: 100%;
    transform: none;
    background: var(--ink);
    border-top: 1px solid var(--ink-90);
    display: none;
  }

  .site-nav[data-open="true"] { display: block; }

  .site-nav__list {
    flex-direction: column;
    align-items: stretch;
    padding: var(--sp-1) var(--pad) var(--sp-3);
  }

  .site-nav__list li + li::before { content: none; }

  .site-nav__list a {
    display: block;
    padding-block: var(--sp-2);
    border-bottom: 1px solid var(--ink-90);
    font-size: 1rem;
  }

  .site-nav__list a[aria-current="page"] { border-bottom-color: var(--ink-90); }

  /* Stacked, the rows are already bar-shaped, so the hover block runs the
     full width of the panel instead of hugging the label — it lights the
     whole row rather than floating a chip inside it. */
  .site-nav__list a::before {
    left: calc(var(--pad) * -1);
    right: calc(var(--pad) * -1);
  }

  .cta-notch {
    position: static;
    transform: none;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Fills the gap the shape reserved, exactly. Sized rather than padded so
       the ink edge and the label's box are the same edge. */
    width: var(--notch-w);
    flex: none;
    font-weight: 700;
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
    padding: 0;
  }

  /* The hover bar fills the notch rather than hugging the label: at this size
     the notch IS the hit area, so lighting anything smaller would leave dead
     ground inside the target. Same scaleY growth and the same polarity flip
     as desktop, so hover behaves identically at both sizes. */
  .cta-notch::before {
    inset: 0;
    left: 0;
    right: 0;
  }
}

@media (max-width: 480px) {
  .site-header { --notch-w: 7rem; --rail-h: 10px; }
  .cta-notch { font-size: 0.75rem; }
  .logo img { height: 21px; }
  .nav-toggle { padding-inline: var(--sp-1); }

  .nav-toggle__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .nav-toggle__bars { width: 22px; }
}

/* --------------------------------------------------------------------------
   10. Buttons
   Roboto, not Special Elite — buttons are UI, below the display floor.
   -------------------------------------------------------------------------- */

/* The deck has exactly one button: a solid black rectangle with white,
   sentence-case, untracked text and tight padding ("Read More" p.8/p.15,
   "View show" p.13/p.14). No outlined variant appears anywhere.

   There was a `.btn--invert` for the black involvement band. That band is gone
   (see decision 3d) and nothing reversed is left, so the variant went with it. */
.btn {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  font-weight: 400;
  font-size: 0.9375rem;
  line-height: 1.35;
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  border: 0;
  padding: 0.34em 0.72em 0.4em;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease,
              box-shadow 0.15s ease;
}

/* Hover flips the block's polarity rather than colouring it in.

   This is the rule the rest of the site already follows: the nav inflates its
   underline into a paper bar and the label goes black inside it, and the ACT
   NOW notch does the same in reverse because it reads dark-on-page. The
   comment there puts it as "hover flips the label against whatever is behind
   it". A button is already one of those blocks, so hovering just turns it
   over.

   It used to go sky blue, which was the one place an accent leaked out of the
   focus ring and into the brand — and the brand is black and white.

   The inset keyline is what makes the flip survive: a white button on a white
   page has no edge, and without it the block stops reading as a block at
   exactly the moment the pointer is on it. Inset rather than a border, so
   nothing reflows. */
.btn:hover {
  background: var(--paper);
  color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--ink);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   11. Hero — the brand film

   A full-width black band with the film centred at its native square size.
   The asset is 640x640 and already carries the logo and the line, so it is
   never upscaled: stretching it to a banner would soften the logo and crop
   the top and bottom off every frame.

   The deck's "Roll up image caroussel (3 or 4)" (p.6, p.11) was dropped by
   decision — see README.
   -------------------------------------------------------------------------- */

.hero-film {
  /* The film now opens on a white card, so the band starts white with dark
     type. As the film moves into its photographic frames the band follows them
     down and the type inverts with it — both driven from these two tokens by
     initFilmTint(). The defaults here are what no-JS and reduced-motion get,
     and they match the opening card. */
  --hero-bg: #ffffff;
  --hero-fg: var(--ink);

  background-color: var(--hero-bg);
  color: var(--hero-fg);
  padding-block: var(--sp-6);
  transition: background-color 600ms ease-out, color 600ms ease-out;

  /* The header is sticky, so it takes its own strip of the flow and the band
     used to start below it — which left the header's transparent cut-outs
     showing the white page rather than the film's tint. Pulling the band up by
     the header's height puts it behind the header instead, so the notch, the
     top-right corner and the strip left of the lower block all fill with
     whatever colour the film is currently on. The matching top padding puts
     the content back exactly where it was, so nothing below moves. */
  margin-top: calc(var(--header-h) * -1);
  padding-top: calc(var(--sp-6) + var(--header-h));
}

@media (prefers-reduced-motion: reduce) {
  .hero-film { transition: none; }
}

.hero-film .grid { align-items: center; row-gap: var(--sp-5); }

.hero-film__frame {
  position: relative;
  width: 100%;
  max-width: 640px;
  aspect-ratio: 1 / 1;
}

.hero-film__motion,
.hero-film__still {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The still is only for reduced-motion and for browsers without video */
.hero-film__still { display: none; }

/* The TnT Art Lab attribution under the hero copy. It takes its colour from
   --hero-fg rather than .credit's own --ink-60, because the band runs from
   white to dark as the film plays and a fixed dark grey would disappear into
   it. Held at 0.65 so it stays subordinate to the statement above it. */
.hero-film__copy .credit {
  color: var(--hero-fg);
  opacity: 0.65;
  transition: color 600ms ease-out;
  max-width: 40ch;
}

.hero-film__title {
  font-size: clamp(1.5rem, 2.5vw, 2.15rem);
  line-height: 1.24;
  margin-bottom: var(--sp-3);
  max-width: 20ch;
  color: var(--hero-fg);
  transition: color 600ms ease-out;
}

.hero-film__copy p {
  color: var(--hero-fg);
  opacity: 0.88;
  transition: color 600ms ease-out;
}

.hero-film__copy .hero-film__premise {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 1.3;
  opacity: 1;
  margin-block: var(--sp-3);
}

/* Solid button in whichever direction the band currently reads */
.hero-film__copy .btn {
  margin-top: var(--sp-1);
  background: var(--hero-fg);
  color: var(--hero-bg);
  transition: background-color 600ms ease-out, color 600ms ease-out;
}

/* Same flip, but against the band's current polarity rather than the page's —
   the hero runs white to dark under the film tint, so a fixed pair of colours
   would be wrong for half the loop. */
.hero-film__copy .btn:hover {
  background: var(--hero-bg);
  color: var(--hero-fg);
  box-shadow: inset 0 0 0 2px var(--hero-fg);
}

/* Pause control. WCAG 2.2.2 — anything that animates for more than five
   seconds needs a way to stop it, and this loop runs 10.1s forever. Kept
   quiet: it only gains a background on hover or focus. */
.hero-film__toggle {
  position: absolute;
  right: 0;
  bottom: 0;
  background: transparent;
  border: 0;
  color: var(--paper);
  font-family: var(--body);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.55em 0.9em;
  cursor: pointer;
  mix-blend-mode: difference;
}

.hero-film__toggle:hover,
.hero-film__toggle:focus-visible {
  mix-blend-mode: normal;
  background: var(--hero-fg);
  color: var(--hero-bg);
}

.hero-film :focus-visible { outline-color: var(--sky); }

/* Stack once the columns get tight. The film takes the full row and centres,
   rather than being left in the half-column it would otherwise inherit. */
@media (max-width: 1024px) {
  .hero-film__frame {
    grid-column: 1 / -1;
    margin-inline: auto;
  }
  .hero-film__copy { grid-column: 1 / -1; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-film__motion,
  .hero-film__toggle { display: none; }
  .hero-film__still { display: block; }
}

/* --------------------------------------------------------------------------
   12. Modules
   -------------------------------------------------------------------------- */

.module { padding-block: var(--sp-5); }

.module__media img { width: 100%; }

/* Never upscale. Every other module picture is wider than its column and just
   fills it; Daapo Reo's extract is 490px and would be stretched about 1.16x to
   do the same — the one thing the hero and the interstitials both refuse to do.
   The frame shrinks with it so the corner bars keep hugging the image. */
.module__media--native .media__frame { width: fit-content; max-width: 100%; }
.module__media--native img { width: auto; max-width: 100%; }

/* Top-aligned, not centred. Centring floated the copy against a photo that is
   always taller than it, so every module's heading sat 56-91px below the top of
   its own image — dead space at the head of all four. After the "What we will
   do" cards that pocket landed directly under the section and read as a hole in
   the page. Aligning the heading with the top edge of the picture is both
   tighter and the stronger composition; the slack now falls below the copy,
   where it belongs. */
.module__body { align-self: start; }

.module__body > *:last-child { margin-bottom: 0; }

/* One reversed / right-aligned module per page maximum (deck p.13).
   Ragged-left is hard to read as a repeating component. */
.module--reverse .module__body {
  text-align: right;
}

.module--reverse .module__body p,
.module--reverse .module__body .lede {
  margin-left: auto;
}

@media (max-width: 640px) {
  .module--reverse .module__body { text-align: left; }
}

/* Image credits. Artwork on this site is by named artists and carries a credit
   wherever it appears — see README on clearance. */
.credit {
  font-size: 0.6875rem;
  line-height: 1.45;
  letter-spacing: 0.01em;
  color: var(--ink-60);
  margin: 8px 0 0;
  max-width: 46ch;
}

.credit em { font-style: italic; }

.interstitial .credit,
.panel .credit { color: #a49d99; }

/* --------------------------------------------------------------------------
   13. Cards
   -------------------------------------------------------------------------- */

/* 4-up, as the partner grid on deck p.14. Explicit rather than auto-fill so the
   count is a design decision instead of a function of the viewport. */
.cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4) var(--gutter);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

.cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 1024px) {
  .cards,
  .cards--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .cards,
  .cards--3 { grid-template-columns: minmax(0, 1fr); }
}

.card { display: flex; flex-direction: column; }

.card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-bottom: var(--sp-2);
}

.card p {
  font-size: 0.9375rem;
  margin-bottom: var(--sp-2);
  flex: 1;
}

/* Now that the cards sit on the spine rather than running the full width, they
   are narrow enough that a credit wraps to two lines in one card and one in the
   next — which put the three titles on three different baselines.

   The image above is a fixed 4:3, so it is the same height in every card, and
   the credit is the only thing that varies. Reserving two lines for it lines the
   titles up.

   This replaced a subgrid whose last track was `1fr`. An `fr` track distributes
   free space, and in a container whose height is resolved differently from the
   way Chrome resolves it, that track grows — which opened a large empty band
   between the cards and the copy beneath them. Nothing here can grow: a
   min-height has no free space to absorb. A credit long enough to need three
   lines will nudge one title down, which is the old behaviour and survivable; a
   card row with a chasm under it is not. */
.card .credit {
  /* `.card p` above sets 0.9375rem and outweighs `.credit`'s own 0.6875rem —
     one class plus a type beats one class — so card credits had been rendering
     at body size, nearly half again the caption size every other credit on the
     site uses. Restated here so they match, which also makes the reserved
     height below resolve against the right line box. */
  font-size: 0.6875rem;

  /* Not `flex: 1` like the body copy — the credit is a p too, so it was taking
     a share of the flex slack and pushing the title down whenever one card's
     body ran shorter than its neighbours'. */
  flex: 0 0 auto;

  /* Two lines held open, in the credit's own em so it tracks the font-size
     above rather than restating a number. */
  min-height: calc(1.45em * 2);
}

/* Same idea one row down: "Convening and dialogue" wraps to two lines where its
   neighbours fit on one, which staggered the body copy beneath them. Two lines
   held open here too, in the title's own em. A min-height only ever sets a
   floor — a longer title still grows — so this cannot open space the way an
   `fr` track can. */
.cards--3 .title-s {
  min-height: calc(1.2em * 2);
}

.card .btn { align-self: flex-start; }

/* The deck's cards go straight from image to title. This keeps the date and
   category, but quietly, so it reads as a caption rather than a second label
   competing with the Special Elite title above it. */
.card__meta {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-60);
  margin-bottom: 6px;
}

/* --------------------------------------------------------------------------
   14. Routes — the three ways in, from deck p.18

   The deck draws this as an inverted black band. It reads on the page ground
   instead, so the artwork in the three cards sits on the same white as every
   other picture on the site, and the section keeps the spine rather than
   bleeding edge to edge. The `.band` rules that painted it — and the
   `.btn--invert` buttons, the lightened credits and the reversed form fields
   that existed only to survive that black — are gone with it.
   -------------------------------------------------------------------------- */

.routes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4) var(--gutter);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

.routes img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  margin-bottom: var(--sp-2);
}

.routes p { font-size: 0.9375rem; }

/* `.routes p` above outweighs `.credit`'s own 0.6875rem — one class plus a type
   beats one class — so these credits had been rendering at body size, the same
   bug the cards had. Restated, then two lines held open for the credit and two
   for the title, so the three routes line up now that they sit on the spine and
   are narrow enough to wrap unevenly. A min-height only sets a floor: a longer
   credit still grows, and nothing here can expand to fill space. */
.routes .credit {
  font-size: 0.6875rem;
  min-height: calc(1.45em * 2);
}

.routes .title-s { min-height: calc(1.2em * 2); }

/* Deck p.22 kept these 3-up at phone width, well below usable size. Stacked. */
@media (max-width: 760px) {
  .routes { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   15. Toolkit download table
   -------------------------------------------------------------------------- */

.panel {
  background: var(--ink);
  color: var(--paper);
  padding: var(--sp-4);
}

.panel :focus-visible { outline-color: var(--paper); }

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.dl-table {
  width: 100%;
  min-width: 620px;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

/* Matches p.20: plain white sans throughout, sentence case, no tracked
   micro-labels, hairline rules, and a white download control at the right of
   each row. The deck draws that control as a square holding a solid triangle;
   a triangle on its own reads as a dropdown or a sort control, so it carries a
   proper download mark and the word instead. */
.dl-table th,
.dl-table td {
  text-align: left;
  padding: var(--sp-2) var(--sp-2) var(--sp-2) 0;
  border-bottom: 1px solid #5c5654;
  vertical-align: middle;
  font-weight: 400;
}

.dl-table thead th {
  font-size: 1.0625rem;
  color: var(--paper);
  border-bottom: 1px solid #8d8683;
  padding-bottom: var(--sp-1);
}

.dl-table tbody th,
.dl-table tbody td { font-size: 1.0625rem; }

.dl-table tbody tr:last-child th,
.dl-table tbody tr:last-child td { border-bottom: 1px solid #5c5654; }

.dl-table .num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.dl-table .type { white-space: nowrap; }

.dl-thumb {
  width: 84px;
  height: 46px;
  object-fit: cover;
  background: var(--paper-2);
}

.dl-table a {
  color: var(--paper);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.dl-table a:hover { border-bottom-color: var(--paper); }

/* The download control is the last column, which makes it the first thing lost
   whenever the table has to scroll sideways inside .table-scroll — and a
   control the reader cannot see is a control that does not exist. Chasing it
   with breakpoints only ever fixes the widths you thought to test.

   Pinned to the right edge instead: the rest of the row scrolls underneath and
   the button stays put at every width, at every zoom, in every browser. It
   needs its own opaque background or the scrolled cells show through it, and a
   hairline on its leading edge so it reads as sitting above them rather than
   being part of them. */
.dl-table tr > *:last-child {
  position: sticky;
  right: 0;
  background: var(--ink);
  width: 1%;
  white-space: nowrap;
  padding-left: var(--sp-2) !important;
  padding-right: 0 !important;
}

.dl-table tbody tr > *:last-child { border-left: 1px solid #5c5654; }

/* Scoped through .dl-table so it outweighs the `.dl-table a` colour above —
   at equal specificity the white link colour would paint the glyph invisible. */
.dl-table .dl-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  /* The cell around this is `width: 1%`, i.e. "as narrow as you can make it".
     Chrome resolves that against the button's content and stops; Gecko squeezes
     harder, and a flex container that is allowed to shrink obliges — which
     clipped the word inside a box that still looked correctly drawn. Pinning
     the button to its content width is what stops the cell winning. */
  min-width: max-content;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 0.8125rem;
  line-height: 1;
  padding: 0.62em 0.8em 0.62em 0.7em;
  border-bottom: 0;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease,
              box-shadow 0.15s ease;
}

/* Stem, arrowhead, baseline — the three parts that make the mark read as
   "download" rather than "open" or "sort". Square caps and mitred joins
   because every other line on this site is cut square; a rounded icon would
   be the only soft edge in the build. It inherits `currentColor`, so it flips
   with the button below instead of needing its own hover rule. */
.dl-btn__icon {
  display: block;
  flex: none;
  width: 15px;
  height: 15px;
}

/* `flex: none`, not the default `0 1 auto`. Without it the label is a
   shrinkable flex item, and when the cell squeezed the button this is the part
   that gave way — the text was clipped inside its own box rather than the box
   being cut off, which is why every measurement of the BOX came back clean. */
.dl-btn__label {
  display: block;
  flex: none;
  white-space: nowrap;
}

/* This one rests paper-on-ink because the panel behind it is black, so its
   flip runs the other way. */
.dl-table .dl-btn:hover {
  background: var(--ink);
  color: var(--paper);
  box-shadow: inset 0 0 0 2px var(--paper);
}

/* Keeping the Download control on screen as the table narrows.

   The table has a min-width and scrolls inside .table-scroll, so once the
   viewport drops below it the rightmost column — which is the download control
   — is the first thing pushed past the clipping edge. That was survivable when
   the control was a 34px square; a 99px labelled button gets visibly sliced,
   which is what a reader sees before they think to scroll a table sideways.

   The preview thumbnail is the least load-bearing column here: it repeats what
   the file name already says, and it is decorative (alt=""). Dropping it buys
   back roughly the width the label costs. */
@media (max-width: 860px) {
  .dl-table { min-width: 460px; }
  .dl-table tr > *:first-child { display: none; }

  .dl-table th,
  .dl-table td { padding-right: var(--sp-1); }
}

/* Last resort, below the width where even that fits: the label goes and the
   mark carries it alone. The mark is now a real download glyph rather than the
   triangle it used to be, so icon-only still reads as what it does.

   580px, not 460px, because the table never gets the whole viewport: the page
   padding and the panel's own padding take about 104px off it first. Set to the
   table's min-width, the breakpoint fires ~100px too late and the control is
   still clipped in the gap between. */
@media (max-width: 580px) {
  .dl-table { min-width: 0; }
  .dl-btn__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .dl-table .dl-btn { padding: 0.62em 0.7em; }
}

/* --------------------------------------------------------------------------
   16. Listings (News, Press)
   -------------------------------------------------------------------------- */

.listing {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  border-top: 2px solid var(--ink);
}

.listing > li {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--sp-3);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}

.listing img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }

.listing h3 { margin-bottom: var(--sp-1); }

.listing p { font-size: 0.9375rem; margin-bottom: var(--sp-2); }

.listing .card__meta { margin-bottom: 6px; }

@media (max-width: 640px) {
  .listing > li { grid-template-columns: 1fr; }
}

.listing--text > li { grid-template-columns: minmax(0, 1fr); }

/* --------------------------------------------------------------------------
   17. FAQ — native details/summary, keyboard accessible for free
   -------------------------------------------------------------------------- */

.faq { border-top: 2px solid var(--ink); max-width: 68ch; }

.faq details { border-bottom: 1px solid var(--rule); }

.faq summary {
  cursor: pointer;
  padding-block: var(--sp-2);
  font-weight: 600;
  font-size: 1.0625rem;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  align-items: baseline;
}

.faq summary::-webkit-details-marker { display: none; }

/* Body font, not Special Elite — this is UI, and it sits below the display
   floor. Same reason the buttons and nav are Roboto. */
.faq summary::after {
  content: "+";
  font-family: var(--body);
  font-size: 1.4rem;
  font-weight: 300;
  line-height: 1;
  flex: none;
  color: var(--ink-60);
}

.faq details[open] summary::after { content: "\2013"; }

.faq details > *:not(summary) { padding-bottom: var(--sp-2); }

.faq p { font-size: 0.9375rem; }

/* --------------------------------------------------------------------------
   18. Forms — email capture. Absent from the deck entirely.
   -------------------------------------------------------------------------- */

/* The closing section runs the argument and the form side by side above
   1024px. Below it the grid collapses and they stack — and `.grid` has
   `row-gap: 0`, so the form's heading lands hard against the founding-supporter
   button above it. It needs its own space back only in that stacked state. */
@media (max-width: 1024px) {
  .join__aside { margin-top: var(--sp-4); }
}

.signup { max-width: 34rem; }

.field { margin-bottom: var(--sp-2); }

.field label {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  background: var(--paper);
  color: var(--ink);
  border: 2px solid var(--ink);
  padding: 0.7em 0.8em;
  font-size: 1rem;
}

.field input::placeholder { color: var(--ink-30); }

.field input[aria-invalid="true"] { border-color: var(--err); }

.field__error {
  display: none;
  color: var(--err);
  font-size: 0.875rem;
  font-weight: 600;
  margin-top: 6px;
}

.field input[aria-invalid="true"] ~ .field__error { display: block; }

.form-note {
  font-size: 0.8125rem;
  color: var(--ink-60);
  max-width: 46ch;
}

.form-status {
  font-weight: 600;
  font-size: 0.9375rem;
  margin-top: var(--sp-2);
}

.form-status[data-state="ok"] { color: var(--ok); }
.form-status[data-state="err"] { color: var(--err); }

/* --------------------------------------------------------------------------
   19. Footer — the angled black band from deck p.6 / p.8 / p.11
   Angle is specified, not eyeballed. Padding guarantees no clipped links.
   -------------------------------------------------------------------------- */

/* The footer in the deck (p.6, p.8, p.11) is the same assemblage idea as the
   header: three offset blocks, the topmost one tilted so its upper edge rises
   to the right. Measured off p.11 as proportions of the page width:
       tilted block   x  3% -> 89%,  top edge rises ~1.5deg
       middle block   x  3% -> 100%
       footer block   x  0% ->  97%
   The tilt is shallow — it should read as a torn strip, not a diagonal. */

.footer-bars {
  position: relative;
  /* The band between the last section and the footer was the largest single
     empty run on every page (192-212px measured). The assemblage now fills
     more of it and starts closer to the content. */
  margin-top: var(--sp-4);
  height: 124px;
}

/* The deck draws three offset blocks here, not two. The third is a
   pseudo-element so the markup does not have to change on eight pages. */
.footer-bars::before {
  content: "";
  position: absolute;
  left: 20%;
  right: 6%;
  top: 0;
  height: 34px;
  background: var(--ink);
}

.footer-bars span {
  position: absolute;
  display: block;
  background: var(--ink);
}

/* Tilted wedge. In the deck the top edge rises by roughly the strip's own
   height across the page, so the left end is a sliver and the right is solid. */
.footer-bars span:nth-child(1) {
  left: 3%;
  right: 11%;
  top: 42px;
  height: 58px;
  clip-path: polygon(0 55%, 100% 0, 100% 100%, 0 100%);
}

/* middle block, runs past the wedge on the right */
.footer-bars span:nth-child(2) {
  left: 3%;
  right: 0;
  top: 100px;
  height: 24px;
}

/* The deck insets the footer block from the right edge (p.11), but at real
   full-bleed width that reads as a missing corner rather than a deliberate
   step, so the block runs the full width. The offset bars above it still
   carry the assemblage. */
.site-footer {
  position: relative;
  background: var(--ink);
  color: var(--paper);
  width: 100%;
  padding-top: var(--sp-5);
  padding-bottom: var(--sp-5);
}

.site-footer a { color: var(--paper); text-decoration: none; }
.site-footer a:hover { text-decoration: underline; }
.site-footer :focus-visible { outline-color: var(--paper); }

.site-footer h3 { color: var(--paper); margin-bottom: var(--sp-2); }

.footer-address {
  margin-top: var(--sp-2);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: #b6afab;
}

.footer-nav { list-style: none; margin: 0; padding: 0; }
.footer-nav li { margin-bottom: 8px; }

.footer-legal {
  margin-top: var(--sp-5);
  padding-top: var(--sp-2);
  border-top: 1px solid #4a4544;
  font-size: 0.8125rem;
  color: #b6afab;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
}

.footer-legal a { color: #b6afab; }

/* --------------------------------------------------------------------------
   20. Utilities
   -------------------------------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.stack > * + * { margin-top: var(--sp-2); }
.mt-0 { margin-top: 0; }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
/* Was missing while a `mt-5` sat in the markup doing nothing — the coda under
   the "What we will do" cards had been asking for this space all along. */
.mt-5 { margin-top: var(--sp-5); }
.mt-6 { margin-top: var(--sp-6); }
.mb-0 { margin-bottom: 0; }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }

.rule { border: 0; border-top: 2px solid var(--ink); margin: 0 0 var(--sp-3); }

/* --------------------------------------------------------------------------
   21. Interstitials — the beat between sections

   The six inner pages became six sections on one scroll (see README). A reader
   who has just finished one needs to feel it close before the next one opens,
   which page breaks used to do for free. These do that job: a full-bleed black
   band carrying one picture and one line of display type, echoing the
   reference site's full-width slides between its content sections.

   The band bleeds; the picture does not. The photography tops out at 1024px,
   so running it edge to edge at 1440 would soften it — the same reason the
   hero film sits at its native 640 rather than being stretched to a banner.
   Only the black goes full width.
   -------------------------------------------------------------------------- */

.interstitial {
  background: var(--ink);
  color: var(--paper);
  padding-block: var(--sp-6);
}

.interstitial .grid {
  align-items: center;
  row-gap: var(--sp-4);
}

/* No `width: 100%`. That forced the picture to fill its column, which upscaled
   the two portrait files 1.4x past native — the exact thing the hero refuses to
   do, and what the note above claims is not happening here. The cap keeps every
   one at or below its own size, and the max-height keeps the three bands within
   sight of each other: at native the portrait file made a band 1122px tall,
   taller than a laptop viewport, which reads as a destination rather than the
   beat between two sections. */
.interstitial__media img {
  width: auto;
  max-width: 100%;
  max-height: 420px;
}

/* No .knock here. On the page a quote knocks itself out in black bars; inside
   the band the ground is already black, so the type simply reverses and the
   bars would paint ink on ink. */
.interstitial .quote {
  color: var(--paper);
  max-width: 24ch;
}

/* Reversed variant — copy left, picture right. Source order follows the
   desktop reading order, so at narrow widths it is flipped back to put the
   picture first: every other interstitial leads with the image, and one
   leading with a quote instead would read as a mistake. */
@media (max-width: 900px) {
  .interstitial--reverse .interstitial__media { order: -1; }
  .interstitial .grid { display: flex; flex-direction: column; }
}

/* --------------------------------------------------------------------------
   22. Programs — the Passport list/map, and the Market Street strip

   Two maps, both inline SVG, no library and no tile server. They inherit
   `currentColor` from the surrounding text, so they are ink on paper like
   everything else and need no colour of their own.

   The list is the content and the map is the index to it. That ordering is
   deliberate and holds at every width: below 900px the map moves ABOVE the
   list but keeps its own scroll position, and with no JavaScript at all the
   list still reads top to bottom.
   -------------------------------------------------------------------------- */

/* Placeholder banner. Loud on purpose — this data is invented, and the site is
   public. See README, "Placeholder data". */
.pp-notice {
  border-left: 6px solid var(--ink);
  padding: var(--sp-1) 0 var(--sp-1) var(--sp-2);
  margin-bottom: var(--sp-3);
  font-size: 0.9375rem;
  max-width: 54ch;
}

.pp-meta {
  font-size: 0.9375rem;
  color: var(--ink-60);
  margin-top: var(--sp-2);
}

/* --- layout: map beside list ------------------------------------------- */

.pp-layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--sp-5) var(--gutter);
  align-items: start;
}

/* The map holds its place while the list scrolls past it — the whole point of
   pairing them. Offset by the sticky header so it never hides under it. */
.pp-map {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-2));
}

.pp-map__svg {
  display: block;
  width: 100%;
  height: auto;
  color: var(--ink);
}

.pp-map__coast path { stroke-width: 2.4; vector-effect: non-scaling-stroke; }

/* Dashed, and thicker than the coast: the two bridges are the fastest way to
   read this shape as the Bay rather than as an abstract outline. */
.pp-map__bridges line {
  stroke-width: 3.5;
  stroke-dasharray: 9 7;
  vector-effect: non-scaling-stroke;
}

.pp-map__hint,
.pp-map__credit { font-size: 0.8125rem; color: var(--ink-60); }
.pp-map__hint { margin-bottom: var(--sp-2); }
.pp-map__credit { margin-top: var(--sp-2); }

/* --- pins --------------------------------------------------------------- */

/* Squares, not teardrops. Every other mark on this site is a rectangle. */
.pp-pin { cursor: pointer; }
.pp-pin rect {
  fill: var(--ink);
  transition: none;
}

.pp-pin__n {
  font-family: var(--body);
  font-size: 22px;
  fill: var(--ink-60);
  text-anchor: middle;
}

.pp-pin:hover rect,
.pp-pin:focus rect,
.pp-pin.is-lit rect {
  fill: var(--sky);
  /* Grow from the centre. The rect is placed at x-10/y-10 for a 20x20 box, so
     the box's own centre is its x+10 — scaling about that keeps it put. */
  transform: scale(1.55);
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

.pp-pin:hover .pp-pin__n,
.pp-pin:focus .pp-pin__n,
.pp-pin.is-lit .pp-pin__n { fill: var(--sky); }

.pp-pin:focus { outline: none; }
.pp-pin:focus-visible rect { outline: 3px solid var(--sky); outline-offset: 3px; }

/* --- filters ------------------------------------------------------------ */

.pp-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-bottom: var(--sp-2);
}

.pp-filter {
  font-family: var(--body);
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 7px 12px;
  border: 2px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}

.pp-filter:hover { background: var(--paper-2); }
.pp-filter.is-on { background: var(--ink); color: var(--paper); }

.pp-count {
  font-size: 0.8125rem;
  color: var(--ink-60);
  margin-bottom: var(--sp-2);
}

/* --- the cards ----------------------------------------------------------- */

/* A passport is a stack of numbered pages, so this is a stack of numbered
   entries rather than a photo grid — which also means no invented body needs
   an invented photograph. */
.pp-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  border-top: 2px solid var(--ink);
}

.pp-card { border-bottom: 2px solid var(--ink); }
.pp-card[hidden] { display: none; }
.pp-card.is-lit { background: var(--sky-soft); }

.pp-card summary {
  display: grid;
  grid-template-columns: 5.5rem 1fr auto;
  gap: 2px var(--sp-2);
  align-items: baseline;
  padding: var(--sp-2) 0;
  cursor: pointer;
  list-style: none;
}

.pp-card summary::-webkit-details-marker { display: none; }
.pp-card summary:focus-visible { outline: 3px solid var(--sky); outline-offset: -3px; }

.pp-card__page {
  font-family: var(--body);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.pp-card__name {
  font-family: var(--display);
  font-size: 1.25rem;
  line-height: 1.15;
}

.pp-card__hood {
  grid-column: 2;
  font-size: 0.875rem;
  color: var(--ink-60);
}

.pp-card__when {
  grid-column: 3;
  grid-row: 1 / span 2;
  font-size: 0.875rem;
  color: var(--ink-60);
  white-space: nowrap;
}

.pp-card__detail { padding: 0 0 var(--sp-3) 5.5rem; }
.pp-card__detail p { max-width: 58ch; }

.pp-card__event {
  font-family: var(--display);
  font-size: 1.0625rem;
  margin-bottom: var(--sp-1);
}

.pp-card__addr { font-size: 0.875rem; color: var(--ink-60); }

/* --- Mid-Market strip ---------------------------------------------------- */

/* One street drawn flat. Market Street really is straight from the Embarcadero
   to Castro, so this is a projection onto its own axis rather than a map of an
   area — which is why it can be a strip a few hundred pixels tall instead of a
   square. */
.vf-map { color: var(--ink); }
.vf-map__svg { display: block; width: 100%; height: auto; overflow: visible; }

.vf-map__street { stroke: currentColor; stroke-width: 4; }
.vf-map__band { fill: var(--sky-soft); }
.vf-map__cross line { stroke: currentColor; stroke-width: 1.5; }

.vf-map__cross text,
.vf-map__label {
  font-family: var(--body);
  font-size: 17px;
  fill: var(--ink-60);
  text-anchor: middle;
}

.vf-map__label {
  font-size: 19px;
  fill: var(--ink);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vf-pin rect { fill: var(--ink); }
.vf-pin:hover rect,
.vf-pin:focus rect,
.vf-pin.is-lit rect { fill: var(--sky); }
.vf-pin:focus { outline: none; }
.vf-pin:focus-visible rect { outline: 3px solid var(--sky); outline-offset: 3px; }

.vf-venues { list-style: none; margin: 0 0 var(--sp-2); padding: 0; }
.vf-venues li {
  border-bottom: 1px solid var(--rule);
  padding: var(--sp-1) 0;
}
.vf-venues li.is-lit { background: var(--sky-soft); }

/* --- the Programs list on the index -------------------------------------- */

.prog-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4) var(--gutter);
}

.prog { border-top: 2px solid var(--ink); padding-top: var(--sp-2); }

.prog__when {
  font-family: var(--body);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-60);
  margin-bottom: var(--sp-1);
}

.prog__name { margin-bottom: var(--sp-1); }
.prog__name a { color: var(--ink); text-decoration-thickness: 2px; }
.prog__name a:hover { color: var(--sky); }
.prog__meta { font-size: 0.875rem; color: var(--ink-60); }

/* --- narrow --------------------------------------------------------------- */

@media (max-width: 1024px) {
  /* The map stops being a companion to the list and becomes a header for it.
     Sticky is dropped with the side-by-side layout: pinned to the top of a
     single column it would eat a third of a phone screen for the whole scroll. */
  .pp-layout { grid-template-columns: minmax(0, 1fr); }
  .pp-map { position: static; max-width: 520px; }
  .prog-list { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .pp-card summary { grid-template-columns: 1fr auto; }
  .pp-card__page { grid-column: 1; }
  .pp-card__name,
  .pp-card__hood { grid-column: 1; }
  .pp-card__when { grid-column: 2; grid-row: 1; }
  .pp-card__detail { padding-left: 0; }
  .vf-map__cross text { font-size: 22px; }
}

/* --------------------------------------------------------------------------
   23. Motion
   -------------------------------------------------------------------------- */

/* The block below zeroes durations but not delays, which would leave the
   header labels invisible for 70ms after the bar snapped into place. */
@media (prefers-reduced-motion: reduce) {
  .site-nav__list a:hover,
  .cta-notch:hover { transition-delay: 0s; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* --------------------------------------------------------------------------
   24. Print
   -------------------------------------------------------------------------- */

@media print {
  .site-header, .btn, .site-footer, .footer-bars { display: none; }
  body { font-size: 11pt; }
}
