/* The whole site's styling. One file, on purpose.
   It used to be shell.css plus a Python string inside site.py, glued
   together at build time, which meant changing how anything looked was a
   Python edit. */

/* ── the system underneath ───────────────────────────────────────────────────
   Everything below sizes and spaces itself from this block. Nothing further
   down should invent a font-size or a section margin.

   Why it exists: before 16 August this stylesheet held eleven separate display
   sizes — clamp(46,148), clamp(30,64), clamp(34,60), clamp(34,58),
   clamp(38,76), clamp(24,38), clamp(18,26), 34px, 20px and two more — each
   written by hand at a different moment, with no ratio between any of them.
   Vertical gaps were the same: sections sat 40, 48, 56, 64, 72 and 96px apart
   with nothing deciding which. A broken grid only reads as intentional when the
   reader can feel the grid it broke, and there was no grid.

   The typeface is deliberately untouched. --sans is the owner's decision and
   the built site is the artifact of it. tools/test-design-rules.py holds that
   line and four others; run it before touching anything here.

   Note for anyone editing comments in this file: that test greps the whole
   stylesheet, comments included. Naming a banned declaration in prose is enough
   to trip it. Describe it in words instead. */

:root{
  --paper:#FFFFFF; --sunk:#F6F6F6; --mount:#ECECEC;
  --ink:#111111; --grey:#5F5F5F; --mute:#767676; --hair:#DCDCDC;
  --link:#0A6E55; --alert:#C1290F;
  /* the inverted block. A ground, never a text colour. One per page at most. */
  --flip-bg:#111111; --flip-fg:#FFFFFF; --flip-dim:#B4B4B4; --flip-hair:#3A3A3A;
  --sans:"acumin-pro","Acumin Pro","Helvetica Neue",Helvetica,Arial,sans-serif;

  /* Text. The three that were already here, unchanged, still the workhorse. */
  --t-body:16px; --t-small:15px; --t-cap:13px; --u:8px;

  /* Display. Four steps, roughly 1.6x apart at full width, fluid 390 to 1400.
     --d1 is the one commanding line on a page and most pages have none.
     --d2 names a record. */
  --d1:clamp(42px,8.4vw,104px);
  --d2:clamp(30px,4.4vw,58px);
  --d3:clamp(23px,2.6vw,34px);
  --d4:clamp(19px,1.4vw,22px);
  --lede:clamp(16px,1.4vw,19px);

  /* Space. --band is the distance between top-level sections, everywhere. Two
     sections a different distance apart is a bug, not a decision. */
  --band:clamp(52px,6.5vw,92px);
  --half:clamp(26px,3.2vw,46px);

  /* Motion. One duration, one curve, switched off at the foot of this file for
     anyone who has asked for less. */
  --beat:160ms; --ease:cubic-bezier(.2,.6,.3,1);

  /* Image shapes. Three, and no more: upright, object, scene. Six were in use —
     5/4, 4/5, 4/3, 3/4, 1/1 and a 168x224 in raw pixels — which is why no two
     rows of pictures on the site shared a proportion. */
  --ar-tall:3/4;    /* a person, a window card, a programme cover */
  --ar-sq:1/1;      /* an object: a record sleeve, a gallery thumbnail */
  --ar-wide:4/3;    /* a scene, a building, a lead picture */

  /* The page measure, and the distance from the viewport edge to the first
     character of body text. Anything that bleeds past the measure and then has
     to line up again reads --gutter rather than guessing at it. */
  --wrapw:min(1100px,92vw);
  --pad:calc(var(--u)*3);
  --gutter:calc((100vw - var(--wrapw))/2 + var(--pad));

  color-scheme:light dark;
}
@media (min-width:1500px){ :root{--wrapw:min(1360px,88vw)} }
@media (min-width:1900px){ :root{--wrapw:min(1580px,84vw)} }
@media (max-width:820px){ :root{--pad:calc(var(--u)*2)} }

@media (prefers-color-scheme:dark){
  :root{--paper:#0E0E0E; --sunk:#1C1C1C; --mount:#252525; --ink:#F2F0EB;
        --grey:#A6A6A6; --mute:#8A8A8A; --hair:#343434;
        --link:#4FC79E; --alert:#FF7256;
        --flip-bg:#F2F0EB; --flip-fg:#0E0E0E; --flip-dim:#565656; --flip-hair:#C9C6BF}
}
:root[data-theme="light"]{--paper:#FFFFFF; --sunk:#F6F6F6; --mount:#ECECEC; --ink:#111111;
  --grey:#5F5F5F; --mute:#767676; --hair:#DCDCDC; --link:#0A6E55; --alert:#C1290F;
  --flip-bg:#111111; --flip-fg:#FFFFFF; --flip-dim:#B4B4B4; --flip-hair:#3A3A3A}
:root[data-theme="dark"]{--paper:#0E0E0E; --sunk:#1C1C1C; --mount:#252525; --ink:#F2F0EB;
  --grey:#A6A6A6; --mute:#8A8A8A; --hair:#343434; --link:#4FC79E; --alert:#FF7256;
  --flip-bg:#F2F0EB; --flip-fg:#0E0E0E; --flip-dim:#565656; --flip-hair:#C9C6BF}

/* Contrast, computed rather than eyeballed. sRGB relative luminance, each
   colour against the paper of its own theme:

     light  --grey  #5F5F5F on #FFFFFF   6.39:1  pass
            --mute  #767676 on #FFFFFF   4.54:1  pass   was #8C8C8C, 3.36:1 — fail
            --link  #0A6E55 on #FFFFFF   6.21:1  pass
            --alert #C1290F on #FFFFFF   5.85:1  pass
     dark   --grey  #A6A6A6 on #0E0E0E   8.89:1  pass
            --mute  #8A8A8A on #0E0E0E   5.59:1  pass   was #7A7A7A, 4.50:1 — on the line
            --link  #4FC79E on #0E0E0E   9.19:1  pass
            --alert #FF7256 on #0E0E0E   7.17:1  pass
     flip   --flip-dim #B4B4B4 on #111111   8.70:1  pass
            --flip-dim #565656 on #F2F0EB   6.55:1  pass

   --mute carries most of the small type on the site: .grid-small figcaption at
   11px, .otd-pic figcaption, .sitefoot, the .nil em dash, every ::placeholder.
   At #8C8C8C none of it met AA and the smallest type was the worst of it.

   --hair is a border and exempt from the text rule, but the dark value was so
   close to the paper it sat on (#2C2C2C on #0E0E0E, 1.3:1) that .cards — which
   draws its entire structure out of 1px hairlines — had no visible structure at
   all in dark. Raised to #343434. */

*{box-sizing:border-box}
html,body{margin:0}
/* Four things on this site are wider than the measure — the hero band, the
   contribute block, the reel of tiles and the day's lead photograph — plus
   --gutter, which is derived from 100vw. 100vw counts the scrollbar and the
   scrollport does not, so each of them overhangs the page by the scrollbar's
   width and asks for a horizontal scrollbar it does not need. `clip` refuses
   the scroll without making the viewport a scroll container, so sticky
   positioning still works. Safari 15 and older ignore it and behave as they did
   before. */
html{overflow-x:clip}
body{background:var(--paper); color:var(--ink); font-family:var(--sans);
  font-size:var(--t-body); line-height:1.5; -webkit-font-smoothing:antialiased}
a{color:inherit; transition:color var(--beat) var(--ease)}
a:focus-visible,button:focus-visible{outline:2px solid var(--link); outline-offset:2px}
img{max-width:100%}

/* Phosphor Icons, MIT. One weight, one 256 grid, one fill, no exceptions. */
.sprite{position:absolute; width:0; height:0; overflow:hidden}
.ic{width:18px; height:18px; fill:currentColor; flex:none; display:block}

.wrap{width:var(--wrapw); margin:0 auto; padding:0 var(--pad) calc(var(--u)*12)}

.bar{display:flex; justify-content:space-between; align-items:center; gap:calc(var(--u)*3);
  padding:calc(var(--u)*2) 0; border-bottom:1px solid var(--hair); font-size:var(--t-small);
  flex-wrap:wrap}
.brand{font-weight:700; text-decoration:none; white-space:nowrap; font-size:17px;
  letter-spacing:-.01em}
/* Search in the nav on every page but the front. Somebody who has scrolled to
   a show page and then thinks of another show should not have to go home. */
.navsearch{flex:1 1 220px; min-width:0; margin:0}
.navsearch input{width:100%; font:inherit; padding:7px 11px; background:var(--paper);
  color:var(--ink); border:1px solid var(--hair); border-radius:0;
  transition:border-color var(--beat) var(--ease)}
.navsearch input:hover{border-color:var(--grey)}
.navsearch input:focus{outline:2px solid var(--link); outline-offset:-1px}
.bar nav{display:flex; align-items:center; flex-wrap:wrap; white-space:nowrap}
.bar nav a.on{color:var(--ink); box-shadow:inset 0 -2px 0 var(--link)}
.bar nav a.signin{color:var(--ink); font-weight:600}
@media (max-width:720px){
  .navsearch{order:3; flex:1 1 100%; margin-top:var(--u)}
  .bar nav a{margin-left:calc(var(--u)*2)}
}
.bar nav a{margin-left:calc(var(--u)*3); text-decoration:none; color:var(--grey)}
.bar nav a:hover{color:var(--ink)}
.mode{font:inherit; font-size:var(--t-cap); background:none; color:var(--grey);
  border:1px solid var(--hair); padding:3px 9px; cursor:pointer; margin-left:calc(var(--u)*3);
  transition:color var(--beat) var(--ease), border-color var(--beat) var(--ease)}
.mode:hover{color:var(--ink); border-color:var(--ink)}

.still{width:100vw; margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  margin-bottom:var(--half)}
.frame{position:relative}
/* direct child on purpose. `.still img` also matches the title treatment inside
   .mark, and forcing that to 2.8/1 with object-fit:cover crops the wordmark. */
.frame > img{width:100%; display:block; aspect-ratio:2.8/1; object-fit:cover}
.still .fade{position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom, transparent 26%, var(--paper) 97%, var(--paper) 100%)}
.still .credit{position:absolute; right:var(--gutter); bottom:2px;
  color:var(--grey); font-size:var(--t-cap)}
.frame{position:relative}
.still{position:relative}

/* straddles the foot of the image. sits in the fade, masks the seam. */
.mark{position:absolute; left:var(--gutter); bottom:-7%; width:min(340px,42%); z-index:1}
.mark img{width:100%; height:auto; display:block;
  filter:drop-shadow(0 1px 3px rgba(255,255,255,.55))}
@media (prefers-color-scheme:dark){.mark img{filter:drop-shadow(0 1px 4px rgba(0,0,0,.6))}}
:root[data-theme="dark"] .mark img{filter:drop-shadow(0 1px 4px rgba(0,0,0,.6))}
:root[data-theme="light"] .mark img{filter:drop-shadow(0 1px 3px rgba(255,255,255,.55))}
/* scoped under .mark on purpose: a bare .logo-dark loses to `.mark img` and
   both marks render stacked. */
.mark .logo-dark{display:none} .mark .logo-light{display:block}
@media (prefers-color-scheme:dark){.mark .logo-dark{display:block} .mark .logo-light{display:none}}
:root[data-theme="dark"] .mark .logo-dark{display:block}
:root[data-theme="dark"] .mark .logo-light{display:none}
:root[data-theme="light"] .mark .logo-dark{display:none}
:root[data-theme="light"] .mark .logo-light{display:block}

.lead{display:flex; justify-content:space-between; align-items:center;
  gap:calc(var(--u)*2); margin:0 0 calc(var(--u)*2)}
/* This h1 had no rule of its own and was falling through to the browser
   default — 32px bold with a 21px margin — on the page most readers land on. */
.lead h1{font-size:var(--d2); line-height:1.02; font-weight:600;
  letter-spacing:-.022em; margin:0; max-width:18ch}
.path{font-size:var(--t-small); color:var(--grey); margin:0}
.path a{color:var(--grey); text-decoration:none}
.path a:hover{color:var(--ink)}
.path .here{color:var(--link)}

.lede{font-size:var(--lede); line-height:1.55; margin:0 0 calc(var(--u)*3); max-width:74ch}

/* pills carry facts. filled, round, mostly inert. */
.tags{display:flex; flex-wrap:wrap; gap:calc(var(--u)*.75); margin:0 0 calc(var(--u)*2.5)}
.tag{background:var(--sunk); border-radius:999px; padding:6px 15px;
  font-size:var(--t-cap); line-height:1.35; color:var(--grey); white-space:nowrap}
.tag b{color:var(--ink); font-weight:600}
a.tag{text-decoration:none; transition:background var(--beat) var(--ease)}
a.tag:hover{background:var(--ink); color:var(--paper)}
a.tag:hover b{color:var(--paper)}

/* actions are icons. nothing in this row reads as a pill. */
.acts{display:flex; gap:calc(var(--u)*.5); margin:0 -9px 0 0; flex:none}
.acts button{display:grid; place-items:center; width:38px; height:38px;
  background:none; color:var(--grey); border:1px solid transparent; cursor:pointer; padding:0;
  transition:color var(--beat) var(--ease), border-color var(--beat) var(--ease)}
.acts button:hover{color:var(--ink); border-color:var(--hair)}

/* ── one heading, two variants ───────────────────────────────────────────────
   `h2` labels a table or a grid on a record page and rules itself off below.
   `h2.rule` opens a section on an index page and rules itself off above.

   They used to be different sizes, weights and trackings, and — because .rule
   never cancelled the base rule — every front-page heading drew a 2px ink line
   above it AND a 1px hairline below it. That doubled rule is a good part of why
   the front page read as noise.

   Anything else that sets its own look on an h2 has to cancel the base as well:
   see h2.big below, h2.plain in search.css, and .way h2 in home.css. */
h2{font-size:var(--t-small); font-weight:600; letter-spacing:.02em;
  margin:var(--half) 0 calc(var(--u)*2);
  padding-bottom:var(--u); border-bottom:1px solid var(--hair);
  display:flex; justify-content:space-between; align-items:baseline; gap:var(--u)}
h2 span{display:flex; align-items:center; gap:calc(var(--u)*1.25)}
h2 .ic{color:var(--grey)}
h2 em{font-style:normal; font-weight:400; color:var(--grey); font-size:var(--t-small); text-align:right}
h2 em.gone{color:var(--alert)}

.docs{display:grid; grid-template-columns:repeat(4,1fr); gap:calc(var(--u)*3) calc(var(--u)*2.5)}
.doc{text-decoration:none; display:block}
.doc .art{position:relative; aspect-ratio:var(--ar-sq); border:1px solid var(--hair);
  overflow:hidden; background:var(--sunk);
  transition:border-color var(--beat) var(--ease)}
.doc .art img{width:100%; height:100%; object-fit:cover; display:block}
.doc .art .none{position:absolute; inset:0; display:grid; place-content:center; justify-items:center;
  gap:6px; color:var(--mute); font-size:var(--t-cap); text-align:center; padding:10px}
.doc:hover .art{border-color:var(--ink)}
.doc b{display:block; font-size:var(--t-small); font-weight:600; color:var(--link);
  margin-top:var(--u); line-height:1.3}
.doc:hover b{color:var(--ink)}
.doc span{display:block; font-size:var(--t-cap); color:var(--grey); margin-top:2px; line-height:1.4}
.doc[href="#"] b{color:var(--grey)}

.empty{display:flex; gap:calc(var(--u)*1.75); align-items:flex-start;
  border:1px dashed var(--hair); padding:calc(var(--u)*2.5); color:var(--grey);
  font-size:var(--t-small); line-height:1.6; max-width:74ch}
.empty .ic{color:var(--mute); margin-top:2px; width:20px; height:20px}
.empty b{display:block; color:var(--ink); font-weight:600; margin-bottom:2px}

table{width:100%; border-collapse:collapse}
td{padding:calc(var(--u)*1.35) 0; border-bottom:1px solid var(--hair); vertical-align:top; font-size:var(--t-small)}
td.n{width:38px; color:var(--mute)}
td .who{display:block; color:var(--grey); font-size:var(--t-cap); margin-top:2px}
.note{font-size:var(--t-cap); color:var(--grey); margin:var(--u) 0 0; max-width:74ch; line-height:1.6}

.company{columns:2; column-gap:calc(var(--u)*5); font-size:var(--t-small); line-height:1.95; max-width:64ch}
.company i{font-style:normal; color:var(--grey)}

.flag{display:flex; gap:calc(var(--u)*1.75); align-items:flex-start;
  border:1px solid var(--alert); padding:calc(var(--u)*2); margin-top:calc(var(--u)*2.5); max-width:70ch}
.flag .ic{color:var(--alert); margin-top:1px; width:20px; height:20px}
.flag b{display:block; color:var(--alert); font-weight:600; font-size:var(--t-small); margin-bottom:4px}
.flag p{margin:0; font-size:var(--t-small); color:var(--grey)}

.stats{margin:0 0 var(--half); max-width:520px}
.stats div{display:flex; gap:calc(var(--u)*2.5); line-height:1.9}
.stats dt{color:var(--grey); flex:0 0 168px; font-size:var(--t-small)}
.stats dd{margin:0; font-size:var(--t-small)}
.stats dd a{color:var(--link); text-decoration:none}

.quote{background:var(--sunk); padding:calc(var(--u)*2.5); font-size:var(--t-small); line-height:1.6}
.quote p{margin:0 0 var(--u)}
.quote cite{font-style:normal; color:var(--grey); font-size:var(--t-cap)}

.foot{border-top:1px solid var(--hair); margin-top:var(--band); padding-top:calc(var(--u)*2);
  font-size:var(--t-cap); color:var(--grey); line-height:1.7; max-width:82ch}
.foot b{color:var(--ink); font-weight:500}

.cols{display:grid; grid-template-columns:minmax(0,1.6fr) minmax(240px,.4fr); gap:0 calc(var(--u)*6)}
/* the aside is ~250px. A label column plus a value column breaks "Stephen
   Sondheim" across two lines, so stack instead of splitting. */
aside .stats{max-width:none; margin-bottom:calc(var(--u)*3)}
aside .stats div{display:block; line-height:1.45; padding:calc(var(--u)*.85) 0;
  border-bottom:1px solid var(--hair)}
aside .stats div:last-child{border-bottom:0}
aside .stats dt{flex:none; font-size:var(--t-cap); color:var(--grey)}
aside .stats dd{font-size:var(--t-small); margin-top:1px}

@media (max-width:820px){
  .wrap{padding:0 var(--pad) calc(var(--u)*9)}
  .bar{flex-wrap:wrap; gap:calc(var(--u)*.5) calc(var(--u)*2); padding:calc(var(--u)*1.5) 0}
  /* The nav used to be `a:not(:first-child){display:none}` below 820px, which
     put Collections, Identify a record, About and Sign in out of reach of every
     phone on the site. It wraps onto its own row now and nothing is hidden. */
  .bar nav{flex:1 1 100%; margin-left:0; white-space:normal;
    column-gap:calc(var(--u)*2.25); row-gap:calc(var(--u)*.5);
    font-size:var(--t-cap); padding-top:calc(var(--u)*.5)}
  .bar nav a{margin-left:0}
  .mode{margin-left:auto}
  .still{margin-bottom:calc(var(--u)*3)}
  /* A 2.8:1 band is 139px tall on a 390px screen, which is a stripe rather than
     a photograph. The hero is the one place on the site that changes its crop
     with the width of the window. */
  .frame > img{aspect-ratio:16/10}
  /* the mark overhangs the frame, so the credit cannot sit on top of it. Below
     the image on its own line, clear of the overhang. */
  .frame{margin-bottom:calc(var(--u)*3.5)}
  .still .credit{position:static; display:block; text-align:right;
    padding:0 var(--pad)}
  .mark{left:var(--pad); bottom:-6%; width:min(230px,52%)}
  .docs{grid-template-columns:repeat(2,1fr)}
  .cols{grid-template-columns:1fr}
  .company{columns:1}
  .stats dt{flex:0 0 128px}
}

/* ── album page ───────────────────────────────────────────────────────────── */
.object{display:grid; grid-template-columns:minmax(0,420px) minmax(0,1fr);
  gap:calc(var(--u)*5); align-items:start; margin:0 0 var(--half)}
.object .sleeve{border:1px solid var(--hair); background:var(--sunk)}
.object .sleeve img{width:100%; display:block}
/* [hidden] is display:none in the UA sheet, which an author display:block beats.
   Say it again here or every face of the sleeve stacks. */
.object .sleeve img[hidden]{display:none}
.object .flip{display:flex; gap:var(--u); margin-top:var(--u)}
.object .flip button{flex:1; font:inherit; font-size:var(--t-cap); background:none;
  color:var(--grey); border:1px solid var(--hair); padding:7px; cursor:pointer;
  transition:color var(--beat) var(--ease), border-color var(--beat) var(--ease)}
.object .flip button:hover{color:var(--ink); border-color:var(--grey)}
.object .flip button[aria-pressed="true"]{color:var(--ink); border-color:var(--ink)}
.object h1{font-size:var(--d3); line-height:1.08; font-weight:600; margin:0 0 calc(var(--u)*1.5);
  letter-spacing:-.015em}
.object .billing{font-size:var(--t-small); color:var(--grey); line-height:1.6;
  margin:0 0 calc(var(--u)*2.5); max-width:52ch}
.object .billing b{color:var(--ink); font-weight:500}

.sides{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 calc(var(--u)*6)}
.sides h3{font-size:var(--t-cap); font-weight:600; color:var(--grey);
  margin:0 0 var(--u); padding-bottom:6px; border-bottom:1px solid var(--hair)}
@media (max-width:820px){ .object{grid-template-columns:1fr} .sides{grid-template-columns:1fr} }

.notices{display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:calc(var(--u)*2.5) calc(var(--u)*4)}
.notices blockquote{margin:0; font-size:var(--t-small); line-height:1.6; color:var(--ink)}
.notices cite{display:block; font-style:normal; color:var(--grey);
  font-size:var(--t-cap); margin-top:6px}
@media (max-width:820px){ .notices{grid-template-columns:1fr} }

/* ── show page ────────────────────────────────────────────────────────────── */
.people{display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); gap:calc(var(--u)*2.5) calc(var(--u)*2)}
.person{text-decoration:none; display:block}
.person .face{aspect-ratio:var(--ar-tall); border:1px solid var(--hair); background:var(--sunk);
  overflow:hidden; position:relative; transition:border-color var(--beat) var(--ease)}
.person .face img{width:100%; height:100%; object-fit:cover; object-position:center 22%; display:block}
.person .face .none{position:absolute; inset:0; display:grid; place-items:center;
  color:var(--mute); font-size:22px; font-weight:600}
.person:hover .face{border-color:var(--ink)}
.person b{display:block; font-size:var(--t-cap); font-weight:600; margin-top:6px; line-height:1.3}
.person span{display:block; font-size:var(--t-cap); color:var(--grey); line-height:1.3}
@media (max-width:820px){ .people{grid-template-columns:repeat(3,minmax(0,1fr))} }

.runs{width:100%; border-collapse:collapse}
.runs td{padding:calc(var(--u)*1.35) 0; border-bottom:1px solid var(--hair); font-size:var(--t-small); vertical-align:top}
.runs td.yr{width:70px; color:var(--mute)}
.runs td.rt{text-align:right; color:var(--grey); font-size:var(--t-cap); white-space:nowrap}
.runs a{color:var(--link); text-decoration:none; font-weight:600}
.runs tr:hover a{color:var(--ink)}
.runs .sub{display:block; color:var(--grey); font-size:var(--t-cap); margin-top:2px}

.lit{border-top:1px solid var(--hair)}
.lit li{list-style:none; padding:calc(var(--u)*2) 0; border-bottom:1px solid var(--hair);
  font-size:var(--t-small); line-height:1.6; max-width:80ch}
.lit{margin:0; padding:0}
.lit cite{display:block; font-style:normal; color:var(--grey); font-size:var(--t-cap); margin-top:6px}

.chips{display:flex; flex-wrap:wrap; gap:calc(var(--u)*.75)}
.chips span{background:var(--sunk); padding:6px 15px; border-radius:999px;
  font-size:var(--t-cap); color:var(--grey)}

/* ── licensing ────────────────────────────────────────────────────────────── */
.rights{width:100%; border-collapse:collapse}
.rights td{padding:calc(var(--u)*1.5) calc(var(--u)*2) calc(var(--u)*1.5) 0;
  border-bottom:1px solid var(--hair); font-size:var(--t-small); vertical-align:top}
.rights td.terr{width:96px; color:var(--grey); font-size:var(--t-cap); padding-top:calc(var(--u)*1.7)}
.rights b{font-weight:600}
.rights .imp{display:block; color:var(--grey); font-size:var(--t-cap); margin-top:2px}
.rights .out{display:inline-flex; align-items:center; gap:5px; color:var(--link);
  text-decoration:none; font-weight:600; white-space:nowrap}
.rights .out:hover{color:var(--ink)}
.rights .out .ic{width:14px; height:14px}
.rights .refused{color:var(--alert); font-weight:600}
.rights .refused-why{display:block; color:var(--grey); font-weight:400; font-size:var(--t-cap); margin-top:3px}
.rights td.st{text-align:right; color:var(--grey); font-size:var(--t-cap); white-space:nowrap}
@media (max-width:820px){
  .rights td.st{display:none}
  .rights td.terr{width:64px}
}

/* an em dash is the null. A field with no value shows one, muted, so a reader can
   tell an empty field from a short one at a glance. */
.nil{color:var(--mute)}
.rights .nil{font-weight:400}

/* a show with no title treatment sets its name instead. This is the common case. */
.still .plain{position:absolute; left:var(--gutter); bottom:-2px; margin:0; z-index:1;
  font-size:var(--d2); line-height:.94; font-weight:600; letter-spacing:-.025em;
  color:var(--ink); max-width:13ch}
@media (max-width:820px){ .still .plain{left:var(--pad); bottom:2px} }

td.act{font-size:var(--t-cap); font-weight:600; color:var(--grey); padding-top:calc(var(--u)*2.5)}

.portrait{margin-bottom:calc(var(--u)*3)}
.portrait img{width:100%; display:block; border:1px solid var(--hair)}
.portrait span{display:block; color:var(--grey); font-size:var(--t-cap); margin-top:6px}

/* ── .whole — an image whose stated terms do not permit cropping ──────────────

   Seven surfaces on this site crop in the browser: .frame > img at 2.8/1,
   .doc .art img, .grid-small img and .results .art img at 1/1,
   .person .face img at 3/4, .otd-pic img at 4/3, .stand-in img at 3/4 — all
   object-fit:cover. **Keep this list current.** It is the register every
   cropping restriction gets checked against, so a surface missing from it is
   a surface nobody will check. .results .art img was added on 17 August and
   the count said six for several hours. A browser
   letterboxing a portrait to 2.8:1 is the same act as a script doing it, and
   web/cropping.py cannot reach it, because it happens after the file is served.

   Measured 16 August against the running site: of the 62 pages holding a Carl
   Van Vechten photograph, 45 rendered one cropped — 42 squared off in
   .grid-small and 3 squeezed into the 2.8:1 hero band. The Library of Congress
   records that

       "the Carl Van Vechten estate has asked that use of Van Vechten's
        photographs 'preserve the integrity' of his work"

   which is the clause `cropping.py` matches. Nobody has ruled on whether it
   binds a CSS crop the way it binds scissors — that is licensing-gaps' call —
   but a stylesheet should not be the thing that answers it.

   So: the class opts an image OUT of cropping, and `cover` stays the default.
   An image that should be whole and is not marked is a rendering we did not
   intend; an image marked and rendered whole is just a whole picture. Applied
   by the `whole` filter in web/app.py, which asks cropping.crop_rule() rather
   than carrying a second opinion about the same restriction.

   The aspect-ratio is deliberately KEPT. Dropping it would let a 3:4 portrait
   set its own height in a 1:1 gallery slot and jump the row — the box stays
   exactly where it was and the picture sits inside it.

   Changed 16 August, appearance only. The ground was --sunk, which in the light
   theme is #F6F6F6 against a #FFFFFF card: a two per cent difference that reads
   as a rendering failure rather than as a decision. It sits on --mount now, a
   distinctly darker ground, with a hairline drawn round the box. The picture
   reads as mounted rather than as a crop that did not work. Outline rather than
   border so the box does not grow, and rather than an inset box-shadow because
   engines disagree about whether that paints above replaced content.

   64% of affected images were near-square when this was measured, so in a 1:1
   slot most show a hairline of mount and no more. Every count in this comment is
   from 16 August 2026 and the library is still growing; findings/blur.md carries
   the dated working. */
img.whole{object-fit:contain !important; background:var(--mount);
  outline:1px solid var(--hair); outline-offset:-1px}

/* ── site ─────────────────────────────────────────────────────────────────── */
.bare{padding:var(--band) 0 calc(var(--u)*3)}
.bare h1{font-size:var(--d2); line-height:1.02; font-weight:600;
  letter-spacing:-.022em; margin:0; max-width:16ch}
.masthead{padding:var(--band) 0 var(--half)}
.masthead h1{max-width:26ch; font-size:var(--d2); line-height:1.02; font-weight:600;
  letter-spacing:-.022em; margin:0}
.masthead .lede{margin-top:calc(var(--u)*3); max-width:62ch}

.find{display:flex; align-items:baseline; gap:calc(var(--u)*2);
  border-bottom:1px solid var(--hair); padding-bottom:calc(var(--u)*1.5);
  margin-bottom:calc(var(--u)*2)}
.find input{flex:1; font:inherit; font-size:var(--d4); background:none; border:0;
  color:var(--ink); padding:calc(var(--u)*1.5) 0; outline:none}
.find input::placeholder{color:var(--mute)}
.find #count{color:var(--grey); font-size:var(--t-cap); white-space:nowrap}

.browse{width:100%; border-collapse:collapse}
.browse th{text-align:left; font-size:var(--t-cap); font-weight:600; color:var(--grey);
  padding:0 calc(var(--u)*2) var(--u) 0; border-bottom:1px solid var(--hair)}
.browse th.rt{text-align:right}
.browse td{padding:calc(var(--u)*1.4) calc(var(--u)*2) calc(var(--u)*1.4) 0;
  border-bottom:1px solid var(--hair); font-size:var(--t-small); vertical-align:top}
.browse td.yr{width:64px; color:var(--mute)}
.browse td.rt{text-align:right; color:var(--grey); font-size:var(--t-cap); white-space:nowrap}
.browse a{color:var(--link); text-decoration:none; font-weight:600}
.browse tr:hover a{color:var(--ink)}
/* A decade link in the muted column stays muted. It is wayfinding, not the
   row's subject — 59 of the 60 rows on a page carry one, and at --link/600
   they out-shout the show titles, which are the thing you came to click.
   `latch` flagged this hierarchy as the thing to preserve if the hover states
   are ever restyled; the three rules below are disposable, that is not. */
.browse td.rt a{color:inherit; font-weight:400; text-decoration:underline;
  text-decoration-color:var(--hair); text-underline-offset:3px}
.browse td.rt a:hover{color:var(--ink); text-decoration-color:var(--ink)}
.browse tr:hover td.rt a{color:inherit}
.browse .sub{display:block; color:var(--grey); font-size:var(--t-cap); margin-top:2px}
@media (max-width:820px){ .browse td.rt:last-child, .browse th.rt:last-child{display:none} }

/* ── .results — a row on /shows ───────────────────────────────────────────────

   The rule above this one is why this block exists. `.browse` hid its last
   column below 821px, `Type` was the last column, and the owner searched
   "gypsy" on a phone and got eight rows that could not tell him whether he was
   looking at shows, productions or recordings. The word that would have said
   so had been deleted by a media query.

   So: **one layout at every width.** Nothing hides, nothing reflows into a
   different shape, and there is no second layout to keep working. At 390 it is
   a 72px picture and a text block wrapping to three or four lines; at 1400 it
   is the same row with a bigger picture and a bigger title.

   An ordered list because results are ranked, with the numbers switched off —
   a screen reader was announcing a three-column data table for what is a
   ranked list. No grid of cards at any width: a grid destroys rank, and rank
   is the only thing making the top of this page useful.

   Square pictures rather than --ar-tall. In a vertical list the thumbnail sets
   the row height, 1:1 is the shortest of the three shape tokens that is still
   a real shape, and 64% of the library is near-square anyway (see f_whole), so
   the crop changes little. img.whole is global and !important, so an image
   whose terms refuse cropping letterboxes here with no rule of its own.

   The title steps up to --d4 at 900px and no further. That is the one type
   call in this block and it is the specification's recommendation rather than
   its instruction — --sans and the scale are the owner's. Deleting the line
   leaves everything else working. */
.results{list-style:none; margin:0; padding:0; border-top:1px solid var(--hair)}
.results li{display:grid; grid-template-columns:72px minmax(0,1fr);
  gap:calc(var(--u)*2); padding:calc(var(--u)*2) 0;
  border-bottom:1px solid var(--hair)}
/* `.result-lead` and not `.lead` in the pairs below. `.lead` was already taken
   at line 190 — the flex row holding the h1 on show.html, production.html and
   venue.html — which I did not check before naming this, and the rename to
   `.result-lead` reached the template and the block at 588 but not these six
   shared selectors. The lead result then rendered with a grid column and an
   aspect-ratio and none of the box or type rules: a natural-size image
   overflowing an unbordered slot. Found by measuring the page, 17 August 2026.
   The other `.lead` holds nothing but an h1, so none of these ever applied to
   it and moving them cannot affect those three pages. */
.results .art,.result-lead .art{aspect-ratio:var(--ar-sq); background:var(--sunk);
  border:1px solid var(--hair); overflow:hidden; display:block;
  transition:border-color var(--beat) var(--ease)}
.results .art img,.result-lead .art img{width:100%; height:100%; object-fit:cover; display:block}
.results li:hover .art,.result-lead:hover .art{border-color:var(--ink)}
.results a.t,.result-lead .t a{color:var(--link); text-decoration:none; font-weight:600;
  font-size:var(--t-body); line-height:1.25}
.results li:hover a.t,.result-lead .t a:hover{color:var(--ink)}
.results .meta,.result-lead .meta{color:var(--grey); font-size:var(--t-cap);
  margin:4px 0 0; line-height:1.5}
.results .meta a,.result-lead .meta a{color:inherit; text-decoration:underline;
  text-decoration-color:var(--hair); text-underline-offset:3px}
.results .meta a:hover,.result-lead .meta a:hover{color:var(--ink); text-decoration-color:var(--ink)}
.results .why{color:var(--grey); font-size:var(--t-cap); margin:4px 0 0; line-height:1.5}
/* The browser's yellow highlight does not belong on this site. The matched
   words step up to full ink and 600 instead, which is enough to find them in a
   grey line and not enough to shout over the title. */
.results mark,.result-lead mark{background:none; color:var(--ink); font-weight:600}
.results .also,.result-lead .also{color:var(--grey); font-size:var(--t-cap);
  margin:6px 0 0; line-height:1.5}
.results .also a,.result-lead .also a{color:inherit; text-decoration:underline;
  text-decoration-color:var(--hair); text-underline-offset:3px}
.results .also a:hover,.result-lead .also a:hover{color:var(--ink); text-decoration-color:var(--ink)}
@media (min-width:560px){ .results li{grid-template-columns:96px minmax(0,1fr)} }
@media (min-width:900px){
  .results li{grid-template-columns:112px minmax(0,1fr)}
  .results a.t{font-size:var(--d4)}
}

/* The lead result. One per page at most, and only when the reader named one
   show and we hold a picture of it — see shows_index().

   **Square, and the column is capped.** The specification asked for --ar-wide
   across the left three fifths, which was written without a screenshot and is
   wrong twice over. At 1280 that box is 612 x 440, and the commonest thing to
   land in it is a record sleeve — `sleeve-front` leads HERO_TYPES — so a 1:1
   picture letterboxed inside a 4:3 hole, on a grey mount, with the right hand
   column two thirds empty underneath four lines of type. Measured 16 August
   2026 on "gypsy" and "chess". --ar-sq is the token whose own comment reads
   "an object: a record sleeve", which is what this is.

   The row keeps the shape of every other row rather than becoming a different
   component: same grid, same square, a picture roughly three times the size,
   the title up a step and a rule under it. That is enough to read as the
   answer without pushing the rest of the results below two screenfolds on a
   phone, which the full-width version did. */
.result-lead{display:grid; grid-template-columns:minmax(0,140px) minmax(0,1fr);
  gap:calc(var(--u)*2); padding-bottom:calc(var(--u)*3);
  margin-bottom:calc(var(--u)*2); border-bottom:2px solid var(--ink)}
.result-lead .art{aspect-ratio:var(--ar-sq)}
.result-lead .t{font-size:var(--d3); line-height:1.05; letter-spacing:-.022em;
  font-weight:600; margin:0; border:0; padding:0; display:block}
.result-lead .lede{font-size:var(--t-small); color:var(--ink); line-height:1.55;
  margin:calc(var(--u)*1.5) 0 0; max-width:60ch}
@media (min-width:560px){
  .result-lead{grid-template-columns:minmax(0,220px) minmax(0,1fr); gap:calc(var(--u)*3)}
}
@media (min-width:900px){
  .result-lead{grid-template-columns:minmax(0,320px) minmax(0,1fr);
    gap:calc(var(--u)*4); align-items:start}
}

/* The empty state. No sort control, no table headers, no list — just the
   sentence and two ways out of it. */
.nowt{padding:var(--half) 0 var(--band); max-width:62ch}
.nowt .lede{margin-bottom:calc(var(--u)*2)}
.nowt p{color:var(--grey); font-size:var(--t-small); line-height:1.6;
  margin:0 0 var(--u)}
.nowt .lede{color:var(--ink); font-size:var(--lede)}
.nowt a{color:var(--link)}

/* ── added for the served site ─────────────────────────────────────────── */
.cols{display:grid; grid-template-columns:minmax(0,2.4fr) minmax(0,1fr);
  gap:calc(var(--u)*6); align-items:start}
@media (max-width:900px){ .cols{grid-template-columns:1fr} }
.grid-small{display:grid; grid-template-columns:repeat(auto-fill,minmax(96px,1fr));
  gap:var(--u)}
.grid-small figure{margin:0}
.grid-small img{width:100%; aspect-ratio:var(--ar-sq); object-fit:cover; display:block;
  background:var(--sunk)}
.grid-small figcaption{font-size:11px; color:var(--grey); margin-top:3px}
aside figure{margin:0 0 calc(var(--u)*3)}
aside figure img{width:100%; display:block}
aside figcaption{font-size:var(--t-cap); color:var(--grey); margin-top:6px}
.empty{color:var(--grey); padding:var(--half) 0}

/* ── editorial front page ──────────────────────────────────────────────────

   The order of loudness, from the top: the opening line at --d1, then the day's
   photograph running off the left edge of the page, then everything level until
   the inverted band at the foot. Two moves and no more. The rest of the page is
   deliberately one size — reels of tiles and ruled lists — because a page where
   six things shout is a page where nothing does. */

.hunt{display:flex; gap:0; margin:calc(var(--u)*3) 0 var(--band);
  border-bottom:2px solid var(--ink)}
.hunt input{flex:1; font:inherit; font-size:var(--d4);
  background:none; border:0; color:var(--ink); padding:calc(var(--u)*1.5) 0;
  outline:none; min-width:0}
.hunt input::placeholder{color:var(--mute)}
.hunt button{font:inherit; font-size:var(--t-small); font-weight:600;
  background:none; border:0; color:var(--ink); cursor:pointer;
  padding:0 0 0 calc(var(--u)*2); letter-spacing:.02em;
  transition:color var(--beat) var(--ease)}
.hunt button:hover{color:var(--link)}

.front{padding:calc(var(--u)*2) 0 var(--band); max-width:none}
.front.tight{padding-bottom:var(--half)}
.huge{font-size:var(--d1); line-height:.88;
  letter-spacing:-.045em; font-weight:700; margin:0; max-width:14ch;
  text-wrap:balance}

/* ── .asked — the h1 on /shows when there is a query ─────────────────────────

   A masthead earns its size when it names the page and loses it when it echoes
   an input. `/shows` with no query says "Every show" and that is the only
   statement the page makes about itself, so it keeps `.huge`. `/shows?q=gypsy`
   renders the reader's own word at --d1, 42px on a phone, about 40px under the
   search box still holding that exact string. That is a repetition. Raised by
   `visual`, ruled by `latch`.

   **The height it saves is 20px, not the 72 the framing implied, and the case
   rests on the repetition rather than on the pixels.** Measured at 390x844:
   `.front` 143px to 123px, of which 12px is the smaller heading and 8px is the
   tightened gap below it. The other 123 are not the heading and mostly must
   not move — 16px padding above, ~48px of `.front-sub`, 26px of `--half`
   below. `.front` being "the largest single block above the first result" was
   true and led everyone, me included, to expect most of it to be the title. It
   was 37px of 143.

   The <h1> stays. The document outline needs one and a screen reader should
   still be told what page this is; it is the *size* that was wrong, not the
   element. `visual` declined to do this as a font-size on the grounds that a
   heading at half size reads as a heading that lost an argument rather than
   one that was cut — hence a separate class rather than an override, and the
   number below is theirs to change.

   What must not shrink is `.front-sub` underneath. That sentence says which of
   the three passes answered, how many shows came back and what order they are
   in. It is the most load-bearing line on the page and it costs almost nothing
   in height. */
.asked{font-size:var(--d3); line-height:1.1; letter-spacing:-.022em;
  font-weight:600; margin:0; max-width:24ch; overflow-wrap:anywhere}
.front-sub{font-size:var(--lede); color:var(--grey);
  max-width:52ch; margin:calc(var(--u)*2) 0 0; line-height:1.5}
/* `a{color:inherit}` at :124 means a link inside this grey sentence renders as
   grey text with no affordance at all — and on the front page this sentence
   carries the plate's theatre link and "We hold three recordings of it", which
   are the only route from the largest element on the page to a theatre page.
   It is load-bearing rather than cosmetic: `landing-words` removed the plate's
   separate exits row on the grounds that the sentence already carries those
   links, which only works if a reader can see them.

   **Green, and that was argued.** I proposed `--ink` with a hairline, to keep
   green out of a page whose one field of colour is now the red band.
   `landing-ui` overruled it on scope and they are right: the tail links went to
   `--ink` because they sit *directly beneath* the band and land in one
   screenful with it. The plate is the first block on the page and the band is
   second to last — they are never co-visible. Two further reasons: green is the
   site's link colour on every other page, and teaching a reader that links look
   different here costs more than the proximity saves; and `--ink` plus a
   hairline is this site's *de-emphasis* pattern (`.browse td.rt a`, :507), used
   for wayfinding that must not out-shout its row. These links are the subject,
   not wayfinding. */
.front-sub a{color:var(--link); text-decoration:none;
  box-shadow:inset 0 -1px 0 var(--hair);
  transition:box-shadow var(--beat) var(--ease)}
.front-sub a:hover{box-shadow:inset 0 -2px 0 var(--link)}
/* The date line under the plate. A single exit, set as one. */
.datestrip{margin-top:var(--half)}
.datestrip a{color:var(--ink)}
.datestrip a:hover{color:var(--link)}
.front:has(.asked) .front-sub{margin-top:var(--u)}

/* Not an eyebrow. It was 13px, set in capitals, tracked out to .06em, in the
   link green, sitting directly above a headline — the exact shape the house
   style bans. The sentence it carries is a real fact ("106 years ago today"),
   so the fix is to set it as one: sentence case, no tracking, grey, and small
   enough that the headline underneath still leads.

   tools/test-design-rules.py permits one capitalised heading in this file and
   reserves it for this class. It does not require one. There are none now. */
.kicker{font-size:var(--t-small); color:var(--grey); font-weight:400;
  letter-spacing:0; text-transform:none; margin:0 0 calc(var(--u)*1.5)}
.big{font-size:var(--d2); line-height:.98; letter-spacing:-.03em;
  font-weight:700; margin:0 0 calc(var(--u)*2); max-width:20ch}
.big a{text-decoration:none}
.big a:hover{color:var(--link)}
/* .big is also set on an <h2> in some templates, where without this it inherits
   the base h2's flex box, hairline and padding and draws a rule under itself. */
h2.big{display:block; border-bottom:0; padding-bottom:0; font-weight:700}
.standfirst{font-size:var(--lede); line-height:1.5; color:var(--ink);
  max-width:60ch; margin:0 0 calc(var(--u)*3)}
.big-sf{font-size:var(--d4); line-height:1.42; max-width:44ch;
  margin-top:var(--half)}

h2.rule{display:flex; align-items:baseline; justify-content:space-between;
  gap:calc(var(--u)*2); border-top:2px solid var(--ink); border-bottom:0;
  padding-top:var(--u); padding-bottom:0;
  margin:var(--band) 0 var(--half); font-size:var(--t-small);
  font-weight:600; letter-spacing:.02em}
h2.rule em{font-style:normal; color:var(--grey); font-weight:400}
h2.rule a{color:var(--link); text-decoration:none}
h2.rule a:hover{color:var(--ink)}
/* A section heading followed by a sentence saying why the section exists sits
   closer to it than to the content, or the sentence reads as a caption for the
   thing below rather than as a gloss on the heading above. */
h2.rule:has(+ .note){margin-bottom:calc(var(--u)*1.5)}

/* ── the front page opening line ─────────────────────────────────────────────
   The page's h1 and the site's one commanding move. Block 0 in index.html, and
   the answer to "I have no idea where I am": it says what is in the catalogue
   before the page shows you any of it.

   --d1, which nothing else on this page uses. Below it the day's show title is
   --d2 and the two search instructions are --d4, so the page steps 104 / 58 /
   22 / 15 and a reader can see the order of things before reading any of them.

   `.ask` is shared with the two search instructions, which is why the size
   lives on the descendant rather than on the class.

   **Removing block 0 is a conversation, not a deletion.** An earlier version
   of this comment said the rule goes with it and nothing else on the page
   changes. That stopped being true once the ladder was anchored here: --d1 is
   used by nothing else, and the 104 / 58 / 22 / 15 steps below it are set
   against it. Pull those two lines and the page reverts to opening on a 1927
   production, which is the failure the owner reported — "I have no idea where
   I am or what I'm supposed to do." So if the headline has to go, what carries
   the top of the page is the question to answer first.

   **Revised 17 August: this comes down to --d3 and --d1 moves to the show's
   name.** The paragraph above was written when block 1 rendered no photograph
   on three of its four states; index() now puts one there on all 366 days, so
   the reason for anchoring the ladder here is gone and only the cost is left.
   The cost was an inversion — the largest type on a theatre history site was a
   sentence about the site, above a photograph of a show whose own name was 44%
   of its size — and that inversion is most of what "no hierarchy" meant.
   findings/landing-ui.md §10 argues it against `visual`, by name, and accepts
   that the first half of their case was right when they made it.

   Two columns rather than a stack, ruled off below: the headline left on 24ch,
   the standfirst right. It stops being a masthead a reader has to walk past and
   becomes a caption on the whole page. */
.top{display:grid; grid-template-columns:minmax(0,7fr) minmax(0,5fr);
  gap:var(--half); align-items:baseline;
  border-bottom:1px solid var(--hair); padding-bottom:var(--half);
  margin-bottom:var(--band)}
.top .ask{font-size:var(--d3); line-height:1.05; font-weight:700;
  letter-spacing:-.025em; max-width:24ch; text-wrap:balance}
.top .standfirst{font-size:var(--lede); color:var(--grey); max-width:54ch;
  margin:0}
@media (max-width:820px){
  .top{grid-template-columns:1fr; gap:var(--u); align-items:start}
}

/* ── the plate's title ───────────────────────────────────────────────────────
   --d1, used exactly once per page, and now on the show's own name.

   It sits ABOVE the photograph and across the whole measure, which is the
   change that buys the hierarchy. In the right-hand column it had 424px, and at
   104px that column holds about five characters to a line — which is why the
   biggest type on the page ended up being the generic sentence above it
   instead. On the full 1052px measure --d1 holds about twenty characters on one
   line.

   .long is set by the template when the title runs past 24 characters: a
   headline fitted by length rather than by taste, and no JavaScript. The jump a
   reader sees from here to the sentence underneath is 104 → 19, which is 5.5×,
   and it is the loudest thing in the whole specification. It costs nothing. */
/* It is an <h2> because the block has no other heading — with one production
   the sentence is the heading, and an h2.rule over a name set at 104px was a
   label on something that labels itself. The base h2 gives every h2 a flex box,
   a bottom hairline and its own padding, so it is cancelled here the same way
   `h2.big` cancels it. Without the cancel this drew a rule under the site's
   largest type. */
h2.plate-title{display:block; border-bottom:0; padding-bottom:0}
.plate-title{font-size:var(--d1); line-height:.88; font-weight:700;
  letter-spacing:-.04em; margin:0 0 var(--half); max-width:20ch;
  text-wrap:balance}
.plate-title.long{font-size:var(--d2); line-height:.98; letter-spacing:-.03em;
  max-width:22ch}
.plate-title a{color:inherit; text-decoration:none}
.plate-title a:hover{color:var(--link)}

/* on this day: one big item, then a tight list. Deliberately not a grid.

   The photograph runs off the LEFT edge of the viewport while the text stays on
   the measure. That is where the front page gets its presence, and it is a
   different move from the record pages' .still/.mark band on purpose — that
   band is how a record page announces its own subject, and borrowing it here
   would make the front page look like a page about one 1927 production, which
   is the confusion the owner reported. A picture that escapes the column on one
   side is a magazine's answer, not a masthead's.

   It pairs with the reel, which escapes to the right. One direction each, so
   the page has a rhythm rather than a set of accidents. Both are held inside
   the viewport by html{overflow-x:clip}.

   The columns are bottom-aligned, so the headline's last line lands on the foot
   of the photograph. At 820px it becomes one column, picture first, bleeding
   both edges — a full-width lead image, which is the right thing on a phone.

   Third state: index() can find nothing it may illustrate, and the template
   omits the <figure> entirely. The 1.35fr/1fr grid would then leave the text
   alone in the right-hand column with a hole beside it, so the rule below drops
   the grid and sets the text at a reading measure instead. */
.otd-lead{display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);
  gap:var(--half); align-items:end; margin-bottom:var(--half)}
.otd-lead:not(:has(.otd-pic)){display:block; max-width:62ch}

/* ── the plate takes its shape from the source, not from the slot ────────────
   The picture that had been leading the site was 500×810 upright, forced into
   this fixed 4:3 landscape box: 62% of its height cropped away and the rest
   upscaled about 1.4×. index() will not offer that file the plate any more, but
   the frame was wrong on its own terms — a fixed hole letterboxes whatever is
   most likely to land in it, which is the lesson `results-build` learned on
   /shows.

   The class comes from the server (`|shape`), so there is no JavaScript and no
   layout shift. An upright picture narrows to columns 1–5 and gives the text
   column the width it loses; a square one takes columns 1–6. The plate is the
   one block on the page with no horizontal neighbour, so a variable height
   costs nothing at all. Nothing here touches the crop policy. */
.otd-lead.wide .otd-pic img{aspect-ratio:var(--ar-wide)}
.otd-lead.sq{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr)}
.otd-lead.sq .otd-pic img{aspect-ratio:var(--ar-sq)}
.otd-lead.tall{grid-template-columns:minmax(0,.85fr) minmax(0,1fr)}
.otd-lead.tall .otd-pic img{aspect-ratio:var(--ar-tall)}
/* An image whose stated terms refuse a crop keeps its own proportions rather
   than letterboxing inside a frame. `img.whole` mounts a picture in a grey
   surround, which is right in a reel of tiles and wrong at 798px — a grey
   border around the site's lead image reads as a rendering failure. */
.otd-lead .otd-pic img.whole{aspect-ratio:auto; object-fit:contain;
  background:none; height:auto}
/* The state with no lead at all is gone, and so are the rules that dressed it.
   The block used to have four states, three of them without a photograph, and
   the two rules here promoted the day's list to the block's own weight on those
   days. The owner's ruling of 17 August removed the list from the front page —
   "it should only feature one production" — and `plate_rotation()` guarantees a
   picture on all 366 days, so there is no picture-less state left to design
   for. `.otd-list`, `.otd-head` and the promotion rules came out with it. The
   list itself still exists at /on/MM-DD, which has always had its own markup
   (`onthisday.html`, a `.browse` table) and never used these classes. */
.otd-pic{margin:0 0 0 calc(-1 * var(--gutter))}
.otd-pic img{width:100%; aspect-ratio:var(--ar-wide); object-fit:cover; display:block;
  background:var(--sunk)}
/* Styled since this file was written and rendered by no template until
   17 August — the credit was going into a title= attribute, which is invisible
   on touch and is not a caption to a screen reader. decisions.md §11 names the
   photographer as one of three classes of claimant. Right-aligned under the
   photograph, so it reads as belonging to the picture rather than opening the
   text column beside it. */
.otd-pic figcaption{font-size:var(--t-cap); color:var(--mute); margin-top:6px;
  padding-left:var(--gutter); text-align:right}
.at{display:block; grid-column:2; color:var(--grey); font-size:var(--t-cap);
  margin-top:2px}
.dim{color:var(--mute)}

/* The no-picture plate state. The owner: "If there is no image, then there is
   no image. Do not put a placeholder image anymore." No photograph, no field
   generated to stand in for one — just the frame's shape and `--mount`, the
   token the site already uses for a slot with nothing in it, so it reads as a
   deliberate empty ground rather than as an image that failed. The caption
   beneath still says what it means. */
.otd-pic .mount{width:100%; aspect-ratio:var(--ar-tall); background:var(--mount);
  display:block}

/* ── a filter menu, on /people and /venues ───────────────────────────────────
   New UI, and written rather than borrowed: nothing on the site was a row of
   filter links, and reaching for `.egs` — which styles the "Try Sondheim" line
   — would have been a class meaning "examples" doing a different job.

   Deliberately a row of links and not a control. `landing-ia`'s rule for these
   two pages is that a sort rack hands the reader a decision the catalogue
   should have made; a named filter with its count is a door, and it reads as
   one. The count sits in `.n-inline`, the same treatment the doors block and
   `/collections` already use for a number annotating a link.

   The selected one goes to `--ink` and stops being a link, so the row shows
   where you are rather than only where you could go. */
.facets{display:flex; flex-wrap:wrap; gap:var(--u) calc(var(--u)*3);
  align-items:baseline; margin:0 0 var(--half);
  padding-bottom:calc(var(--u)*1.5); border-bottom:1px solid var(--hair);
  font-size:var(--t-small)}
.facets a{color:var(--link); text-decoration:none; font-weight:600;
  transition:color var(--beat) var(--ease)}
.facets a:hover{color:var(--ink)}
.facets a.on{color:var(--ink); cursor:default}
.facets .n-inline{margin-left:4px}

/* ── the doors ───────────────────────────────────────────────────────────────
   Ten named ways into the catalogue, one line each, no pictures. The page's
   middle tier of detail: the plate above carries five facts about one
   production, these carry a phrase and a count, the reels below carry two facts
   a tile. Without a middle the page steps from one enormous picture straight to
   rows of thumbnails, which is what the day's list was doing before the owner
   ruled it off.

   Two columns of ruled lines, one on a phone. Set at --t-small in the link
   colour with the count in --mute and tabular figures, so the labels form a
   left edge a reader can run down and the numbers form a right one. Deliberately
   NOT cards and deliberately not a reel: nine identical bordered boxes is the
   shape the house style bans, and these are all the same weight because they
   genuinely are — no door here is more important than another.

   The hairline is on the <li> rather than the <a>, so a door with no count
   still rules off at the same place as one with a count. */
.doors{margin:0 0 var(--band)}
.doors ul{list-style:none; margin:0; padding:0; columns:2;
  column-gap:calc(var(--u)*6)}
.doors li{break-inside:avoid; display:flex; justify-content:space-between;
  gap:calc(var(--u)*2); align-items:baseline;
  padding:calc(var(--u)*1.4) 0; border-bottom:1px solid var(--hair)}
.doors a{color:var(--link); text-decoration:none; font-weight:600;
  font-size:var(--t-small); line-height:1.35;
  transition:color var(--beat) var(--ease)}
.doors a:hover{color:var(--ink)}
.doors .n-inline{flex:none}
@media (max-width:720px){ .doors ul{columns:1} }

/* The day's collection, leading the Collections section. It used to be its own
   top-level block and drew a 2px ink rule above itself; it now sits directly
   under that section's h2.rule, which draws that line already. Two heavy rules
   one under the other is the bug this file spent the morning removing, so this
   one separates itself from the cards below with a hairline instead. */
.today{border-top:0; padding-top:0; border-bottom:1px solid var(--hair);
  padding-bottom:var(--half); margin:0 0 var(--half)}
.pick{list-style:none; margin:0 0 calc(var(--u)*3); padding:0; columns:2;
  column-gap:calc(var(--u)*6)}
.pick li{break-inside:avoid; padding:calc(var(--u)*1.2) 0;
  border-bottom:1px solid var(--hair)}
.pick a{color:var(--link); text-decoration:none; font-weight:600}
.pick a:hover{color:var(--ink)}
.pick .at{grid-column:auto}

.more{margin:calc(var(--u)*3) 0 0; font-size:var(--t-small)}
.more a{color:var(--link); text-decoration:none; font-weight:600}
.more a:hover{color:var(--ink)}

/* ── the two ways in ─────────────────────────────────────────────────────────
   Two instructions, each over its own field. Both are .ask, shared with the
   page's h1, so the size is set here rather than on the class. --d4 puts the
   instruction at exactly the size of the field beneath it: a label and its
   input, read as one object, the label bold and the field not.

   The second is not demoted. It is the thing no other catalogue does, and a
   reader deciding which box to use should meet two equal offers rather than a
   main one and an afterthought. `.way.second .ask` is the hook if that turns
   out to be wrong; home.css leaves the decision to this file. */
.way .ask{font-size:var(--d4); line-height:1.25; font-weight:700;
  letter-spacing:-.01em; max-width:28ch}
.way .note{margin-top:calc(var(--u)*1.5); max-width:46ch}

/* ── collection cards ────────────────────────────────────────────────────────
   Fixed columns rather than auto-fit, because a lead item cannot span two of
   something whose count nobody knows. Three at full width, two from 900px, one
   from 600px. */
.cards{display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--half) calc(var(--u)*3); background:none; border:0}
.card{background:var(--paper); padding:calc(var(--u)*3); border:1px solid var(--hair);
  transition:border-color var(--beat) var(--ease)}
.card:hover{border-color:var(--grey)}
.card.on{background:var(--sunk)}
.card h3{margin:0 0 var(--u); font-size:var(--d4); line-height:1.15; letter-spacing:-.015em}
.card h3 a{text-decoration:none}
.card h3 a:hover{color:var(--link)}
/* The count on a collection card. It used to be a 38px bare numeral — "112"
   in green over a card headed "The title song", leaving the reader to guess
   whether that meant songs, shows or years. It now carries its unit, and at
   that length it is a line of type rather than a hero number. */
.card .n{font-size:var(--t-small); line-height:1.2; font-weight:600;
  margin:0 0 calc(var(--u)*1.5); color:var(--link);
  font-variant-numeric:tabular-nums}
.card .standfirst{font-size:var(--t-small); color:var(--grey);
  margin-bottom:calc(var(--u)*1.5)}
.card .hint{list-style:none; margin:0; padding:0; font-size:var(--t-cap);
  color:var(--mute)}
.card .hint li{padding:2px 0; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap}
@media (max-width:900px){ .cards{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (max-width:600px){ .cards{grid-template-columns:1fr} }

/* One card leads and the rest support it. Three identical cards in a row, with
   identical pictures and identical three-line summaries, is the shape the house
   style bans outright, and it is what this was.

   The lead spans two columns and sets its picture beside its text instead of
   above it, so the difference is a change of composition and not only a change
   of size.

   `:has(.card-pic)` sits on the CARD, not on the grid, and that placement is
   load-bearing. index.html renders .card-pic only when collection_art() found a
   picture, and featured_today() rotates the six collections by the date's
   ordinal — so on some days the first card has no picture at all. Scoped to the
   grid, this rule would still fire on those days and lay out a two-column card
   with an empty left column. Scoped to the card, the lead treatment simply does
   not apply and the day's front page is a plain three-up. It also excludes
   /collections for free, which repeats .cards once per group with no pictures
   anywhere in it.

   Children are placed explicitly rather than left to auto-flow, so a fourth
   element added to the card later lands in the text column instead of in the
   picture's row. */
.cards > .card:first-child:has(.card-pic){
  grid-column:span 2;
  display:grid; grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  column-gap:var(--half); align-content:start;
  grid-template-rows:auto auto 1fr}
.cards > .card:first-child:has(.card-pic) > *{grid-column:2}
.cards > .card:first-child:has(.card-pic) > .card-pic{
  grid-column:1; grid-row:1 / span 3; align-self:start; margin:0}
.cards > .card:first-child:has(.card-pic) > h3{grid-row:1; font-size:var(--d3);
  letter-spacing:-.02em; margin-bottom:calc(var(--u)*1.5)}
.cards > .card:first-child:has(.card-pic) > .standfirst{grid-row:2; font-size:var(--t-body)}
.cards > .card:first-child:has(.card-pic) > .hint{grid-row:3; align-self:start}
@media (max-width:600px){
  /* the broken grid collapses to one deliberate column: picture above text, the
     same order as every other card, rather than a squashed two-up. */
  .cards > .card:first-child:has(.card-pic){grid-column:auto; display:block}
  .cards > .card:first-child:has(.card-pic) > .card-pic{margin:0 0 calc(var(--u)*1.5)}
}

/* A card with no picture, in a row of cards that have one. `art` is decided per
   card, so the grid genuinely mixes — and a card whose neighbours open with a
   photograph, and which opens with a small heading, reads as a picture that
   failed to load. It leads with its title at --d3 instead, so it is visibly a
   text card rather than a broken one. Only where the row mixes: on /collections
   no card has a picture and every heading stays at --d4. */
.cards:has(.card-pic) > .card:not(:has(.card-pic)) h3{font-size:var(--d3);
  letter-spacing:-.02em; margin-bottom:calc(var(--u)*1.5)}

.pager{display:flex; gap:calc(var(--u)*3); align-items:baseline;
  margin:var(--half) 0; font-size:var(--t-small); color:var(--grey);
  border-top:1px solid var(--hair); padding-top:calc(var(--u)*2)}
.pager a{color:var(--link); text-decoration:none; font-weight:600}
.pager a:hover{color:var(--ink)}

@media (max-width:820px){
  /* One deliberate column, in document order, and the one thing that must not
     happen is the plate shrinking into a column: it bleeds BOTH edges and is
     the widest thing on the screen. The .sq and .tall variants have to be
     overridden here as well as the base, or an upright picture keeps its
     narrower grid and the collapse leaves it 85% of a column it no longer
     shares. */
  .otd-lead, .otd-lead.sq, .otd-lead.tall{grid-template-columns:1fr}
  .otd-pic{margin-right:calc(-1 * var(--gutter))}
  .otd-pic figcaption{padding-right:var(--gutter)}
  /* An upright picture at full phone width is taller than the screen, so the
     sentence under it is below the fold on every phone. Capped, and the crop
     that costs is the same object-fit:cover the frame already applies —
     `img.whole` is excluded because its whole point is that it may not be
     cropped, and a picture that refuses a crop is allowed to be tall. */
  .otd-lead .otd-pic img:not(.whole){max-height:78vh}
  .pick{columns:1}
  .huge{max-width:none}
}

/* the generated collections are an index, not an argument — set them as one */
.tight-index{list-style:none; margin:0 0 var(--half); padding:0;
  columns:3; column-gap:calc(var(--u)*5)}
.tight-index li{break-inside:avoid; display:flex; justify-content:space-between;
  gap:var(--u); align-items:baseline; padding:calc(var(--u)*.9) 0;
  border-bottom:1px solid var(--hair); font-size:var(--t-small)}
.tight-index a{color:var(--link); text-decoration:none; font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.tight-index a:hover{color:var(--ink)}
.n-inline{color:var(--mute); font-size:var(--t-cap); flex:none;
  font-variant-numeric:tabular-nums}
@media (max-width:900px){ .tight-index{columns:2} }
@media (max-width:560px){ .tight-index{columns:1} }

/* a record we know is wrong, said out loud rather than rendered as fact */
.warn-box{border-left:3px solid var(--alert); background:var(--sunk);
  padding:calc(var(--u)*2); margin:calc(var(--u)*2) 0 var(--half);
  font-size:var(--t-small); max-width:70ch; line-height:1.5}

/* A picture of something near the show, never of the show. Deliberately small
   and captioned, so it cannot read as evidence.

   The grayscale stays — it is what stops the picture reading as documentation
   of the show. The opacity does not: at .85 on a dark ground the photograph
   went nearly black, and the caption was explaining a picture nobody could
   see. */
.stand-in{margin:0 0 var(--half); display:grid;
  grid-template-columns:170px minmax(0,1fr); gap:calc(var(--u)*3);
  align-items:center; max-width:64ch}
.stand-in img{width:100%; aspect-ratio:var(--ar-tall); object-fit:cover; display:block;
  background:var(--sunk); filter:grayscale(1);
  transition:filter var(--beat) var(--ease)}
.stand-in a:hover img{filter:none}
.stand-in figcaption{font-size:var(--t-small); color:var(--grey); line-height:1.5}
@media (max-width:600px){ .stand-in{grid-template-columns:1fr; max-width:none} }

/* ── venue: productions per decade ───────────────────────────────────────────
   A count per decade, drawn as a bar, because the shape of a building's
   working life is the one thing a table of 324 rows will not show you.
   Widths come from the server as a percentage of the busiest decade. */
.bars{list-style:none; margin:0 0 calc(var(--u)*2); padding:0}
.bars li{display:grid; grid-template-columns:58px minmax(0,1fr) 46px;
  gap:calc(var(--u)*1.5); align-items:center; padding:3px 0;
  font-size:var(--t-cap); color:var(--grey)}
.bars .k{font-variant-numeric:tabular-nums}
.bars .bar{background:var(--sunk); height:14px; display:block}
.bars .bar i{background:var(--link); height:100%; display:block; min-width:2px}
.bars .v{text-align:right; font-variant-numeric:tabular-nums; color:var(--ink)}

/* ── album: the candidate a date points at ───────────────────────────────────
   Marked, never merged, and the note underneath says it is a lead. */
.runs tr.lead-row td{background:var(--sunk)}

/* the two-column name-and-role list runs long on a big company; give it room
   rather than a third column that collapses to nothing on a phone. */
.company{margin:0 0 var(--u)}
.company a{color:var(--link); text-decoration:none}
.company a:hover{color:var(--ink)}

/* Identifiers and label strings are the only values long enough to push a
   240px aside sideways. Let them break anywhere rather than scroll the page. */
.stats dd{overflow-wrap:anywhere}
.company b, .company a{overflow-wrap:anywhere}


/* ── the site footer ─────────────────────────────────────────────────────────
   Added by the legal agent, because a privacy policy nobody can find is not a
   privacy policy. Quiet on purpose: this is the one piece of site chrome whose
   job is to be available rather than noticed. `.foot` above is a different
   thing — a note at the end of a record page — hence the separate name.

   Whoever owns site chrome should feel free to restyle or absorb this; the
   only requirement is that the three policy links stay reachable from every
   page. */
.sitefoot{border-top:1px solid var(--hair); margin-top:var(--band);
  padding:calc(var(--u)*3) 0 0; display:flex; flex-wrap:wrap;
  gap:var(--u) calc(var(--u)*3); align-items:baseline;
  font-size:var(--t-cap); color:var(--mute)}
.sitefoot a{color:var(--grey); text-decoration:none}
.sitefoot a:hover{color:var(--ink)}
.sitefoot .said{flex:1 1 22ch; color:var(--mute)}


/* ── the re-cut control ──────────────────────────────────────────────────────
   Proposed by `latch`, who owns the mechanics; the styling is here. Included by
   shows.html, collection.html, list.html and favourites.html — four pages, none
   of them the front page — and it must keep reaching all four. It sets no
   typeface: `font:inherit` throughout, deliberately.

   The overflow fix, 16 August, diagnosed by `latch` against his own change.
   voice.md requires every number to carry its unit, so the options went from
   "Revue (46)" to "Revue — 46 shows", and a <select> sizes itself to its
   longest option. Flex items default to a minimum width of their content and
   will not shrink below it, so at 390 the control ran off the side of the
   screen. A `min-width` on the label is the line that actually fixes it — it
   was `0` at first, and the floor two rules down explains why that was not
   enough; the other two keep the select inside it. The option text does not have to get
   shorter, which is the right outcome — the unit stays. */
.recut{display:flex; flex-wrap:wrap; gap:calc(var(--u)*2); align-items:flex-end;
  margin:0 0 calc(var(--u)*3); padding-bottom:calc(var(--u)*2);
  border-bottom:1px solid var(--hair)}
/* min-width:0 alone fixed the 390px overflow and let a label shrink to
   nothing. With three menus on the row — order, kind, and place — that
   squeezes every select around 600px until the longest option truncates
   and loses the unit at the end of it, which is the whole reason the
   option text is long. A floor makes them wrap instead, which is what
   flex-wrap is for. min() stops the floor becoming its own overflow
   below 16ch. Longest option is 'Play With Music — 2 shows', 25 chars. */
.recut label{display:flex; flex-direction:column; gap:6px;
  min-width:min(100%,16ch);
  font-size:var(--t-cap); color:var(--grey)}
.recut select,.recut button{font:inherit; font-size:var(--t-small);
  background:none; color:var(--ink); border:1px solid var(--hair);
  padding:7px 10px; transition:border-color var(--beat) var(--ease)}
/* Truncating in the closed state is a deliberate trade, not an oversight.
   Two-up at 390 leaves about 16 characters visible and the longest option,
   'Play With Music — 2 shows', is 25. It bites only once that option is
   selected, the default selection is short, and the native popup shows the
   whole string at the moment a reader is choosing. Much cheaper than the
   overflow it replaces. Firefox ignores text-overflow on a select and
   clips, which is today's behaviour, so it is a no-op there. */
.recut select{max-width:100%; overflow:hidden; text-overflow:ellipsis}
.recut button{color:var(--grey); font-weight:600}
.recut button:hover{color:var(--ink); border-color:var(--ink)}
@media (max-width:560px){
  /* Two-up rather than stacked. Measured on an iPhone 12 viewport against the
     live tree: the control drops 205px to 130px and the first result moves
     from y=694 to y=619 — 75px back on an 844px screen. Constraining the
     button as well was tested and gains nothing, so this is one rule or none.
     Composes with the 16ch floor above: at 390 the 45% basis is about 161px
     and clears the 133px floor, so two columns hold; near 320 the basis drops
     under the floor, the floor wins, and they stack again by themselves. */
  .recut label{flex:1 1 45%}
  .recut select{width:100%}
}

/* Why a portrait is out of focus, set above the credit and in the alert colour.
   A blurred face with no sentence beside it reads as a broken file. */
.portrait .why{color:var(--alert); margin-top:8px}


/* The picture on a collection card. 4:3 so a row of cards keeps its
   proportions whatever shape the photograph is. */
.card-pic{display:block; margin:0 0 calc(var(--u)*1.5)}
.card-pic img{width:100%; aspect-ratio:var(--ar-wide); object-fit:cover;
  background:var(--sunk); display:block}
.card-pic img.whole{object-fit:contain}


/* ── rows of shows on the front page ────────────────────────────────────────
   A horizontal reel, the way IMDb and TMDB present theirs. Scrolls with a
   swipe on a phone and with a trackpad on a desktop; no buttons, no
   JavaScript.

   It runs off the right edge of the viewport rather than stopping at the
   measure, and that is the whole affordance: a row cut by the edge of the
   screen is read as continuing, a row that ends neatly inside the column is
   read as all there is. No gradient, no arrows, nothing painted over the last
   tile. The left edge stays on the text grid, so the reel still lines up with
   the heading above it.

   A short row is fine and needs nothing: the tiles sit left-aligned, the row
   does not reach the edge, and it reads as complete because it is. */
.shelf{margin:0}
.shelf .note{font-size:var(--t-small); max-width:62ch; margin:0 0 var(--half)}
.reel{display:flex; gap:calc(var(--u)*2); overflow-x:auto;
  margin-right:calc(-1 * var(--gutter)); padding-right:var(--gutter);
  padding-bottom:calc(var(--u)*1.5);
  scroll-snap-type:x proximity; scroll-padding-left:0;
  -webkit-overflow-scrolling:touch}
.reel::-webkit-scrollbar{height:6px}
.reel::-webkit-scrollbar-thumb{background:var(--hair); border-radius:3px}
.tile{flex:0 0 clamp(140px,15vw,182px); scroll-snap-align:start;
  text-decoration:none; display:block;
  transition:transform var(--beat) var(--ease)}
.tile img{width:100%; aspect-ratio:var(--ar-tall); object-fit:cover; display:block;
  background:var(--sunk); margin-bottom:calc(var(--u)*1.25);
  outline:2px solid transparent; outline-offset:-2px;
  transition:outline-color var(--beat) var(--ease)}
/* Declared per row by what the row is mostly made of. A sleeve-led row is
   square: a square sleeve under object-fit:cover in a 3:4 box loses 25% of one
   dimension, and it loses it exactly where the title is printed. */
.reel.sq .tile img{aspect-ratio:var(--ar-sq)}
.reel.wide .tile img{aspect-ratio:var(--ar-wide)}
.tile img.whole{object-fit:contain}
.tile b{display:block; font-size:var(--t-small); font-weight:600; line-height:1.25}
.tile span{display:block; font-size:var(--t-cap); color:var(--grey); margin-top:2px}
.tile:hover{transform:translateY(-3px)}
.tile:hover b{color:var(--link)}
.tile:hover img{outline-color:var(--ink)}

/* ── the longest runs, ranked ────────────────────────────────────────────────
   This was a second reel of twelve identical tiles sitting directly under the
   first one. Two picture modules of the same weight, shape and size, one after
   the other, is most of what "no hierarchy, just a bunch of shit thrown at the
   user" meant — and a horizontal row of identical tiles destroys rank, which is
   the only thing this block is about.

   So: the number one at ~335px with its count set large, and rows 2 to 12 as a
   ruled list with a 56px identification thumbnail each. Same query, same twelve
   items, same exit. Eleven ruled rows occupy the height of one reel and carry a
   fact each, so this doubles the density while halving the noise.

   It is also the text module in the picture/text alternation: plate → search
   fields → reel → this → collections → band → tail, no two adjacent blocks the
   same shape. That alternation is what makes a dense page legible; the reference
   sites are dense and readable because their modules differ, not because they
   hold less.

   Tabular figures on every count, and the word "performances" sits beside the
   number rather than inside it, so right-aligning the column does not cost the
   reader the noun. voice.md's most-broken rule, and `landing-ia` made keeping it
   a condition of accepting this block. */
.champion{margin:0}
.champion .note{font-size:var(--t-small); max-width:62ch; margin:0 0 var(--half)}
.champ-grid{display:grid; grid-template-columns:minmax(0,4fr) minmax(0,8fr);
  gap:var(--half) calc(var(--u)*4); align-items:start}
.champ-one{text-decoration:none; display:block;
  transition:transform var(--beat) var(--ease)}
.champ-one img{width:100%; aspect-ratio:var(--ar-tall); object-fit:cover;
  display:block; background:var(--sunk); margin-bottom:calc(var(--u)*1.5);
  outline:2px solid transparent; outline-offset:-2px;
  transition:outline-color var(--beat) var(--ease)}
.champ-one.sq img{aspect-ratio:var(--ar-sq)}
.champ-one.wide img{aspect-ratio:var(--ar-wide)}
.champ-one img.whole{object-fit:contain}
.champ-one b{display:block; font-size:var(--d2); line-height:.98;
  letter-spacing:-.03em; font-weight:700; margin-bottom:var(--u)}
.champ-one:hover{transform:translateY(-3px)}
.champ-one:hover b{color:var(--link)}
.champ-one:hover img{outline-color:var(--ink)}
.champ-one .count{display:block; font-size:var(--lede); color:var(--ink);
  font-weight:400}
.champ-one .count i{font-style:normal; font-weight:700;
  font-variant-numeric:tabular-nums}
.champ-one .where{display:block; font-size:var(--t-cap); color:var(--grey);
  margin-top:2px}

/* The rank is the <ol>'s own counter, set in the left margin in tabular figures
   so 2 and 12 line up. It starts at 2 because the champion above it is 1, and
   `start="2"` in the markup keeps that true for a screen reader as well as for
   the eye. */
.ranked{list-style:none; margin:0; padding:0;
  counter-reset:rank 1; border-top:1px solid var(--hair)}
.ranked li{counter-increment:rank; border-bottom:1px solid var(--hair)}
.ranked a{display:grid;
  grid-template-columns:28px 56px minmax(0,1fr) auto;
  grid-template-rows:auto auto; gap:2px calc(var(--u)*2); align-items:center;
  padding:calc(var(--u)*1.5) 0; text-decoration:none; color:var(--ink)}
.ranked a::before{content:counter(rank); grid-column:1; grid-row:1 / span 2;
  color:var(--mute);
  font-size:var(--t-cap); font-variant-numeric:tabular-nums; text-align:right}
.ranked img{grid-column:2; grid-row:1 / span 2;
  width:56px; height:56px; object-fit:cover; display:block;
  background:var(--sunk)}
.ranked b{grid-column:3; grid-row:1; align-self:end;
  font-size:var(--t-small); font-weight:600; line-height:1.25;
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  transition:color var(--beat) var(--ease)}
.ranked .where{grid-column:3; grid-row:2; align-self:start;
  font-size:var(--t-cap); color:var(--grey);
  display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.ranked .count{grid-column:4; grid-row:1 / span 2; text-align:right;
  font-size:var(--t-small); color:var(--grey); white-space:nowrap}
.ranked .count i{font-style:normal; color:var(--ink); font-weight:600;
  font-variant-numeric:tabular-nums}
/* Colour only on hover. Twelve rows taking a background fill is a menu, not a
   list. */
.ranked a:hover b{color:var(--link)}
@media (max-width:820px){
  .champ-grid{grid-template-columns:1fr}
  .champ-one img{aspect-ratio:var(--ar-wide)}
  /* the theatre goes before the count is squeezed; a run length is the fact
     this block exists for and it keeps its unit at every width */
  .ranked .where{display:none}
  .ranked a{grid-template-columns:24px 44px minmax(0,1fr) auto}
  .ranked img{width:44px; height:44px}
}

/* The search fields on the front page, sized like the thing you came to use.
   No left padding, on purpose: the caret starts on the same vertical line as
   every other first character on the page, which is the only thing that makes a
   rule under a field look deliberate rather than approximate. */
.big-hunt{margin:calc(var(--u)*3) 0 0}
.big-hunt input{font-size:var(--d4); padding:calc(var(--u)*2) 0}
.big-hunt button{font-size:var(--t-small); padding:0 0 0 calc(var(--u)*3)}


/* An instruction above a field. `.ask` carries the shape — bold, tight, short
   measure — and the size comes from context: --d1 in .top, --d4 in .way.
   `.opener` was the wrapper before index.html was rebuilt; the rule stays
   because it costs nothing and other pages may still reach for it. */
.opener{margin:var(--half) 0 var(--band)}
.ask{font-size:var(--d3); line-height:1.12; font-weight:700;
  letter-spacing:-.02em; margin:0; max-width:26ch}
.egs{font-size:var(--t-small); color:var(--grey); margin:calc(var(--u)*2) 0 0;
  max-width:60ch; line-height:1.6}
.egs a{color:var(--link); text-decoration:none;
  box-shadow:inset 0 -1px 0 var(--hair);
  transition:box-shadow var(--beat) var(--ease)}
.egs a:hover{box-shadow:inset 0 -2px 0 var(--link)}


/* ── the invitation to contribute ────────────────────────────────────────────
   The one inverted block on the site, and the second of the front page's two
   loud moves. It was a grey slab with a 3px green rule on top, sitting under a
   heading that drew its own 2px black rule: two rules, one on top of the other,
   for one block.

   Colour blocking rather than ornament. The block takes the full width of the
   viewport and swaps ground for figure, so at a glance it is the one part of
   the page asking for something, which is exactly what it is.

   It works in both themes without a second rule, because --flip-bg and
   --flip-fg swap with the theme: black on white becomes white on black. The
   tokens are reassigned inside the block, so everything nested in it — the
   heading, the links, the hairlines between the gaps — comes out right without
   any of them knowing they are on an inverted ground.

   Under CADB_READ_ONLY the block loses its three links and gains a sentence, so
   it renders shorter in production than it does locally. Nothing here depends
   on its height. */
/* ── the one field of colour on the site, added 2026-08-17 ───────────────────
   `#C8102E` — theatre red: house curtain, Playbill, the colour of the subject.
   It sits well against the black-and-white and sepia scans that are most of
   what we hold, and it does not read as software.

   **The owner ruled on the contained version, and the contained version is
   what this is.** `landing-ui` §5 first proposed replacing the green
   site-wide — links, rules, rank figures — which would have collided with
   `--alert` (also red) and made a discrepancy notice look like a link, and
   would have needed `.flag`, `.warn-box`, the contribute forms and `.results`
   walked before it could land. That is off the table. `--link`, `--alert` and
   every `h2.rule` border stay green and untouched. One block changes.

   **One value in both themes, and the type does not flip.** `#C8102E` with
   `#FFFFFF` type is 5.88:1, which passes AA for body text. Against light paper
   the field boundary is 5.88:1 and against dark paper `#0E0E0E` it is 3.28:1 —
   a field edge rather than text, so no AA requirement applies and it is
   comfortably visible either way. It also disposes of `visual`'s worry that the
   near-white band read as a flashbang in dark.

   **Do not darken it.** `landing-ui` tested `#8E0F26` to put more distance
   between the band and the alert vermilion: 9.35:1 with white type, and it
   fails in dark at 2.06:1 against the page, where the band nearly disappears.

   **All type inside is white** and hierarchy comes from size and weight, not
   from a dimmed tint — the tints that pass on this ground are all thin. The
   five tokens the block reassigns therefore all point at white, and the
   hairlines at a translucent white rather than at a grey. The `--flip-*` tokens
   themselves are left alone; nothing else uses them and removing them is a
   separate edit. */
.join{
  --band-red:#C8102E;
  background:var(--band-red); color:#FFF;
  --ink:#FFF; --grey:#FFF; --mute:#FFF;
  --hair:rgba(255,255,255,.32); --link:#FFF;
  width:100vw; margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  margin-top:var(--band); margin-bottom:var(--band);
  padding:var(--half) var(--gutter) var(--band)}
.join .rule{margin-top:0; border-top-color:#FFF; border-top-width:3px;
  font-size:var(--d3); font-weight:700; letter-spacing:-.02em;
  padding-top:calc(var(--u)*1.5)}
.join .standfirst{max-width:52ch; color:#FFF}
.join .more a{text-decoration:underline; text-underline-offset:4px;
  text-decoration-thickness:1px; text-decoration-color:rgba(255,255,255,.55)}
.join .more a:hover{text-decoration-color:#FFF}

/* Three named gaps, set as a bill of work rather than as three matching cards.
   Each is a fact and its detail on one ruled line, the fact holding the left
   column, so the three read as jobs somebody could pick one from. 26ch holds
   the longest of the three facts to two lines. Also used twice on
   contribute/home.html, where the same shape holds. */
.gaps{list-style:none; padding:0; margin:var(--half) 0 0;
  display:block; border-top:1px solid var(--hair)}
.gaps li{display:grid; grid-template-columns:minmax(0,26ch) minmax(0,1fr);
  gap:var(--u) calc(var(--u)*4); align-items:baseline;
  padding:calc(var(--u)*2) 0; border-bottom:1px solid var(--hair);
  font-size:var(--t-small); line-height:1.5; color:var(--grey)}
.gaps b{display:block; color:var(--ink); font-weight:600; margin:0;
  font-size:var(--t-body); line-height:1.3}
@media (max-width:720px){
  .gaps li{grid-template-columns:1fr; gap:4px}
}


/* ── motion, and the switch that turns it off ────────────────────────────────
   Every transition on this site is var(--beat) — 160ms, one curve. Nothing
   moves further than 3px and nothing animates on load. For anyone whose system
   asks for less motion, all of it stops, including the smooth scrolling the
   reel would otherwise inherit. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    transition-duration:1ms !important;
    animation-duration:1ms !important;
    animation-iteration-count:1 !important;
    scroll-behavior:auto !important}
  .tile:hover{transform:none}
  .reel{scroll-snap-type:none}
}
