  /* ══════════════════════════════════════════════════════════════════
     bun & bolt — the index.

     This page is the only one on the site that is not about anything. Its
     whole job is to introduce the two of us and let you leave for one of
     the other pages, so it is a shop window: five pictures, five names,
     and no paragraph explaining a thing you find out by clicking it.

     It carries one script — the rabbit and the horse walking the card
     frames — and that script is allowed to fail. It appends two <div>s,
     moves them, and touches nothing else; with JavaScript off, or reduced
     motion on, this is a static page of pictures and links exactly as it
     was before. Everything else here is still a subtraction.

     The house style comes from the first product and is not re-invented
     here: white paper, one dark green ink, Hanken Grotesk, and hand-drawn
     ink for the few things that are allowed to be warm. Colour arrives
     only inside the thumbnails, the way it arrives only inside the
     illustrations over there.
     ══════════════════════════════════════════════════════════════════ */

  /* Hanken Grotesk — the site's grotesque, same cut as the football page.
     Its subset is the full Latin-1 block, which covers en/es/pt outright
     and every Latin word inside the zh page, so this file is copied
     across unchanged rather than re-cut. */
  @font-face {
    font-family: "Hanken Grotesk"; font-style: normal; font-weight: 100 900; font-display: swap;
    src: url("/home/web/hanken_latin.woff2?v=e9201eddf1d4") 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+2212, U+2215, U+FEFF, U+FFFD;
  }
  /* The wordmark used to be set: Hanken at 800 with one hand-drawn ampersand
     borrowed from Caveat, cut to that single character. It is now drawn — a
     real wordmark, with a rabbit for the B of bun and a lightning bolt for
     the l of bolt — so both the pen font and the 1.3 KB subset it needed are
     gone. See the sprite at the top of the body. */


  :root {
    color-scheme: light;

    --ink:    #12271E;   /* 15.4:1 on white */
    --ink-2:  #3D5148;   /* body prose */
    --ink-3:  #5C6F66;   /* labels, dates, the quiet register */
    --paper:  #FFFFFF;
    --rule:   #E3E8E5;   /* hairlines between entries */
    --pitch:  #2C6320;   /* the house green; here it is only ever a verb */
    /* The wordmark's own green, as drawn. It is NOT --ink: they are two
       different dark greens four steps apart, which is close enough that a
       reader will never see it as a decision and just slightly off. Kept as
       delivered rather than quietly snapped to --ink, because the mark is
       the one thing on this page whose colour is not this stylesheet's call.
       One place to change it if it should match. */
    --brand:  #12392B;

    /* One stack for every language. Hanken is first and its range is Latin
       only, so 汉字 fall straight through to Noto and Latin never reaches
       Noto at all — which matters, because Noto's own range here is CJK
       only, so a stack that led with it would hand the wordmark, the dates
       and the language switcher to whatever system face came after. That is
       what a zh-first stack did in the first draft: "bun & bolt" set in
       PingFang on one of four pages, which is not a logotype. */
    --font-body: "Hanken Grotesk", "Noto Sans SC", "PingFang SC", "Hiragino Sans GB",
                 "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Segoe UI",
                 Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, "Noto Sans SC", monospace;

    /* Wider than a reading column, because the content is now pictures. At
       660 — the width this page had when every entry was a paragraph — two
       staggered thumbnails come out barely 300px each, and a shop window
       whose goods are that small is a list again with extra steps. */
    --col: 940px;
    --gut: 22px;
  }

  * { box-sizing: border-box; }

  /* clip, NOT hidden. Bare `overflow-x: hidden` computes overflow-y to
     `auto` and quietly turns html and body into scroll containers — the
     bug that made the football page refuse to scroll for a week. Nothing
     on this page pokes out of its box today, but the rule is the site's,
     not the page's. `hidden` stays as the pre-2022-engine fallback. */
  html { overflow-x: hidden; overflow-x: clip; }

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

  /* 汉字 want more leading than Latin at the same size. The family is NOT
     switched here — see the stack above. text-autospace is the engine-level
     fix for the CJK/Latin gap, which is why no space is typed by hand
     anywhere in the zh copy; engines without it yet lose nothing. */
  :root[data-lang="zh"] body { line-height: 1.75; text-autospace: normal; }

  .wrap { max-width: var(--col); margin: 0 auto; padding: 0 var(--gut); }

  /* ── streaming curtain ─────────────────────────────────────────────
     The head (site-level) locks scroll and holds this cover until the DOM
     is complete. Its colours must match meta.json's splash_bg/splash_fg,
     which paint before this stylesheet exists. */
  #splash { gap: 10px; }
  #splash .sp-mark { display: block; width: 150px; height: auto; color: var(--brand); }
  #splash .sp-txt { color: var(--ink-3); font-weight: 500; }

  /* ── masthead ──────────────────────────────────────────────────────
     Wordmark and language switcher on one line, then a hairline. The
     wordmark is set, not drawn: it has to hold at 15px in a browser tab's
     worth of space and in four languages, and a logotype that survives
     that is a logotype made of type. */
  .mast {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
    padding: clamp(24px, 5vw, 44px) 0 14px;
    border-bottom: 1px solid var(--rule);
  }
  /* Centred once there is room for it, which is where the sketch puts it —
     a title over a scene rather than a label in a corner. Narrow, it goes
     back to the corner: a centred wordmark with the four language chips
     under it costs a whole line of a phone screen to say nothing new. */
  @media (min-width: 620px) {
    .mast { display: grid; grid-template-columns: 1fr auto 1fr; }
    .mast .mark { grid-column: 2; }
    .mast #lang { grid-column: 3; justify-self: end; }
  }
  /* The wordmark is drawn now, not set — so the heading carries the picture
     and the words go into a visually hidden span beside it. Both, not either:
     an aria-label would name the heading for a screen reader while leaving a
     crawler to read an <svg>, and alt text on an <img> can't be styled or
     coloured. This way the h1 genuinely contains the string "bun & bolt".
     Width in px rather than em because it is a logo, not type: it should not
     resize when the body font-size does. The clamp is the range the mark was
     drawn for — about 200 on a desktop header, about 160 on a phone. */
  .mark { margin: 0; line-height: 0; }
  .mark .wordmark {
    display: block; width: clamp(160px, 24vw, 200px); height: auto;
    color: var(--brand);
  }
  /* Off-screen but not display:none — the text has to stay in the accessibility
     tree and in the document a crawler reads. clip-path over the old clip rect,
     and nowrap so a long string can't be reflowed into a 1px column and read
     one letter per line. */
  .vh {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }

  /* ── language ──────────────────────────────────────────────────────
     One mark that opens, rather than four codes standing there all the
     time. This page has one job — get you into one of five pieces — and
     four permanent chips at the top were four more things competing with
     those five, over a decision most readers make once, or never.

     <details>, so it still needs no script. A disclosure is the one piece
     of interaction HTML has been able to do by itself for years, and the
     page's rule is that it carries nothing it can do without. The cost is
     that it will not close when you click elsewhere — that part does want
     JavaScript — and clicking the globe a second time is not a hardship.

     The mark is drawn, not typed. The obvious alternative is 文A, the way
     Wikipedia writes it, and it is wrong HERE: no CJK subset is loaded on
     the en/es/pt pages, so that 文 would be set in whatever face the
     reader's system happens to offer. The menu below accepts exactly that
     trade for 中文, because a reader looking for their own language needs
     to see their own language; a piece of the site's own furniture does
     not get the same excuse, so it is drawn in the site's own line. */
  #lang { position: relative; align-self: center; }

  .lang-btn {
    display: block; padding: 7px; margin-right: -7px;
    cursor: pointer; color: var(--ink-3);
    border-radius: 5px;
    list-style: none;                              /* the disclosure triangle… */
  }
  .lang-btn::-webkit-details-marker { display: none; }   /* …and again, for older WebKit */
  .lang-btn svg { display: block; width: 20px; height: 20px; }
  .lang-btn:hover, #lang[open] > .lang-btn { color: var(--ink); background: #F2F5F3; }
  .lang-btn:focus-visible { outline: 2px solid var(--pitch); outline-offset: 2px; }

  /* Absolute, so opening it moves nothing: the masthead is a fixed line and
     a menu that pushed the page down would move the first thumbnail out from
     under whoever is reading it. z-index because the drawing below opens a
     painting layer of its own further down the document. */
  .lang-menu {
    position: absolute; z-index: 20;
    top: calc(100% + 8px); right: 0;
    display: grid; gap: 2px; padding: 6px;
    background: var(--paper); border: 2px solid var(--ink);
  }
  /* Say the quiet part. A closed <details> already hides this — but it does it
     by containment on a pseudo-element the browser owns, and an absolutely
     positioned child is exactly the case where engines have historically
     disagreed about whether the containment still reaches it. Measured in
     Chrome it is hidden and unfocusable while still reporting a full 96×148
     box, which is the shape of a thing being hidden by a mechanism I cannot
     see. One line makes it a rule this stylesheet states rather than one it
     inherits. */
  #lang:not([open]) .lang-menu { display: none; }
  .lang-menu a {
    font-size: .82rem; font-weight: 600;
    color: var(--ink-3); text-decoration: none; white-space: nowrap;
    padding: 5px 10px; border-radius: 4px;
  }
  .lang-menu a:hover { color: var(--ink); background: #F2F5F3; }
  .lang-menu a[aria-current="page"] { color: var(--paper); background: var(--ink); }
  /* 汉字 carry far more strokes per em than Latin and turn to mud at the same
     size, so they need a floor: bigger.
     The floor follows the SCRIPT OF THE LABEL, not the language of the page.
     This menu lists 中文 on all four pages, so it needs the floor on all four;
     keying it to :root[data-lang="zh"] left the row unreadable everywhere
     else, which is where a reader who cannot read this page is most likely to
     be looking for it. */
  .lang-menu a[data-set="zh"] { font-size: .88rem; }

  /* ── the two of us, working ──────────────────────────────────────────
     Still one drawing rather than a sentence in four languages — see the
     history above — but now a loop instead of a photograph: the horse
     has three thoughts in turn, and each one turns into a small page
     stacked up beside the rabbit's laptop. That loop is the only account
     this page gives of itself, which is the whole point of it — the two
     paragraphs that used to say the same thing in words are gone.
     No script. Second pass of this asset: the first cut hid a doodle
     already baked into each bubble and revealed it on cue, but left all
     three bubble OUTLINES sitting there permanently, which read as three
     clouds with changing weather rather than three thoughts. This cut
     erases the bubbles too — one mask big enough for all three, plus the
     string of trailing dots each used to lead down to the horse's head —
     so the sky is empty between thoughts, and what fades in each time is
     a complete cloud: outline, trail and doodle together, drawn once as a
     self-contained group per thought. The typing hand and the eject marks
     by the monitor are the same erase-then-redraw trick without a
     permanent original to hide first.
     Every coordinate below — the masks, the hand, the three product
     boxes — was placed by eye against the FULL, uncropped 1672×941
     canvas, so the crop for display is done by the wrapper below rather
     than by trimming the art itself: trim the pixels and every mark on
     top drifts off whatever it was covering or pointing at.
     `--art-ink` is a local colour, not the site's `--ink`: the art itself
     is drawn in a plain near-black, and matching that is more important
     here than matching the site's ink, which is a dark green a few steps
     away from it. `stroke-width` is left to scale with the artwork
     (unlike the delivered file, which pinned it constant) so the overlay
     doesn't go bolder than the linework under it at the smaller sizes
     this cap actually renders at. */
  .intro { padding: clamp(22px, 4.5vw, 40px) 0 clamp(20px, 4vw, 34px); }

  .intro-scene {
    position: relative; overflow: hidden;
    width: 100%; max-width: 700px; margin: 0 auto;
    aspect-ratio: 1384 / 834;
    --art-ink: #111;
    --loop: 6s;
  }
  /* The full 1672×941 canvas, positioned so that the 1384×834 window
     starting at (185, 44) — the crop — is what the overflow:hidden
     parent actually shows. Percentages here resolve against the PARENT's
     own box (width-like properties against its width, height-like ones
     against its height), which is what keeps this proportional at any
     size without a pixel of arithmetic done at runtime. */
  .intro-scene__frame {
    position: absolute;
    width: calc(100% * 1672 / 1384); height: calc(100% * 941 / 834);
    left: calc(100% * -185 / 1384); top: calc(100% * -44 / 834);
  }
  .intro-scene__base, .intro-scene__motion {
    position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  }
  .intro-scene__motion { pointer-events: none; }

  .intro-scene .ink {
    fill: none; stroke: var(--art-ink); stroke-width: 3;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .intro-scene .thought-mask { fill: var(--paper); stroke: none; }
  /* The two name labels. Brand green, not the near-black the drawing is in,
     so they read as the site labelling its own picture rather than as two
     more things someone drew into the scene. Deliberately NOT .ink — that
     class would override the wordmark's own 9-unit stroke with the overlay's
     3 and thin the letters to wire. */
  .intro-scene .scene-mark { color: var(--brand); }

  .intro-scene .thought { opacity: 0; transform-box: fill-box; transform-origin: center; }
  .intro-scene .thought--bolt    { animation: iscene-bolt    var(--loop) ease-in-out infinite; }
  .intro-scene .thought--network { animation: iscene-network var(--loop) ease-in-out infinite; }
  .intro-scene .thought--maze    { animation: iscene-maze    var(--loop) ease-in-out infinite; }
  .intro-scene .bolt-fill { fill: var(--art-ink); }

  .intro-scene .typing-hand { transform-box: fill-box; transform-origin: center; }
  .intro-scene .typing-hand--left  { animation: iscene-tap-l .08s linear infinite alternate; }
  .intro-scene .typing-hand--right { animation: iscene-tap-r .08s linear infinite alternate; }
  .intro-scene .tap-mark { opacity: .15; animation: iscene-tap-mark .16s steps(2, end) infinite; }

  .intro-scene .product { opacity: 0; transform-box: fill-box; transform-origin: center; }
  .intro-scene .product--one   { animation: iscene-product-1 var(--loop) cubic-bezier(.2,.8,.3,1) infinite; }
  .intro-scene .product--two   { animation: iscene-product-2 var(--loop) cubic-bezier(.2,.8,.3,1) infinite; }
  .intro-scene .product--three { animation: iscene-product-3 var(--loop) cubic-bezier(.2,.8,.3,1) infinite; }
  .intro-scene .eject-mark { opacity: 0; animation: iscene-eject var(--loop) ease-out infinite; }

  @keyframes iscene-bolt {
    0%, 2% { opacity: 0; transform: translateY(7px) scale(.82); }
    6%, 25% { opacity: 1; transform: translateY(0) scale(1); }
    30%, 100% { opacity: 0; transform: translateY(-4px) scale(.94); }
  }
  @keyframes iscene-network {
    0%, 29% { opacity: 0; transform: translateY(7px) scale(.82); }
    34%, 53% { opacity: 1; transform: translateY(0) scale(1); }
    58%, 100% { opacity: 0; transform: translateY(-4px) scale(.94); }
  }
  @keyframes iscene-maze {
    0%, 57% { opacity: 0; transform: translateY(7px) scale(.82); }
    62%, 84% { opacity: 1; transform: translateY(0) scale(1); }
    91%, 100% { opacity: 0; transform: translateY(-4px) scale(.94); }
  }
  @keyframes iscene-tap-l { from { transform: translateY(-2px) rotate(-1deg); } to { transform: translateY(4px) rotate(1deg); } }
  @keyframes iscene-tap-r { from { transform: translateY(4px) rotate(1deg); } to { transform: translateY(-2px) rotate(-1deg); } }
  @keyframes iscene-tap-mark { 0%, 49% { opacity: .12; } 50%, 100% { opacity: .72; } }
  @keyframes iscene-product-1 {
    0%, 14% { opacity: 0; transform: translate(74px, -22px) rotate(7deg) scale(.65); }
    20%, 88% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); }
    94%, 100% { opacity: 0; transform: translate(0, 10px) scale(.96); }
  }
  @keyframes iscene-product-2 {
    0%, 41% { opacity: 0; transform: translate(68px, 19px) rotate(-7deg) scale(.65); }
    47%, 88% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); }
    94%, 100% { opacity: 0; transform: translate(0, 10px) scale(.96); }
  }
  @keyframes iscene-product-3 {
    0%, 68% { opacity: 0; transform: translate(63px, 58px) rotate(7deg) scale(.65); }
    74%, 88% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); }
    94%, 100% { opacity: 0; transform: translate(0, 10px) scale(.96); }
  }
  @keyframes iscene-eject {
    0%, 13%, 21%, 40%, 48%, 67%, 75%, 100% { opacity: 0; }
    15%, 18%, 42%, 45%, 69%, 72% { opacity: .85; }
  }

  /* The resting frame: the last thought the horse had, and all three pages
     it led to, held rather than looping — the same "settled" reading a
     visitor with JavaScript off would get if the base art still carried its
     own baked-in doodles (it no longer needs to: this cut erases them
     unconditionally, motion or not, and this is the one thought the CSS
     itself chooses to leave standing). */
  @media (prefers-reduced-motion: reduce) {
    .intro-scene .thought, .intro-scene .typing-hand, .intro-scene .tap-mark,
    .intro-scene .product, .intro-scene .eject-mark { animation: none; }
    .intro-scene .thought--maze, .intro-scene .product { opacity: 1; }
  }

  /* ── the index ─────────────────────────────────────────────────────
     Numbered entries under a heading that names the verb, not the medium.
     "Things you can play with" scales to a second heading when something
     arrives that you read instead — which is the only kind of growth this
     list has to survive. */
  .eyebrow {
    font-family: var(--font-mono);
    font-size: .7rem; font-weight: 600; letter-spacing: .17em; text-transform: uppercase;
    color: var(--ink-3); margin: 0 0 clamp(14px, 2.4vw, 20px);
  }
  /* The zh floor, and the reason .eyebrow and .colophon h2 get one while the
     numbers above do not: these two hold translated words, so on zh they are
     汉字 at a size drawn for spaced Latin capitals. Mono goes too — no
     monospace face has a Chinese cut worth setting a label in. */
  :root[data-lang="zh"] .eyebrow {
    font-family: var(--font-body); font-size: .84rem; letter-spacing: .06em; text-transform: none;
  }

  /* Two columns, and the right one hangs lower than the left. The old list
     gave every piece a paragraph explaining it, which is the one thing a
     playable page cannot be told in words — you find out by clicking. So
     each entry is now its picture and its name, and the page gets to be
     half as tall and twice as much of a shop window.
     The stagger is what makes it a walk rather than a table: no two frames
     start at the same height, so the two of them below always have a step
     to take. */
  .index-wrap { position: relative; padding-top: 34px; }
  .index { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(26px, 4vw, 34px); }
  @media (min-width: 720px) {
    .index { grid-template-columns: 1fr 1fr; column-gap: clamp(24px, 4vw, 40px); }
    .index > li:nth-child(even) { margin-top: clamp(34px, 6vw, 70px); }
  }

  .entry { display: block; color: inherit; text-decoration: none; }

  /* The frame is not decoration: it is the floor the two of them walk on,
     which is why it is a real 2px line in the ink colour and why the
     corners are square. A rounded corner is a place a walker falls off. */
  .entry-fig {
    margin: 0; border: 2px solid var(--ink); background: var(--paper);
    transition: box-shadow .18s ease;
  }
  .entry-fig img { display: block; width: 100%; height: auto; }
  /* Hover casts a shadow and moves nothing. A card that shifts under the
     cursor would slide out from under whoever is standing on it — the
     walkers measure these rectangles once, not every frame. */
  .entry:hover .entry-fig,
  .entry:focus-visible .entry-fig { box-shadow: 7px 7px 0 var(--ink); }
  .entry:focus-visible { outline: none; }
  .entry:focus-visible .entry-fig { outline: 2px solid var(--pitch); outline-offset: 3px; }

  .entry-cap {
    display: flex; align-items: baseline; gap: .55em; flex-wrap: wrap;
    margin: .62em 0 0; font-size: 1rem; font-weight: 800; letter-spacing: -.012em;
  }
  :root[data-lang="zh"] .entry-cap { letter-spacing: 0; }
  .entry:hover .entry-name {
    text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px;
  }
  /* No zh floor on the number: it reads "01" in every language, and
     identical content should not be set four different ways. */
  .entry-no {
    font-family: var(--font-mono); font-size: .74rem; font-weight: 600;
    letter-spacing: .1em; color: var(--ink-3);
  }

  /* Four of the pieces exist only in English and Chinese. On the es and pt
     pages their links go to the English version, and this says so before the
     click rather than after it. It is empty markup in en and zh — where the
     link does land in the reader's language — so :empty removes it. */
  .entry-lang { color: var(--ink-3); font-weight: 500; font-size: .78rem; }
  .entry-lang::before { content: "·"; margin-right: .45em; opacity: .55; }
  .entry-lang:empty { display: none; }

  /* ── the two of them, out for a walk ───────────────────────────────
     The card frames are ledges. A rabbit and a horse walk along the tops
     and bottoms, drop off the ends onto whatever is below, and start over
     at the top when they run out of list. It is there to make the eye
     travel the whole grid instead of stopping at the first picture.
     Entirely decorative: inert to the pointer, invisible to a screen
     reader, and if the script never runs the page is exactly what it was
     without it. */
  .walk-layer { position: absolute; inset: 0; pointer-events: none; }
  .walker { position: absolute; left: 0; top: 0; will-change: transform; }
  .walker svg { display: block; height: 30px; width: auto; }
  @media (prefers-reduced-motion: reduce) { .walk-layer { display: none; } }

  /* ── keeping in touch ──────────────────────────────────────────────
     A place to leave an address, last on the page, because it is the
     question that only makes sense after you have seen the five things and
     not before. One field and one button: a name box would be a second
     thing to fill in for a mail that opens "here is a new one".

     No script. The form posts, the mail service answers with its own
     confirmation page, and that is the whole flow — the same reasoning as
     the language menu. An inline "thanks!" without leaving the page is the
     one thing here that would genuinely need JavaScript, and it is worth
     asking for rather than assuming. */
  .subscribe {
    margin-top: clamp(34px, 6vw, 56px);
    padding-top: clamp(30px, 5vw, 40px);
    border-top: 1px solid var(--rule);
    display: grid; gap: 14px;
    grid-template-columns: 1fr;
  }
  @media (min-width: 720px) {
    /* The words and the field side by side once there is room, with the words
       given the wider share: the field only needs to be wide enough for an
       address, and a form control stretched across a 940px column reads as the
       page's main event, which this is not. */
    .subscribe { grid-template-columns: 1fr auto; align-items: start; column-gap: clamp(24px, 4vw, 44px); }
  }
  .subscribe-say { margin: 0; max-width: 42ch; font-size: .95rem; color: var(--ink-2); line-height: 1.6; }
  .subscribe-say b { color: var(--ink); font-weight: 700; }
  :root[data-lang="zh"] .subscribe-say { font-size: .95rem; }

  .subscribe-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
  .subscribe-form input {
    font: inherit; font-size: .92rem;
    padding: 9px 12px; min-width: 0; width: clamp(180px, 42vw, 244px);
    color: var(--ink); background: var(--paper);
    border: 2px solid var(--ink); border-radius: 0;
    -webkit-appearance: none; appearance: none;
  }
  .subscribe-form input::placeholder { color: var(--ink-3); opacity: 1; }
  .subscribe-form input:focus-visible,
  .subscribe-form button:focus-visible { outline: 2px solid var(--pitch); outline-offset: 2px; }
  .subscribe-form button {
    font: inherit; font-size: .88rem; font-weight: 700; letter-spacing: .01em;
    padding: 11px 16px; cursor: pointer; white-space: nowrap;
    color: var(--paper); background: var(--ink);
    border: 2px solid var(--ink); border-radius: 0;
  }
  .subscribe-form button:hover { background: var(--pitch); border-color: var(--pitch); }
  /* The promise, kept small and kept honest. It sits under the field rather
     than under the sentence because it is a condition of the thing you are
     about to do, not part of the pitch. */
  .subscribe-note { flex-basis: 100%; margin: 0; font-size: .78rem; color: var(--ink-3); line-height: 1.5; }
  :root[data-lang="zh"] .subscribe-note { font-size: .82rem; }

  /* ── colophon ──────────────────────────────────────────────────────
     Nothing renders in this register today: the two blocks that described
     the site to its reader have been removed for good, and what is left is
     one address block still held back by the omit markers. The rules stay
     because that block ships by deleting those markers, and a style deleted
     alongside the last thing using it is a style someone has to reinvent
     from a screenshot. auto-fit rather than a fixed column count, for the
     same reason: whatever ends up here should lay itself out without a
     breakpoint being invented for the occasion. */
  .colophon {
    margin-top: clamp(34px, 6vw, 56px);
    padding: clamp(30px, 5vw, 40px) 0 clamp(50px, 9vw, 80px);
    border-top: 1px solid var(--rule);
    display: grid; gap: clamp(26px, 4vw, 34px);
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  }
  .colophon h2 {
    font-family: var(--font-mono);
    font-size: .7rem; font-weight: 600; letter-spacing: .17em; text-transform: uppercase;
    color: var(--ink-3); margin: 0 0 .8em;
  }
  :root[data-lang="zh"] .colophon h2 {
    font-family: var(--font-body); font-size: .84rem; letter-spacing: .06em; text-transform: none;
  }
  .colophon p { margin: 0; max-width: 46ch; font-size: .92rem; color: var(--ink-2); line-height: 1.66; }
  :root[data-lang="zh"] .colophon p { font-size: .95rem; }

  .end {
    border-top: 1px solid var(--rule);
    padding: 20px 0 40px;
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    font-size: .8rem; color: var(--ink-3);
  }
  /* Signature size, and quieter than the masthead: the foot of the page is
     where the mark is a sign-off, not an entrance. align-items goes from
     baseline to center on this row because there is no longer any text on the
     left to share a baseline with — an SVG's baseline is its bottom edge, so
     baseline alignment would have hung the domain name off the mark's feet. */
  .end .mark .wordmark { width: 112px; color: var(--ink-3); }
  :root[data-lang="zh"] .end { font-size: .86rem; }

  @media (prefers-reduced-motion: reduce) {
    * { transition: none !important; }
  }
