  /* ----------------------------------------------------------------- FONTS
     Self-hosted, not Google Fonts. The stylesheet link was the site's only
     third-party request and sat in the critical render path: a DNS lookup, a
     TLS handshake and a round trip to fonts.googleapis.com before any text
     could paint in the intended face.

     Browser HTTP caches have been partitioned per-site for years, so the old
     argument for Google Fonts - that a visitor probably already has the file
     from some other site - no longer holds. Self-hosting is now strictly
     faster, and it stops every visitor's browser announcing itself to Google.

     latin and latin-ext only; the site is English and the vietnamese, greek
     and cyrillic subsets Google also serves would never be fetched. Files are
     the same variable fonts Google serves, with Google's own descriptors and
     unicode-ranges preserved, so rendering is unchanged - Fraunces keeps its
     opsz axis, which font-optical-sizing: auto below depends on.

     Named without a content hash, so /fonts/* is cached for a year in
     _headers. Rename the file if one is ever replaced. */
  @font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('/fonts/fraunces-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('/fonts/fraunces-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('/fonts/inter-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('/fonts/inter-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

:root {
    --navy: #1b2a41;
    --navy-deep: #101a29;
    --charcoal: #2e2e2e;
    --gray: #5c6570;
    --gray-light: #8b93a0;
    --line: #d9dde3;
    --paper: #faf9f6;
    /* Foot of the fixed background. #e8e1d1 is the floor - below it --gray
       body copy drops under WCAG AA 4.5:1 once the ruling and the grain stack
       on top of it. Measured against RENDERED pixels, not the flat value - the
       flat value overstates it by about 0.25. */
    --paper-deep: #eee8dd;
    --gold: #a8823a;
    --white: #ffffff;

    /* Ruled paper. The ruling is warm grey-blue rather than the old grid's
       cooler blue, and the margin rule is the oxblood a ruled pad actually
       uses. Both are alpha values over paper, so the contrast figures below
       hold wherever they fall. */
    --rule-line: rgba(120, 134, 158, 0.16);
    --rule-margin: rgba(150, 68, 52, 0.20);

    /* The two structural widths. --wrap is the container; --measure is how
       wide running prose is ever allowed to get inside it. Keeping them
       separate is what gives the page a wide/narrow rhythm instead of one
       column at one width. */
    --wrap: 860px;
    --measure: 64ch;
    --rail: 132px;      /* the label column on Public Record */
    --rail-gap: 36px;

    /* Fraunces stands in for Recoleta — same warm, soft-terminal display serif,
       but freely licensed. Recoleta/Nord/Yoshida are commercial; if one gets
       bought, self-host it and swap the first name in these two stacks.
       System fallbacks stay so nothing reflows badly if Google Fonts is blocked. */
    --font-display: 'Fraunces', Georgia, "Times New Roman", Times, serif;
    --font-body: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  html { scroll-behavior: smooth; }
  body {
    font-family: var(--font-body);
    color: var(--charcoal);
    background: var(--paper);
    line-height: 1.65;
    font-size: 18px;
  }

  /* ------------------------------------------------------------------ WRAPPING
     Widows are handled once, here, for the whole site — not string by string.

     `pretty` tells the browser to look ahead over the last few lines of a block
     and pull a word down rather than strand one on its own line. `balance`
     evens out the lines of a short block instead, which is what a heading
     wants: no line dramatically shorter than its neighbours.

     Both degrade to normal wrapping where unsupported, so nothing depends on
     them. The hero keeps its explicit non-breaking space in build.py as well —
     it is the most visible line on the site and worth guaranteeing everywhere.

     Verified with tools/audit-layout.py across every page at 21 widths. */
  p, li, .lede, .post-excerpt, .pull-line {
    text-wrap: pretty;
  }
  h1, h2, h3, .hook, .section-title, .subhead, .post-list a {
    text-wrap: balance;
  }
  /* Public Record and The Grand Resign deliberately show no page title - both
     open straight into content, and DECISIONS.md records why. They still need
     an h1 for document structure and for search, so they carry one that is
     read aloud and indexed but never painted. Not display:none, which would
     take it out of the accessibility tree along with the layout. */
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* Public Record entries are citations - title, em dash, venue - and read as
     one unit rather than as running prose, so they balance rather than pretty.
     `pretty` still stranded the last word of a long citation at 320px. */
  .ref-list li {
    text-wrap: balance;
  }

  /* ---------------------------------------------------------------- BACKGROUND
     Fixed viewport overlay: content scrolls across it. Pure CSS, no images, no JS.

     ONE metaphor: ruled paper. This replaced three that were arguing with each
     other - film cue burns with a flicker animation, seven coffee-ring stains,
     and a blue engineering grid. Foolscap is a paper size, so paper is what the
     name already commits to.

     Two things this fixes beyond coherence. The stains and burns were switched
     off below 1200px, so the most elaborate code on the site was invisible to
     phone traffic; ruling and a margin rule work at every width. And the stains
     had to be kept out of the text column because --gray over one measured
     3.88:1, under the 4.5 floor - the ruling is light enough to sit anywhere.

     HORIZONTAL RULING ONLY, at 32px, which is the body line-height (18px x
     1.65 = 29.7px, rounded up so lines sit near but not exactly on the rules -
     locking them together makes the page look like a form).

     NOTE: this relies on z-index:-1 painting behind content but above body's
     background. Adding transform, filter, or opacity<1 to html/body would create
     a stacking context and make the whole layer vanish. */
  .bg {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;
    background-image:
      repeating-linear-gradient(0deg, var(--rule-line) 0 1px, transparent 1px 32px),
      /* A long, shallow warm-up down the page rather than the old hard
         horizon. Both tones are opaque, so this is what text sits on - see
         --paper-deep for the contrast floor. */
      linear-gradient(to bottom, var(--paper) 0 38%, var(--paper-deep) 100%);
  }

  /* The margin rule - the vertical line down a ruled pad. This is the one
     ownable device on the page and the reason the metaphor is worth having.

     Placed with max() rather than a media query: on a wide screen it sits 22px
     left of the text column (which starts at 50% - 398px, being a 860px wrap
     with 32px of padding); once the viewport is too narrow for that to be
     positive, it pins to 12px from the edge, which is where a real pad's margin
     is anyway. One line, no breakpoint to keep in sync. */
  .bg .rule-margin {
    position: absolute;
    top: 0;
    bottom: 0;
    left: max(12px, calc(50% - 420px));
    width: 1px;
    background: var(--rule-margin);
  }

  /* Paper tooth. 4% is deliberately near the threshold of visibility: enough to
     take the flatness off, little enough not to move any contrast figure. */
  .bg .grain {
    position: absolute;
    inset: 0;
    filter: url(#grain);
    opacity: 0.04;
    mix-blend-mode: multiply;
  }

  /* Decoration only - never worth ink, and fixed elements repeat per page. */
  @media print {
    .bg { display: none; }
  }

  /* Fraunces is variable on an optical-size axis (opsz 9..144) and the
     stylesheet already loads it. font-optical-sizing: auto lets the browser
     pick a cut per size, so 13px labels get sturdier forms and 50px display
     gets finer ones - the thing a real type family does, for no extra bytes.
     Deliberate overrides live on the individual rules below. */
  h1, h2, h3, .serif {
    font-family: var(--font-display);
    color: var(--navy-deep);
    font-weight: 400;
    line-height: 1.2;
    letter-spacing: -0.005em;
    font-optical-sizing: auto;
  }
  a { color: var(--navy); }

  /* LINKS - gold now does exactly ONE job on this site, and it is section
     labels. Every link used to carry a gold underline, which meant nothing was
     emphasised because everything was: the accent had no signal left. The rule
     is now the quiet --line, and gold appears on hover, where it is a response
     rather than decoration. */
  .lnk {
    color: var(--navy-deep);
    text-decoration: none;
    border-bottom: 1px solid var(--line);
    transition: border-color 120ms ease, color 120ms ease;
  }
  .lnk:hover { border-bottom-color: var(--gold); color: var(--navy); }
  .wrap {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 0 32px;
  }

  /* NAV - subtle, quiet, not sticky */
  nav {
    padding: 22px 0 0;
  }
  nav .wrap {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  nav .brand {
    font-family: var(--font-display);
    font-size: 15px;
    letter-spacing: 0.01em;
    color: var(--gray-light);
    text-decoration: none;
  }
  nav ul {
    list-style: none;
    display: flex;
    gap: 18px;
  }
  nav ul li a {
    text-decoration: none;
    color: var(--gray-light);
    font-size: 13px;
    letter-spacing: 0.02em;
  }
  nav ul li a:hover, nav ul li a.active { color: var(--gray); }

  /* HERO */
  header.hero {
    /* Home is nav + hero and nothing else, so the hero takes the remaining
       viewport and centres in it. dvh second so mobile browsers with a
       collapsing toolbar don't leave a scroll stub; vh stays as the fallback.
       No border-bottom — with the footer gone it was a stray rule across the
       bottom of the screen. */
    display: flex;
    align-items: center;
    /* NOT a full viewport any more. It owned the whole first screen, which is
       what made home a hero-and-stop: the proof below it was unreachable
       without scrolling blind. At 72vh the logo wall breaks the fold on a
       laptop, so the page reads hero -> proof. dvh second so mobile browsers
       with a collapsing toolbar don't leave a scroll stub. */
    min-height: calc(72vh - 132px);
    min-height: calc(72dvh - 132px);
    padding: 32px 0 56px;
  }
  .hero-inner {
    display: flex;
    align-items: center;
    gap: 56px;
    width: 100%;
  }
  .portrait {
    flex: 0 0 190px;
    width: 190px;
    height: 190px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    /* No border and no white backing. The portrait's edge is a baked alpha
       ramp, so a ring would sit around a dissolving edge, and a white disc
       would be what the edge faded into instead of the paper. */
  }
  .portrait img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 15%;
  }
  .hero-text .role {
    color: var(--gold);
    font-size: 13px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    margin-bottom: 16px;
  }
  /* The one genuinely large piece of type on the site. It was clamped at 36px,
     which is barely above a section heading - a landing page whose whole job is
     to be read by someone deciding whether to book him can afford one confident
     line. opsz is pushed to the display end of the axis so the forms tighten as
     they grow rather than just scaling up. */
  .hero-text h1.hook {
    color: var(--navy-deep);
    margin-top: 0;
    font-family: var(--font-display);
    font-size: clamp(32px, 4.6vw, 54px);
    font-variation-settings: 'opsz' 120;
    line-height: 1.16;
    letter-spacing: -0.018em;
    max-width: 20ch;
    margin-bottom: 20px;
  }
  /* Kept for nothing: the hero body is the Carson quote now. One rule, and the
     day a lede comes back it should not have to be reinvented. */
  .hero-text p.lede {
    color: var(--gray);
    font-size: 19px;
    max-width: 44ch;
  }
  /* The quote in the hero. Narrower measure than in the About column - it sits
     beside a portrait and under a 20ch hook, and a wide grey slab under that
     hook would fight it. The gold rule indents it off the hook's left edge,
     which is what separates the two voices before either is read. */
  .hero-text .quote {
    font-size: 20px;
    max-width: 40ch;
    margin: 0;
  }
  .hero-text .attrib { max-width: 48ch; }

  section { padding: 64px 0; border-bottom: 1px solid var(--line); }
  /* the proof block on home: no top border, it follows the hero directly */
  .proof { padding-top: 8px; border-bottom: none; }
  /* Closes the logo wall. Small and grey on purpose: the marks are the argument
     and this is the footnote to them, not a call to action competing with the
     evidence above it. */
  .kit-line {
    margin-top: 22px;
    font-size: 15px;
    color: var(--gray);
  }
  .proof .subhead { margin-top: 0; }

  /* ---------------------------------------------------------------- ONE SCREEN
     Home is meant to hold in a single screen: hook, hero quote and the fifteen
     outlet marks, no scrolling. His ask, for desktop primarily.

     It FITS rather than CLIPS. The page is a flex column exactly one viewport
     tall, the hero takes the slack (flex: 1) and everything else is sized to its
     content - so on a window that genuinely cannot hold it (a short laptop, 150%
     zoom, a phone in landscape) the content overflows and the page scrolls
     normally instead of hiding the bottom of itself. No overflow: hidden
     anywhere; that is the difference between fitting and truncating.

     min-height, not height, for the same reason.

     dvh first with vh as the fallback: on mobile browsers with a collapsing
     toolbar, vh is the taller value and would guarantee a scroll stub. */
  .one-screen {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }
  .one-screen nav { flex: 0 0 auto; }
  .one-screen header.hero {
    flex: 1 1 auto;
    /* it no longer claims a share of the viewport for itself - the flex column
       hands it whatever is left after the proof block has what it needs.
       The bottom padding is tight on purpose: with thirteen marks the grid runs
       to four rows, and 1366x768 - still a common laptop - had three pixels of
       overflow at 28px. */
    min-height: 0;
    padding: 20px 0 22px;
  }
  .one-screen .proof {
    flex: 0 0 auto;
    padding: 0 0 34px;
  }
  /* the outlet grid is the tallest fixed thing on the page, so it is where the
     vertical budget is won or lost - tighter rows here than on Public Record,
     where the block is one section among several and can breathe */
  .one-screen .outlets { gap: 18px 28px; }

  /* A SHORT window is a different problem from a narrow one. On something like
     1280x720 the width is fine but the height is not: four rows of marks plus a
     54px display line do not fit, and the thing that should give is the type,
     not the evidence. The hook steps down and the rows tighten, which buys back
     about 70px - enough for the proof to stay on screen. */
  @media (min-width: 641px) and (max-height: 780px) {
    .one-screen .hero-text h1.hook { font-size: clamp(30px, 3.2vw, 42px); }
    .one-screen .hero-text .quote { font-size: 18px; }
    .one-screen .hero-text .attrib { font-size: 13px; }
    .one-screen .portrait { flex-basis: 158px; width: 158px; height: 158px; }
    .one-screen .outlets { gap: 14px 28px; }
  }

  /* Below the point where a hook, a lede and four rows of logos cannot share a
     screen without one of them becoming silly, stop trying. */
  @media (max-height: 620px), (max-width: 640px) {
    .one-screen { min-height: 0; }
    .one-screen header.hero { flex: 0 0 auto; padding: 24px 0 40px; }
    .one-screen .outlets { gap: 26px 28px; }
  }
  section:last-of-type { border-bottom: none; }
  /* h1 on the page that opens on this heading, h2 on any that follows it.
     Both carry the class; the selector names both tags so promoting the first
     one to h1 for document structure cannot change how it looks. */
  h1.section-title,
  h2.section-title {
    font-size: clamp(34px, 3.6vw, 44px);
    font-variation-settings: 'opsz' 96;
    letter-spacing: -0.018em;
    margin-bottom: 28px;
  }
  /* Prose is held to --measure, narrower than the container, so the page has a
     wide/narrow rhythm instead of everything running to one edge. */
  section p { color: #3a3f45; max-width: var(--measure); }
  section p + p { margin-top: 16px; }

  /* QUOTE — the `>>` marker. The one line on the site in someone else's voice,
     and it has to look like it.

     Italic, quotation marks, em-dash byline: the three signals every reader
     already knows, and all three are now used. This ran roman and unquoted on
     the argument that the About pull-lines are italic display serif, so a
     quotation in that same italic would read as a fifth beat of his own
     argument. That was reasoned for About — and the quote does not live on
     About. It lives in the hero, where nothing else on the page is italic and
     the line directly above it is a roman display serif hook in his voice.
     Roman under roman read as a second tagline, not as another speaker.

     The marks hang: text-indent pulls the opening mark out into the gutter so
     the text stays optically flush with the gold rule instead of notching off
     it. Notching was the entire objection to setting them.

     Synthetic oblique, not a new face — the `>` pull-lines have been slanting
     Fraunces the same way since launch. A fifth font file for one line is not
     worth the request.

     The rule down the left is the page's own margin rule borrowed inward, in
     the gold already carrying subheads and link underlines — its one appearance
     inside the prose, which is what makes it read as a stop.

     Width is left to .about-flow, like every other paragraph there. */
  .quote {
    font-family: var(--font-display);
    font-style: italic;
    font-size: 20px;
    line-height: 1.45;
    color: var(--navy-deep);
    border-left: 2px solid var(--gold);
    padding-left: 18px;
    text-indent: -0.42em;
    margin: 30px 0 0;
  }
  .quote::before { content: "“"; }
  .quote::after  { content: "”"; }
  /* The byline belongs to the quote, so it hangs off the quote's left edge and
     not the prose's. .attrib's own -6px top margin was tuned to sit under a
     21px italic pull-line and is wrong under this. */
  .quote + .attrib {
    padding-left: 20px;
    margin-top: 8px;
  }
  /* ABOUT — photos float in the prose so text wraps two sides of each */
  .about-flow::after { content: ""; display: table; clear: both; }
  /* Narrower than .wrap, and the paragraphs run its full width rather than the
     global 68ch cap. Both matter: a left-anchored max-width would squeeze lines
     beside a LEFT float to ~39 characters while leaving a RIGHT float's lines
     long, so the two sections would not match. Letting the container set the
     measure makes both photos leave exactly the same text width beside them. */
  .about-flow { max-width: 800px; }
  .about-flow p { max-width: none; }   /* the container sets the measure */

  /* Photos come out of camera neutral-to-cool; the page is warm off-white
     (--paper #faf9f6). A light desaturation plus a touch of sepia settles them
     into the palette without reading as a filter. Tune the two together —
     sepia alone goes muddy, desaturation alone goes flat. Applied to both the
     hero portrait and the About photos so they match each other. */
  /* The edge fade and tone shift are baked into the *-soft.webp files by
     tools/soften-photos.py — not done in CSS. The CSS mask version was too weak
     to read and could only be tuned blind. Retune by editing FEATHER /
     SATURATION / WARMTH in that script and re-running; don't reintroduce a
     filter or mask-image here, the two would compound. */

  /* The edge is now a plain edge. It used to be a baked alpha ramp that
     dissolved the photo into the page; that made sense when three photos sat on
     a flat ground and none of them shared a background. Against ruled paper,
     with one photograph that has been properly colour corrected, the dissolve
     read as a smudge rather than a blend — and, as he put it about the site
     generally, clean over busy. No border and no corner radius either: the
     photograph is allowed to be a photograph. */
  /* Byline under a pull-line quote. Deliberately small and grey: the quote is
     the thing, the name is the credential, and the name must not compete with
     the sentence it is vouching for. */
  .attrib {
    font-size: 14px;
    color: var(--gray);
    margin-top: -6px;
    max-width: 46ch;
  }

  /* The press-kit headshot, shown on the page rather than linked. height:auto
     for the same reason as .float-photo below — the img carries width/height
     attributes, and an unmatched height attribute wins any axis CSS leaves
     alone. */
  .press-shot {
    display: block;
    width: 100%;
    max-width: 300px;
    height: auto;
    margin: 6px 0 14px;
  }
  .press-meta {
    font-size: 15px;
    margin-bottom: 8px;
  }

  /* height:auto is LOAD-BEARING, not tidying. The img carries width/height
     attributes so the browser can reserve the box before the file lands, and an
     unmatched `height` attribute is a presentational hint: with only `width`
     set here, the photo rendered 230x588 instead of 230x242 — squashed to a
     column. Any rule that sizes an image on ONE axis has to say `auto` on the
     other, or the attribute wins the axis CSS left alone.

     .outlets img already gets this right (height + width:auto) and .portrait img
     sets both, which is why the logo wall and the hero were unaffected.

     audit-layout.py does not catch this: it checks overflow, widows and line
     lengths, not whether a box kept its aspect ratio. */
  .float-photo {
    width: 230px;
    height: auto;
    display: block;
  }
  .float-photo.right { float: right; margin: 6px 0 22px 34px; }
  .float-photo.left  { float: left;  margin: 6px 34px 22px 0; }

  /* Below ~720px the column is too narrow to wrap around anything without
     shredding the line lengths — stop floating and stack.

     The selector must carry BOTH classes. A media query adds no specificity, so
     a plain `.float-photo` here (0,1,0) loses to `.float-photo.right` (0,2,0)
     above it no matter how far down the file it sits. That is exactly what went
     wrong: width dropped to the mobile value but float and margin did not, so
     the photo stayed floated with a 2-character column of text beside it and
     stranded a single word ("At") next to the testimony photo. */
  @media (max-width: 720px) {
    .float-photo.left,
    .float-photo.right {
      float: none;
      width: 100%;
      max-width: 230px;
      margin: 0 auto 26px;
    }
  }

  /* SUBHEAD */
  /* opsz 9 is the text end of the axis: sturdier stems and more open counters,
     which is what 13px letterspaced caps need to stay legible. */
  .subhead {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 9;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--gold);
    margin: 40px 0 16px;
  }

  /* DOCKET - the label rail on Public Record.

     The page is a strictly alternating run of subhead, content, subhead,
     content. Grid auto-placement fills row by row, so a two-column grid puts
     every label in the rail and its content beside it without the markup being
     restructured at all - build.py adds one class.

     This is where the site gets its asymmetry. Everything was previously
     centred blocks at one width; a label column with content set against it
     reads like a docket, which is what these citations are.

     Below 760px there is no room for a rail, so it collapses to the stacked
     arrangement it had before - which is also the arrangement the About and
     writing pages keep. */
  /* WIDER THAN THE REST, RIGHTWARD ONLY.

     The rail costs the content column 168px (132 rail + 36 gap), which left
     citations 628px to work in - and the testimony entries are long enough that
     "Congress in a Post-Chevron World — U.S. House Committee on House
     Administration" could not hold one line. It broke after "U.S. House",
     splitting the committee's name, which is what bind_units() had just stopped
     it doing one word earlier.

     So the docket gets its own width. The LEFT edge is pinned to where a normal
     860px wrap would put it - level with the nav brand, the hero and the margin
     rule, all of which are tuned to that column - and the extra width is taken
     on the RIGHT. Centring a wider box instead would drag the left edge outward
     and break alignment with everything else on the page.

     max() so that below the point where the calc goes negative it simply pins
     to the viewport, where .wrap's own 32px padding takes over. */
  .docket {
    max-width: 1000px;
    margin-left: max(0px, calc(50% - 430px));
    margin-right: auto;
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: var(--rail-gap);
    row-gap: 42px;
    /* BASELINE, not start. A hand-tuned padding on the label only lines up
       against one size of content: it matched the 16.5px citations on Public
       Record and left the label a few px high against the 22px display serif
       of a post title. Baseline alignment lines up the first line of the label
       with the first line of whatever it labels, at any size, with nothing to
       re-tune when the type changes. */
    align-items: baseline;
  }
  /* Auto-placement alone was enough while every page was a strict
     subhead/content alternation. The writing page is not - it opens with a
     full-width title and intro - so the columns are assigned explicitly.
     Items still flow in document order; these rules only say which column. */
  .docket > .section-title,
  .docket > .lede-note { grid-column: 1 / -1; }
  .docket > .subhead {
    grid-column: 1;
    margin: 0;
    text-align: right;
    line-height: 1.5;
  }
  .docket > .ref-list,
  .docket > .outlets,
  .docket > .post-list { grid-column: 2; margin-top: 0; }
  /* images have no baseline worth aligning to - the logo wall stays top-aligned
     with its label */
  .docket > .outlets,
  .docket > .outlets + .subhead { align-self: start; }
  .docket > .lede-note { max-width: var(--measure); margin-bottom: 4px; }
  /* the rail's label should sit on the same optical line as the first item of
     its content, not above it */
  /* .post-list li carries 20px of top padding and .ref-list li does not, which
     put the first post a whole line below its label. Drop it on the first item
     only - the gap between posts still wants it. */
  .docket > .post-list > li:first-child { padding-top: 0; }

  @media (max-width: 760px) {
    .docket {
      display: block;
      margin-left: auto;
    }
    .docket > .lede-note { margin-bottom: 0; }
    .docket > .subhead {
      text-align: left;
      margin: 40px 0 16px;
    }
    .docket > *:first-child { margin-top: 0; }
  }

  /* REFERENCE LIST */
  .ref-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .ref-list li {
    font-size: 16.5px;
    color: #4a4f55;
    border-bottom: 1px solid var(--line);
    padding-bottom: 14px;
    font-variant-numeric: oldstyle-num;
  }
  .ref-list li:last-child { border-bottom: none; }
  /* A caption for the list under it, not a sentence above it. .ref-list has no
     top margin, so a plain paragraph in this slot collided with the first
     citation; quieter and smaller than .subhead because it divides a section
     that already has one, and it must not compete with it. */
  .list-label {
    font-family: var(--font-display);
    font-variation-settings: 'opsz' 9;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--gray);
    margin: 0 0 12px;
  }
  /* Between two labelled lists the gap has to reappear, and the list above ends
     on a borderless last item, so there is nothing to separate them. */
  .ref-list + .list-label,
  .tape-list + .list-label { margin-top: 32px; }
  /* SUBJECTS - a list of topics, not of citations. No rules, no per-item
     padding: it should read as one block the eye takes in at once, the way an
     index entry does, rather than as seven separate records. */
  .subject-list { gap: 5px; }
  .subject-list li { border-bottom: none; padding-bottom: 0; }
  .ref-list a { border-bottom-color: var(--line); }
  /* The venue sits on its own line under the title. See citation() in build.py
     for why: run inline, the break lands inside the venue and strands a word
     from the institution it names. */
  .ref-list .venue,
  .tape .venue {
    display: block;
    margin-top: 3px;
    color: var(--gray);
  }

  /* TAPE - clips cut from hearing feeds, served as native <video>. See tape()
     in build.py for why there is no third-party player. Each figure is a
     citation with the footage above it: the caption is set exactly as a
     .ref-list entry, so the clips and the list that follows read as one
     column of records, some of which happen to play.

     The video is a plain box, no frame, no shadow, no rounded corners: the
     page draws nothing else that way and a player styled like a product card
     would be the one object on the site that came from somewhere else.

     Full column width was the first version and he did not love it: three
     stacked 16:9 boxes at 736px owned the page. On a wide screen each clip is
     now a row, the video at 340px on the left and the caption beside it, so
     the three sit in the height one of them used to take and read as list
     entries with tape attached. Fullscreen is one tap away for anyone who
     wants it big. On a phone the row stacks and the video takes the column,
     which is small already. */
  .tape-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
  }
  .tape {
    margin: 0;
    font-size: 16.5px;
    color: #4a4f55;
    font-variant-numeric: oldstyle-num;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .tape video {
    display: block;
    width: 100%;
    height: auto;
    background: var(--navy-deep);
  }
  /* A podcast excerpt: the browser's audio bar sits where the picture would.
     Nothing drawn around it, for the same reason the video has no frame. */
  .tape audio {
    display: block;
    width: 100%;
  }
  .tape figcaption { text-wrap: balance; }
  .tape a { border-bottom-color: var(--line); }
  @media (min-width: 721px) {
    .tape {
      flex-direction: row;
      align-items: center;
      gap: 22px;
      border-bottom: 1px solid var(--line);
      padding-bottom: 20px;
    }
    .tape video,
    .tape audio { flex: 0 0 340px; width: 340px; }
    .tape figcaption { flex: 1; }
  }
  /* The list that continues the group. .ref-list has no top margin and the
     figure has no bottom rule on a phone, so without this the first entry sat
     on the last caption. */
  .tape-list + .ref-list { margin-top: 20px; }

  /* Home's proof label is the only subhead on that page, and it labels a wall
     of logos rather than a list of citations. Public Record's three "Select ..."
     headings keep the display serif; this one takes the body sans, smaller and
     more widely tracked, so the two pages do not read as the same list with
     different contents. Gold, uppercase and the 0.1em-family tracking stay -
     it is a different voice in the same system, not a different system. */
  .proof .subhead {
    font-family: var(--font-body);
    font-variation-settings: normal;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.16em;
  }

  /* MEDIA STRIP - the outlet logos under "Media appearances".

     A GRID, not a flex row. Eight wordmarks do not fit one line at any width
     this site is read at, and a flex row that wraps leaves the second line
     starting wherever the first one happened to run out - which reads as a
     mistake. auto-fill columns wrap into a block with even gaps instead.

     The tracks are a FIXED 120px, not minmax(120px, 1fr). 1fr stretched them to
     fill the 796px text column, which left every track carrying dead space that
     accumulated left to right, so the last column sat visibly further right than
     its own spacing justified. A fixed track is exactly the width of the widest
     logo, so the block is as tight as its contents and the columns are evenly
     pitched all the way across. justify-content: start then packs the whole
     thing against the left margin instead of spreading the slack.

     120px is measured, not picked for looking round: it is the width of the
     widest logo at its tuned scale - WAMU at 118.3px, with WSJ, The Federalist,
     Bloomberg, The Hill and Roll Call all within 2px of it - and it sets the
     column count. 6 columns (6x120 + 5x28 = 860) do not fit the 796px text
     column and 5 do, so the count tops out at 5 and steps down to 4, 3, 2 and 1
     as the viewport narrows - which is what build.py's OUTLETS order is spaced
     against, so the two Federalist logos never land adjacent.

     justify-items: start keeps each logo on its track's left edge, so the first
     column is level with the body copy and the section headings.

     No border, no plate, no card. The subhead above already separates the
     section, exactly as it does for the two citation lists. */
  .outlets {
    display: grid;
    grid-template-columns: repeat(auto-fill, 120px);
    align-items: center;
    justify-items: start;
    justify-content: start;
    gap: 26px 28px;
    margin-top: 6px;
  }

  /* Sized by HEIGHT with width:auto, so each SVG keeps its own aspect ratio.
     max-width is a guard only: the tracks are a fixed 120px and every --k below
     is set so no logo exceeds that, so nothing should ever reach it.

     The files themselves are single-ink SVGs cropped to their artwork, so the
     height set here is the height of the ink - no padding inside one logo
     silently making it smaller than its neighbour. */
  .outlets img {
    display: block;
    height: calc(22px * var(--k, 1));
    width: auto;
    max-width: 100%;
  }

  /* Per-logo optical scale. These are NOT derived from a formula - a single
     height across aspect ratios from 1.9:1 to 7.1:1 makes POLITICO enormous and
     Fox Business a stamp. Each was tuned by eye against a rendered screenshot
     so no one logo dominates the block and all eight stay readable.

     What the numbers are answering:
       Bloomberg, POLITICO   long, and Bloomberg is much the heavier weight
       Roll Call, THE HILL   long; The Hill is the widest ratio here at 7.1:1
       CNBC                  nearly square and very bold, so it goes down
       PBS                   P-head over a short wordmark, so it runs deep
       Fox Business, NPR     solid bars with the type reversed out of them -
                             the heaviest mass on the page, so the smallest
       RealClearPolitics     half solid plate, half plain text
       WAMU                  8.4:1, and the widest ratio here until WSJ
       Wall Street Journal   the widest mark on the wall at 11.8:1
       American Prospect     condensed all-caps, near Bloomberg's ratio and weight
       Psychedelics Today    spore mark over a two-line wordmark
       The Federalist        eagle over a condensed wordmark
       S&P Global            a wordmark under a full-width bar, so it carries
                             more mass than its ratio alone suggests */
  .outlets .o-wsj         { --k: 0.45; }
  .outlets .o-bloomberg   { --k: 0.98; }
  .outlets .o-cnbc        { --k: 1.00; }
  .outlets .o-pbs         { --k: 1.42; }
  .outlets .o-spglobal    { --k: 1.14; }
  .outlets .o-federalist  { --k: 1.27; }
  .outlets .o-politico    { --k: 0.96; }
  .outlets .o-thehill     { --k: 0.75; }
  .outlets .o-npr         { --k: 1.15; }
  .outlets .o-foxbusiness { --k: 1.50; }
  .outlets .o-rollcall    { --k: 0.92; }
  .outlets .o-rcp         { --k: 1.22; }
  .outlets .o-wamu        { --k: 0.64; }
  .outlets .o-psychedelicstoday { --k: 1.30; }
  .outlets .o-americanprospect  { --k: 0.98; }

  /* FOOTER - a terminal rule and the address. Pages used to simply stop. */
  .site-foot {
    margin-top: 96px;
    padding: 26px 0 40px;
    border-top: 1px solid var(--line);
    font-size: 14px;
    letter-spacing: 0.01em;
    /* The "· Media:" connective is plain text; without this it renders in
       full-strength ink and reads darker than the two links beside it. */
    color: var(--gray);
  }
  .site-foot .lnk { color: var(--gray); }

  /* CONTACT */
  footer.contact {
    padding: 64px 0 48px;
    text-align: center;
  }
  footer.contact h2 { font-size: 32px; margin-bottom: 14px; }
  footer.contact p { margin: 0 auto 24px; color: var(--gray); }
  /* THE ONE DARK SURFACE. The palette had no dark ground anywhere, so the
     contact button was a lone dark object floating in white space and did not
     read as the end of anything. Giving the contact block itself the dark
     ground anchors the page and lets the button become light-on-dark, which is
     what makes it look like the action rather than a decoration.

     One surface, on one page. If a second appears, they stop being an anchor
     and become a pattern. */
  /* Contact is one page, one block. With the footer gone (see NO_FOOTER) the
     panel is the whole page, so it holds the screen rather than sitting as a
     band with a few hundred pixels of empty paper under it — which is what it
     looked like, and read as an unfinished page.

     min-height rather than a fixed height so it can still grow with its
     content, and vh-capped so a short laptop window never has to scroll to
     reach a button that is the point of the page. */
  #contact.panel {
    background: var(--navy-deep);
    border-bottom: none;
    padding: 76px 0 82px;
    margin-top: 24px;
    /* svh, not vh. On mobile, 100vh is the viewport with the URL bar HIDDEN, so
       the value changes as the bar collapses on scroll — the box resizes
       mid-visit and every layout shift it causes is counted as CLS. 100svh is
       the small viewport height and never moves. The vh line stays first as the
       fallback for anything that does not know svh; the svh line wins where it
       is supported. */
    min-height: min(600px, calc(100vh - 120px));
    min-height: min(600px, calc(100svh - 120px));
    display: flex;
    align-items: center;
  }
  #contact.panel > .wrap { width: 100%; }
  #contact.panel h1,
  #contact.panel h2 { color: var(--paper); }

  /* The press kit is the panel's SECONDARY action, sitting under the button:
     primary is "email him", secondary is "take the bios and the headshot".

     It was briefly its own section under the panel and read as an orphan - a
     tiny link floating in white space, and visibly off-centre because a global
     max-width caps a paragraph at --measure and anchors that box left, so
     `text-align: center` centres the text inside 606px rather than on the page.
     `margin: auto` is what the panel's own lede already uses to fix that, and
     inside the panel the link has a block to belong to. */
  #contact.panel .panel-alt {
    margin: 24px auto 0;
    font-size: 14px;
  }
  /* .lnk is navy on a --line rule, which is invisible on the one dark surface. */
  #contact.panel .lnk {
    color: var(--paper);
    border-bottom-color: rgba(250, 249, 246, 0.32);
  }
  #contact.panel .lnk:hover {
    color: var(--white);
    border-bottom-color: var(--gold);
  }
  #contact.panel p { color: #a9b3c2; }
  .btn {
    display: inline-block;
    background: var(--paper);
    color: var(--navy-deep) !important;
    text-decoration: none;
    padding: 13px 30px;
    border-radius: 3px;
    font-size: 14px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    transition: background 120ms ease;
  }
  .btn:hover { background: var(--white); }

  @media (max-width: 640px) {
    .hero-inner { flex-direction: column; align-items: center; }
    .hero-text { text-align: left; }
    .hero-text p.lede { max-width: 100%; }

    /* Three of the five nav labels are multi-word phrases. Kept on one row at
       this width they break mid-phrase — "Public / Record", "The / Grand /
       Resign" — and the fragments land on different baselines, which is what
       reads as scattered. So stack: the name gets its own line, the four links
       get a second line they are never allowed to wrap on.

       The gap between links is a real gap, not leftover space. An earlier
       version used space-between with gap:0, which looks right only while the
       row has slack — on a phone with text scaling turned up the slack goes to
       zero and the four labels butt together into one long sentence.

       So: a fixed gap, centred, and the LIST may wrap even though a label may
       not. If the four don't fit, they break across two lines as whole phrases,
       which stays legible at any text size. Nothing here depends on the labels
       happening to fit. */
    .wrap { padding: 0 20px; }
    nav .wrap {
      flex-direction: column;
      align-items: center;
      gap: 14px;
    }
    nav ul {
      flex-wrap: wrap;
      justify-content: center;
      gap: 10px 22px;
    }
    nav ul li a {
      white-space: nowrap;
      font-size: 12.5px;
    }
  }

  /* THE GRAND RESIGN — post feed */
  .post-list { list-style: none; margin-top: 8px; }
  .post-list li {
    padding: 20px 0;
    border-bottom: 1px solid var(--line);
  }
  .post-list li:last-child { border-bottom: none; }
  .post-list a {
    font-family: var(--font-display);
    font-size: 22px;
    color: var(--navy-deep);
    text-decoration: none;
    border-bottom: 1px solid var(--line);
  }
  .post-list a:hover { color: var(--navy); border-bottom-color: var(--gold); }

  .post-meta {
    font-size: 13px;
    color: var(--gray-light);
    margin-top: 6px;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
  }
  /* the docket is wider than the text column now, so this needs its own cap or
     the excerpts run past a comfortable measure */
  .post-excerpt {
    max-width: var(--measure);
    font-size: 16.5px;
    color: var(--gray);
    margin-top: 8px;
    line-height: 1.6;
  }
