/* =====================================================================
   THE LETTERING, CUT AND FILED WITH THE SHEET (v28, E).
   The four faces are served from this site, not from Google: the latin cut
   of each, 232 KB in all, OFL (Sorts Mill Goudy, Archivo Narrow, Petrona)
   and Apache 2.0 (Special Elite) — the licences are beside the files in
   /fonts. Archivo Narrow and Petrona are variable on wght; the weight
   ranges here are the ones the sheet asks for. font-display: swap, so the
   paper is never blank waiting for ink.
   ===================================================================== */
@font-face {
    font-family: 'Sorts Mill Goudy';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/sorts-mill-goudy-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Sorts Mill Goudy';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/sorts-mill-goudy-italic.woff2') format('woff2');
}
/* THE PLATE'S OWN FACE (room E3 follow-up, 2026-09-13): the same Goudy with
   its lining figures frozen in (`pyftfeatfreeze -f lnum`), because a canvas
   2D context has no font-variant-numeric and the atlas would otherwise set
   every figure old-style (2o26). Used by the engine's glyph atlas only. */
@font-face {
    font-family: 'Sorts Mill Goudy LN';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/sorts-mill-goudy-normal-lnum.woff2') format('woff2');
}
@font-face {
    font-family: 'Sorts Mill Goudy LN';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/sorts-mill-goudy-italic-lnum.woff2') format('woff2');
}
@font-face {
    font-family: 'Archivo Narrow';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('/fonts/archivo-narrow-normal-400-700.woff2') format('woff2');
}
@font-face {
    font-family: 'Archivo Narrow';
    font-style: italic;
    font-weight: 400 700;
    font-display: swap;
    src: url('/fonts/archivo-narrow-italic-400-700.woff2') format('woff2');
}
@font-face {
    font-family: 'Petrona';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('/fonts/petrona-normal-400-700.woff2') format('woff2');
}
@font-face {
    font-family: 'Petrona';
    font-style: italic;
    font-weight: 400 600;
    font-display: swap;
    src: url('/fonts/petrona-italic-400-600.woff2') format('woff2');
}
@font-face {
    font-family: 'Special Elite';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/special-elite-normal.woff2') format('woff2');
}

/* =====================================================================
   THE SURVEY — the site is the sheet.
   Palette and lettering are taken from Ordnance Survey one-inch sheets:
   Hawes (Third Edition, 1907) and Edinburgh (Popular Edition, 1926).
   Cream paper, sepia hachures, orange-brown contours, pale cyan water,
   near-black lettering, oxblood for the marks that are links.
   ===================================================================== */

:root {
    /* the desk the sheet lies on: dark walnut, waxed */
    --board:        #31241a;
    --board-hi:     #47331f;
    --board-lo:     #1d140d;
    --board-line:   #241911;

    /* the paper */
    --paper:        #f1e7d1;
    --paper-hi:     #f7efdd;
    --paper-shade:  #e6d9bd;

    /* the ink */
    --ink:          #2e261c;
    --ink-2:        #55483a;
    --ink-3:        #7d6d59;

    /* the plates */
    --sepia:        #9a6b41;   /* hachures */
    --contour:      #b8763f;   /* intermediate contour */
    --contour-idx:  #97562a;   /* index contour, figured */
    --coast:        #6d4520;
    --sea:          #a3b9bd;   /* the sea: printed, not digital */
    --sea-line:     #4a7079;
    --water:        #c8dcd0;   /* still water: lighter and greener than the sea */
    --water-line:   #3f7f88;
    --foreshore:    #d8d2b0;
    --accent:       #96322a;   /* revision ink — bench marks, links */

    /* THE FILM over the plate, and how strong it is. One number. */
    --film: .55;
    /* THE PAPER-AT-DISTANCE EXPERIMENT (?paper=1): 0 is the palette as
       printed, 1 is paper. Set from the zoom by the canvas; nothing reads it
       unless the flag is on. */
    --paperness: 0;

    --measure: 40rem;

    /* =================================================================
       THE SUN, AS IT REACHES THE ROOM.

       Tommy, 2026-09-10: "You essentially rotate the world in the portal
       and actually change the light source in the real world ... this
       world in the portal is controlling stuff and slightly more sinister
       than it seems." So there is no light in this room of its own. The
       desk is lit THROUGH the portal, by the sun that is over the ground
       inside it, and every shadow on the oak — the sheet's, the two
       books', the pin's, the place where the wax catches — is struck from
       these five numbers. sky.js writes them on every view change: the
       sun's own bearing over London, turned through however far the world
       has been turned. Turn the map a half-turn and they all swing round.

       The defaults are the sheet's old fixed north-west light, so the
       page is lit correctly before a line of script has run.
       ================================================================= */
    --sun-az:    315;       /* bearing on the SCREEN, clockwise from up */
    --sun-el:    34;        /* degrees above the horizon */
    --sun-dx:    .7071;     /* the direction a shadow runs, x right ... */
    --sun-dy:    .7071;     /* ... y down. Unit; multiply by a length. */
    --sun-len:   1;         /* cotangent of the height, held in range */
    --sun-soft:  1;         /* cloud softens an edge */
    --sun-dark:  1;         /* and takes the weight out of it */
    /* HOW MUCH CAST SHADOW THERE IS AT ALL: one under a clear sun, nought
       under a full overcast or a thin crescent. Everything cast in this
       room is multiplied by it, and what is left at nought is the CONTACT
       shadow — the dark line a thing has because it is lying on something,
       which does not belong to any light and never goes. */
    --sun-cast:  1;
    --wet:       0;         /* rain in the room as well as in the sky */
    --night:     0;         /* 1 when the sun is down and the lamp is lit */
    --lamp-x:    26%;       /* where the wax on the oak catches */
    --lamp-y:    26%;

    /* THE SKY OVER THE BLOCK — a wash, never a photograph. Keyed to the
       sun by sky.js; --sky-k is how much of it there is, which is a
       function of the tilt alone, and is nought on the flat sheet. */
    --sky-top:     #b2c0c1;
    --sky-horizon: #e8dfc8;
    --sky-haze:    #ded0b8;
    --sky-rain:    0;
    --sky-k:       0;
}

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
    margin: 0;
    background: var(--board);
    color: var(--paper);
    font-family: 'Sorts Mill Goudy', Georgia, serif;
    overflow: hidden;
    overscroll-behavior: none;
    -webkit-text-size-adjust: 100%;
}

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--paper); color: var(--ink); padding: .5rem .9rem;
    font: 600 .8rem/1 'Archivo Narrow', sans-serif; letter-spacing: .08em;
    text-transform: uppercase; text-decoration: none;
}
.skip:focus { left: .6rem; top: .6rem; }

/* =====================================================================
   The canvas
   ===================================================================== */

.canvas {
    position: fixed;
    inset: 0;
    height: 100dvh;
    outline: none;
    /* Dragging the map used to select the lettering on it, and then anything
       else the pointer crossed. Nothing on the sheet is text you copy. */
    -webkit-user-select: none;
    user-select: none;
}
/* THE FOCUS RING IS NOT A RED BORDER ROUND THE SCREEN.
   Tommy, on v14: "I also sometimes seem to manage to get a red border around
   my entire screen." This was it: the canvas is the whole viewport, and it
   takes keyboard focus (it is the map's key handler), so one arrow key drew
   two pixels of revision ink round the edge of the world. A sheet on a desk
   does not light up. What a keyboard reader needs is to know which thing has
   the keys, and the thing that has them is the WINDOW in the plate — so the
   ring is a hairline of ink inside the neat line, and nothing at all for a
   hand that arrived by pointer. */
.canvas:focus-visible { box-shadow: none; }
/* PH10: a book's leaf is turned by a swipe, so the browser must not take
   the stroke for a scroll: the field book's leaf is a scroller and a
   swipe on it was a pointercancel, so it never turned */
html.phone .book, html.phone .book * { touch-action: none; }
html.phone, html.phone body { overscroll-behavior: none; touch-action: none; overflow: hidden; }
/* GW1: on a phone the canvas is the WINDOW's size (phone.js --gw/--gh,
   innerWidth/innerHeight), not the page box's: iOS 26 Chrome's first load
   on its side has the box the screen's width and the window narrower.
   Upright on the map the turned rule below sets its own size. */
html.phone .canvas { width: var(--gw, auto); height: var(--gh, 100dvh); }
/* SB1 (2026-10-05): on its side Chrome on an iPhone will not let the page
   into the notch's and the home bar's margins and fills them with the
   page's own background, so they were two bars of brown board either end.
   Tommy: "Near black is probably right": they read as the phone's edge. */
@media (orientation: landscape) {
    html.phone, html.phone body { background: #0f0c09; }
    /* SB2: and over the room's ground below (html.room body, later and as
       specific), which kept them brown on a real iPhone 17 Pro */
    html.phone.room body { background: #0f0c09; }
}

/* Paper grain. This lived inside the SVG as a full-sheet feTurbulence, which
   Chrome re-rasterises at every zoom level and which dominated the cost of
   panning. As a fixed, tiled layer over the canvas it is rasterised once. */
.canvas::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' seed='7'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
    background-size: 160px 160px;
    opacity: calc(.12 + var(--paperness, 0) * .17); mix-blend-mode: multiply;
}

/* R12: `.sky-wash`, `.sky-bodies`, `.sky-star`, `.sky-moon-*` are gone
   (survey/v32). They styled nodes relief.js made; water.js (4) draws the
   wash now, and nothing draws the moon's disc — sky.js keeps it a LIGHT. */

/* =====================================================================
   The field notebook — a cloth-bound book lying on the desk beside the
   sheet. Closed it is a book; opened it is two ruled pages. There is no
   panel of web chrome anywhere on this site.
   ===================================================================== */
/* THE BOOK IS AN OBJECT ON THE DESK, NOT A TAB.
   Closed, it lies under the south-west of the sheet with a hand's breadth
   of cloth and the fore-edge of the block showing past the paper's edge —
   its place is set by JS from where the sheet actually lands. Opened, it
   slides out from under the paper and comes to the top of everything. */
.book {
    position: absolute; left: 1.6rem; bottom: 1.4rem; z-index: 1;
    transition: transform .22s ease;
    /* HB1 (2026-09-22): THE ASIDE'S OWN BOX TAKES NO POINTER. The aside
       has a box of its own — the cover button's untransformed layout, at
       the rest place css puts it — and it never moves: the cover and the
       hit box are laid on the drawn book every frame by desk-books.js,
       but this box stayed where it was, a dead rectangle 378 x 623 css px
       at the desk pose's east that swallowed every press with the default
       cursor. Standing, it lay over most of the laptop. Tommy: "when
       standing the hitbox for going to the laptop is all wrong. lots of
       the screen doesn't have a pan arrow but clicking does nothing, it's
       quite ambiguous". Only the things that ARE the book take the hand:
       the cover, the hit box and the open body, each of which says so. */
    pointer-events: none;
}
.book-cover, .book-hit { pointer-events: auto; }
.book[data-open="true"] .book-body { pointer-events: auto; }
.book[data-open="true"] { z-index: 60; }

/* ONE STILL HIT BOX TO A BOOK.
   Tommy, on v14: "The click boxes for both are also a bit funky. I can hit
   locations where I don't have the hand icon … sometimes it seems to unhover
   while I'm still hovering over it." The lift used to be put on the button
   itself, so the element under the pointer moved out from under the pointer:
   hover ended, the book dropped back, and at the bottom thirteen pixels of
   the cover the two states chased each other. The BUTTON is now a fixed box
   tall enough to hold both the resting cover and the lifted one, with no
   paint of its own; the FACE inside it is what moves. */
.book-cover {
    position: relative; display: block; cursor: pointer;
    width: 9.4rem; height: calc(6.4rem + 15px); padding: 0;
    border: 0; background: none; box-shadow: none;
    -webkit-appearance: none; appearance: none;
}
/* THE COVER IS NOT PAINTED HERE ANY MORE (THE SWITCH). The cloth, the
   spine, the fore-edge of the block and the cast shadow are OBJECTS in the
   scene — js/room/objects.js draws them under the same sun as everything
   else — so this box has no paint of its own. What it still does is hold
   the paper LABEL, which is type and stays DOM, exactly where the engine
   draws the cover; and it still lifts on hover, which moves the label with
   the cover the engine is lifting. */
/* R22: AND IT NO LONGER LIFTS ON ITS OWN. The label used to slide
   thirteen pixels up the cover on hover while the cover itself — the
   cloth, the spine, the shadow, the sheet lying over its head — stayed
   put: a caption on a hover state, not a book being picked up. The whole
   book moves now (objects.js, BOOK_OUT / BOOK_RISE) and this element is
   given the same offset from there, by transform, every frame of the
   180 ms. So there is no :hover rule here and NO CSS TRANSITION on the
   transform: two easings on one property fight, and the label would lag
   the cloth it is stuck to. `opacity` keeps its own. */
/* R29: AND THE LABEL IS BACK BEFORE THE BOOK MOVES AGAIN. The cover comes
   back on to the desk the instant the panel starts to shut (objects.js, in
   the hand's pose, behind 94 %-opaque leaves) and it slots home at 300 ms,
   so the paper label on it has the window between: it fades up from 120 ms
   — the leaves are at 17 % by then — and is whole at 240, a beat before
   the book slides. It used to start at 160 and not finish until 380, which
   put the label's fade ON TOP of the movement it belongs to. */
.book-face {
    position: absolute; left: 0; right: 0; bottom: 0; height: 6.4rem;
    display: block; border-radius: 2px 4px 4px 2px;
    transition: opacity .12s ease .12s;
}
/* THE LABEL SITS AT THE FOOT OF THE COVER, not the head, because the head
   of the book is under the sheet: the only part of it anyone ever reads on
   the desk is the corner that shows past the paper's southern edge. */
/* R63: the label is inset from the SPINE, and the spine is the one part
   of the cover whose width is a millimetre count (objects.js bookDims
   writes --bk-sp at ?books=real / ?books=big). 12.8px + 10.4 is 1.45rem
   to the pixel, so the default is the rule R7 shipped. */
.book-label {
    position: absolute; left: calc(var(--bk-sp, 12.8px) + 10.4px); right: .55rem; bottom: .5rem;
    display: block; padding: .34rem .3rem .4rem;
    background: #efe6cd; color: #2e261c;
    border: 1px solid rgba(46,38,28,.5);
    box-shadow: 0 1px 0 rgba(255,255,255,.4) inset;
    text-align: center;
}
/* LK1: AT NIGHT THE LAMP IS THE LIGHT, ON THE PAGES TOO. The sheet, the
   card and the print dim outside the lamp's pool (lampTemp, six shader
   copies); the DOM leaves and the cover labels are the same paper in the
   same room, so they take the same light: --dim is sky.js's night plus
   under half the dusk, the number the shaders are handed, nought by day.
   Multiplied, so the ink darkens with its paper and nothing reflows. */
.book-body::after {
    content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
    border-radius: inherit; mix-blend-mode: multiply;
    background: linear-gradient(140deg, #fff6e4 0%, #e6d6bc 35%, #a8987f 80%, #8a7c68 100%);
    opacity: var(--dim, 0);
}
.book-label { filter: brightness(calc(1 - .42 * var(--dim, 0))); }
.book-label-rule {
    display: block; height: 0; border-top: 1px solid rgba(46,38,28,.3);
    margin: 0 .5rem .28rem;
}
.book-label-a {
    display: block; font: 400 .9rem/1 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .05em;
}
.book-label-b {
    display: block; margin-top: .18rem; white-space: nowrap;
    font: 400 .55rem/1.2 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .09em; text-transform: uppercase; color: #6b5c47;
}

/* --- opened: two ruled pages, sewn at the gutter --- */
/* THE BOOK OPENS WHERE IT LIES.
   Tommy, on v9: "The field book sits nicely when closed but when opened the
   bottom left of the opened book should roughly align with the closed one,
   not perfectly over the map." So the open block is pinned by its SOUTH-WEST
   corner to the closed cover's south-west corner — left:0, bottom:0 against
   the .book box, which JS has already put at the cover's own place on the
   desk — and it opens up and to the east from there, over the sheet. It may
   overhang the desk margin at the top; a book being read does.
   The pages were shrunk before the anchor moved, so that a spread still fits
   the glass at 1440x900 with the anchor down at the desk. */
.book-body {
    position: absolute; left: 0; bottom: 0;
    transform: translateY(14px);
    /* Two 100:150 mm leaves: constrain the frame by width AND height rather
       than squeezing a wide web panel into an unrelated rectangle. */
    width: min(44rem, calc(100vw - 3.2rem), calc((70dvh - 20px) * 1.333333));
    color: var(--ink);
    overflow: hidden;
    max-height: 0; opacity: 0;
    /* ==== BK1: AND THE TRANSFORM IS NOT TRANSITIONED ==================
       The panel is laid on the desk by the camera now (desk-books.js,
       panelsAtPose), which writes a fresh matrix on every frame the
       camera moves. A .3s ease on that property is a second easing on
       top of the camera's own — R30's rule for the covers, "two easings
       on one property fight" — and the book would swim 300 ms behind the
       desk it is lying on. What goes with it is the 14 px slide the
       panel used to make as it opened, which happened under a panel that
       was still at max-height 0 and opacity 0; the two beats the
       choreography is measured on (.22s the leaves, .3s the block) are
       untouched. */
    transition: max-height .3s ease, opacity .22s ease;
    /* the boards, still bound: cloth showing all round the opened block */
    padding: 10px 11px;
    border-radius: 3px;
    box-shadow:
        0 2px 4px rgba(0,0,0,.36),
        calc(var(--sun-dx) * var(--sun-len) * var(--sun-cast) * 20px)
        calc(var(--sun-dy) * var(--sun-len) * var(--sun-cast) * 20px)
        calc(52px * var(--sun-soft)) rgba(0,0,0, calc(.62 * var(--sun-dark) * var(--sun-cast))),
        inset 0 0 0 1px rgba(0,0,0,.4);
    background:
        repeating-linear-gradient(0deg, rgba(255,255,255,.04) 0 1px, rgba(0,0,0,.05) 1px 2px),
        repeating-linear-gradient(90deg, rgba(255,255,255,.04) 0 1px, rgba(0,0,0,.05) 1px 2px),
        linear-gradient(120deg, #4d5c4a 0%, #3d4a3c 55%, #2f3a2f 100%);
}
.book-inner {
    position: relative;
    background:
        linear-gradient(90deg, rgba(90,70,40,.22) 0 3px, rgba(0,0,0,0) 14px),
        linear-gradient(270deg, rgba(90,70,40,.22) 0 3px, rgba(0,0,0,0) 14px),
        #f3ecd8;
    box-shadow: 0 1px 4px rgba(0,0,0,.4);
}
/* No offset: the corner of the open book IS the corner of the closed one. */
.book[data-open="true"] { transform: none; }
.book[data-open="true"] .book-body {
    max-height: min(70dvh, 37rem); opacity: 1; transform: none;   /* BK1 lays the real one */
    /* 2026-09-20: the .42 s wait for "the physical cover to travel and
       hinge first" is gone. On the desk (L, below) the stage rules had
       already put transition: none over it; on the phone's bars, where no
       cover moves at all, it was 420 ms of nothing between the tap and
       the page. */
}
/* A SHUT BOOK'S LEAVES ARE NOT ON THE DESK. The collapsed body still sat at
   the foot of the cover and took the pointer there, so the bottom few pixels
   of both books were dead: no hand cursor, no lift, no click — the other half
   of "the click boxes for both are also a bit funky". */
.book:not([data-open="true"]) .book-body { pointer-events: none; }
/* THE CLOSED BOOK IS NOT STILL LYING THERE.
   Tommy, on v11: "The open field book sits over the closed field book but
   not perfectly, so you can see there's a closed book underneath still. It
   either needs to fit perfectly (or hide the closed book)." A book cannot
   be both shut and open on the same desk, so the cover goes as the leaves
   come out and returns as they go back in. */
/* CLOSING IS OPENING, RUN BACKWARDS.
   Tommy, on v14: "When you click off of them, they don't really have a nice
   animation." Opening: the cover goes at once, the leaves come out. Closing:
   the leaves go back in over the same span, the book settles under the
   sheet, and only then does the cover fade back on to the desk — which is
   the same three movements in the other order, at the same durations. */
/* Tommy, 2026-09-14: "field guide -> esc leaves a weird black and white
   bordered box on screen" — the browser's focus ring on the cover, which
   Esc's keyboard event makes :focus-visible, drawn round the button's
   upright box under a pitched cover. The cover LIFTS when it has focus
   (objects.js liftOnBtn), and that is its focus indicator. */
.book-cover:focus-visible { outline: none; }
.book[data-open="true"] .book-cover { pointer-events: none; }
/* THE HAND FINDS THE WHOLE BOOK. Tommy, 2026-09-19: "it really doesn't
   feel massively clickable, there's areas at the bottom, even unhovered,
   that aren't clickable". The cover above is the LABEL's plate — the top
   face alone, as an affine, with nothing of the fore-edge or the block
   under it. This is the HIT: one box a book, made and laid by
   desk-books.js (hitsAtPose) on the whole drawn solid — the far edge of
   the top face down to the near edge of the footprint on the wood — as
   the exact projective plate, cut by the same paper, with no paint. It
   lies over the cover and hands it the hand; focus stays on the button.
   Shown by the script, only once it is laid; never on the phone's bars. */
.book-hit {
    position: absolute; left: 0; top: 0; display: none;
    transform-origin: 0 0; pointer-events: auto; cursor: pointer;
    background: none;
}
.book[data-open="true"] .book-hit { pointer-events: none; }
.book[data-open="true"] .book-face {
    transform: rotate(-1.6deg) translateY(2px);
    opacity: 0;
    transition: transform .18s ease, opacity .14s ease;
}
/* data-open becomes false before the physical hinge runs back. Keep the
   closed label out of that retract/close interval; it belongs to the cover
   only after the cover is shut and home. */
.book[data-book-motion="opening"] .book-face,
.book[data-book-motion="framing"] .book-face,
.book[data-book-motion="reading"] .book-face,
.book[data-book-motion="retracting"] .book-face,
.book[data-book-motion="closing"] .book-face { opacity: 0; }

.book-inner {
    display: grid; grid-template-columns: 1fr 1fr;
    /* EXACTLY THIRTY-TWO RULES DEEP (36rem = 32 x 18px). A page longer than
       the leaf scrolls, as the marks list does; making the leaf a whole
       number of rules means the fold falls ON a rule rather than through
       the middle of a line of writing. */
    aspect-ratio: 4 / 3; max-height: none; overflow-y: auto;
}
/* the gutter: two pages meeting, with a shadow in the fold */
.book-inner::before {
    content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 26px;
    transform: translateX(-50%); pointer-events: none;
    background: linear-gradient(90deg, rgba(90,70,40,0) 0%, rgba(90,70,40,.20) 42%,
                rgba(60,44,22,.30) 50%, rgba(90,70,40,.20) 58%, rgba(90,70,40,0) 100%);
}
/* THE TYPE SITS ON THE RULING.
   Tommy: "In our field guide can we make the text actually fit into the
   lines in the book properly?" A ruled book has one measure, and everything
   written in it is written to that measure. --rule is it. Every line of type
   on these pages has a line box exactly one rule deep, every heading and
   every gap between sections is a whole number of rules, and the ruling
   itself is drawn at the FOOT of each line box (--rule minus a hairline), so
   the baselines land on the rules rather than floating between them.
   --rule-drop is the one number that registers the ruling to the baseline;
   it was set by eye against a capture, which is how a compositor would have
   done it. */
.book-page {
    --rule: 18px;
    --rule-drop: -1px;
    padding: var(--rule) 1.1rem calc(var(--rule) * 2);
    /* feint ruling, as a field book is ruled */
    background-image: repeating-linear-gradient(180deg,
        rgba(70,96,120,0) 0 calc(var(--rule) - 1px), rgba(70,96,120,.16) calc(var(--rule) - 1px) var(--rule));
    background-size: 100% var(--rule);
    background-position: 0 calc(var(--rule) + var(--rule-drop));
}
.book-page--left { border-right: 1px solid rgba(120,96,60,.25); }

/* one whole rule between sections: 17px of space and the 1px rule itself */
.key-sec + .key-sec { margin-top: var(--rule); padding-top: calc(var(--rule) - 1px); border-top: 1px solid rgba(46,38,28,.2); }
.key-head {
    margin: 0; height: var(--rule);
    font: 400 .82rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}

.key-legend { list-style: none; margin: 0; padding: 0; }
.key-legend li {
    display: grid; grid-template-columns: 34px 1fr; align-items: center; gap: .55rem;
    font: 400 .78rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif; color: var(--ink);
    min-height: var(--rule); padding: 0;
}
.key-legend svg { width: 30px; height: 12px; overflow: visible; }
.lg-contour { fill: none; stroke: var(--contour); stroke-width: .8; }
.lg-contour.lg-index { stroke: var(--contour-idx); stroke-width: 1.4; }
.lg-hachure { fill: none; stroke: var(--sepia); stroke-width: .9; }
/* the three tints of the water, in the order the sea takes them */
.lg-sea { stroke: none; }
.lg-sea--0 { fill: #b4c5c8; }
.lg-sea--1 { fill: #9bb0b7; }
.lg-sea--2 { fill: #7f9ea9; }
.lg-depth  { fill: none; stroke: var(--sea-line); stroke-width: .5; opacity: .55; }
/* R42 (b): THE LAND'S TINT SCALE, set as a printed one is: a strip of the
   bands in the order of height inside a box rule, the figure each band
   starts at under its own left edge, and the line under both. Three whole
   rules, so the leaf's measure is unchanged in kind. The colours and the
   figures are the engine's (books.js reads field.js's ladder); nothing
   about this scale is written in a stylesheet but its rule and its type. */
.key-legend li.lg-tints-row { grid-template-columns: 1fr; gap: 0; }
.lg-tints {
    display: flex; align-items: stretch;
    height: 11px; margin: 3px 0 4px;
    border: 1px solid rgba(46,38,28,.45);
}
.lg-tints span { flex: 1 1 0; }
.lg-tint-figs {
    display: flex;
    font: 400 .62rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink-2); font-variant-numeric: lining-nums tabular-nums;
    letter-spacing: .02em;
}
.lg-tint-figs span { flex: 1 1 0; }
/* the line under the scale is a line of the key like any other, so it is
   set in the key's own text column and not against the outer margin */
.lg-tints-row > span:last-child { padding-left: calc(34px + .55rem); }
.lg-track { fill: none; stroke: var(--ink-2); stroke-width: 1; stroke-dasharray: 4 2.5; }
.lg-node { fill: var(--paper); stroke: var(--ink-2); stroke-width: .8; }
.lg-trig { fill: none; stroke: var(--ink); stroke-width: .9; }
.lg-dot { fill: var(--ink); }
.lg-marsh { fill: none; stroke: var(--water-line); stroke-width: .8; }
.lg-bm { fill: none; stroke: var(--accent); stroke-width: 1.3; }
/* the figure beside the cut, as it is set on the plate */
.lg-bm-fig { font: 400 5px 'Sorts Mill Goudy', Georgia, serif; fill: var(--ink-2); letter-spacing: .04em; }
.lg-vig-fill { fill: var(--paper-hi); }
.lg-vig { fill: none; stroke: var(--ink-2); stroke-width: 1; stroke-linejoin: round; }
/* R36: the marker horizon, as it is printed on the plate — a pecked
   hairline in the contour's own ink, no heavier than a contour. */
.lg-horizon { fill: none; stroke: var(--contour); stroke-width: .9; stroke-dasharray: 3.4 2.2; }
.lg-unsurveyed { fill: none; stroke: #a89268; stroke-width: .8; stroke-dasharray: 3 2; }
/* R12: `.lg-water`, `.lg-ripple`, `.lg-mhw`, `.lg-fsh-*` gone — base.njk's
   key never set them. It does set every other `.lg-` here. */

/* R36: `.sw`, `.sw--new`, `.sw--old` gone with the two Series — there are
   no eras on this sheet and the key has no swatch left to hand-colour. */

/* R12: `.key-marks`, `.key-mark*`, `.key-sec--marks`, `.key-marks-source`
   gone — survey-map.js built that index, books.js does not (survey/v32).
   The paging furniture below IS live. */

.key-note {
    margin: 0; font: 400 .76rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink-2);
}
.key-note--plough { margin: var(--rule) 0 0; font-style: italic; }
.key-note + .key-note { margin-top: var(--rule); }
/* a live figure set into a note: the sheet revises it, so it is lining and tabular */
.key-fig { font-variant-numeric: lining-nums tabular-nums; }

/* --- pages that turn. Twelve marks to a page; the corner is the thing you
   take hold of, as you would on paper. --- */
.book-page { position: relative; }
.book-folio {
    position: absolute; left: 1.1rem; right: 1.1rem; bottom: 0; margin: 0;
    height: var(--rule);
    font: 400 .66rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif; color: var(--ink-3);
    letter-spacing: .12em; text-transform: uppercase; text-align: center;
    font-variant-numeric: lining-nums tabular-nums;
}
.book-corner {
    position: absolute; bottom: 0; right: 0; width: 2.6rem; height: 2.6rem;
    padding: 0; border: 0; background: none; cursor: pointer; z-index: 2;
}
.book-corner[hidden] { display: none; }
.book-corner--prev { right: auto; left: 0; }
/* the leaf lifting: a triangle of the page's own tone with a ruled fold.
   R72 (2026-09-21): AND IT READS AS A LEAF LIFTED, NOT A SHADOW CAST. The
   fold used to be one flat fifth-strength tint, which at a reading
   distance is a stain on the page. A corner that has been turned up shows
   the page's UNDERSIDE — a shade lighter than the face it left — with the
   crease itself a darker line and a little of the page's own shadow under
   the lifted flap. Same triangle, same size, same reach for the hand. */
.book-corner-glyph {
    position: absolute; inset: auto 0 0 0; height: 100%;
    background:
        linear-gradient(225deg, rgba(255,250,236,.55) 0 30%, rgba(120,96,60,.26) 30% 44%, rgba(0,0,0,0) 44%);
    border-bottom: 1px solid rgba(120,96,60,.34);
    filter: drop-shadow(-1px -1px 1px rgba(60,44,20,.18));
}
.book-corner--prev .book-corner-glyph {
    background:
        linear-gradient(135deg, rgba(255,250,236,.55) 0 30%, rgba(120,96,60,.26) 30% 44%, rgba(0,0,0,0) 44%);
    filter: drop-shadow(1px -1px 1px rgba(60,44,20,.18));
}
.book-corner:hover .book-corner-glyph { background-image: linear-gradient(225deg, rgba(150,50,42,.26) 0 52%, rgba(0,0,0,0) 52%); }
.book-corner--prev:hover .book-corner-glyph { background-image: linear-gradient(135deg, rgba(150,50,42,.26) 0 52%, rgba(0,0,0,0) 52%); }
.book-corner:focus-visible { outline: 1px solid var(--accent); outline-offset: -2px; }

/* R72: the body takes the opened book's focus (the arrows turn its pages
   from there); no ring, for the reason the notch it replaces had none. */
.book-body:focus { outline: none; }

/* the surveyor's own lines, stamped inside the cover */
.key-links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0 .9rem; line-height: var(--rule); }
.key-links a {
    font: 400 .82rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif; color: var(--accent);
    text-decoration: none; border-bottom: 1px solid rgba(150,50,42,.4);
}
.key-links a:hover { border-bottom-color: var(--accent); }

/* =====================================================================
   FB2 (2026-09-28): THE FIELD BOOK AS A BOOK. Tommy: "remember it's about
   craft and aesthetic and it's meant to be like a real book so make it
   beautiful" — "the quality with which you create the actual HTML content
   as much as the text". The back matter of a real survey: a title leaf, the
   sections numbered in the sheet's red, the conventional signs in named
   groups with the meaning set in italic after the name (as an Ordnance
   key sets it), and a colophon closing the book. THE RULING HOLDS: every
   line box below is a whole number of rules, and every space between
   things is too, so the writing stays on the feint lines of the page.
   ===================================================================== */
.key-note { text-wrap: pretty; }
/* the links as a printed line of particulars: separated, not listed */
.key-links { gap: 0 .5rem; }
.key-links li + li::before {
    content: "\00B7"; margin-right: .5rem; color: var(--ink-3);
    font: 400 .82rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
}

/* ---- the title leaf: set on the page's centre line, low on the leaf ---- */
.key-sec--title { text-align: center; padding-top: calc(var(--rule) * 6); }
.key-title-a {
    margin: 0; font: 400 .74rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .26em; text-transform: uppercase; color: var(--ink-2);
    padding-left: .26em;                        /* the tracking's last space, given back */
}
.key-title-b {
    margin: 0; font: italic 400 1.75rem/calc(var(--rule) * 3) 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink); text-wrap: balance;
}
.key-title-c {
    margin: 0; font: 400 .6rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .22em; text-transform: uppercase; color: var(--ink-3);
    padding-left: .22em; font-variant-numeric: lining-nums;
}
/* the ornament: a short rule broken by the cartouche's own red lozenge,
   one rule deep, standing on its centre */
.key-orn { position: relative; margin: 0; height: var(--rule); }
.key-orn::before {
    content: ""; position: absolute; left: 50%; top: 50%; width: 4.4rem; height: 1px;
    transform: translate(-50%, -50%);
    background: linear-gradient(90deg, rgba(46,38,28,0), rgba(46,38,28,.42) 30%,
                rgba(46,38,28,.42) 42%, rgba(46,38,28,0) 42%, rgba(46,38,28,0) 58%,
                rgba(46,38,28,.42) 58%, rgba(46,38,28,.42) 70%, rgba(46,38,28,0));
}
.key-orn::after {
    content: ""; position: absolute; left: 50%; top: 50%; width: 4px; height: 4px;
    transform: translate(-50%, -50%) rotate(45deg); background: var(--accent);
}

/* ---- the sections: numbered in the sheet's red, the numeral hung ---- */
.key-num {
    display: inline-block; min-width: 1.9em; color: var(--accent);
    letter-spacing: .08em;
}
/* a group of signs: named in italic, a rule of air above it */
.key-sub {
    margin: var(--rule) 0 0; height: var(--rule);
    font: italic 400 .8rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink-2); letter-spacing: .01em;
}
.key-head + .key-sub { margin-top: 0; }
/* the sign's name in the key's roman, its meaning in italic after an en space */
.lg-g { color: var(--ink-2); font-style: italic; }
.lg-g::before { content: "\2002"; font-style: normal; }

/* ---- the colophon: no rule above it but its own ornament ---- */
.key-sec.key-sec--colophon {
    border-top: 0; padding-top: 0; margin-top: calc(var(--rule) * 2); text-align: center;
}
/* a page that continues the signs starts on its first rule, not below a
   gap meant for the group above it */
.book-ref > .key-sec:first-child > .key-sub:first-child { margin-top: 0; }
/* the outer margins wider than the gutter's, as a bound book's are — and
   the turned left leaf is foreshortened toward its foot, which ate the
   narrow one */
#key .book-page--left  { padding-left: 1.7rem; }
#key .book-page--right { padding-right: 1.7rem; }
/* the colophon on a page of its own stands low on it, where a printer
   sets one: a third of the way up from the foot */
.book-ref > .key-sec--colophon:first-child { margin-top: calc(var(--rule) * 13); }
.key-colophon {
    margin: 0 auto; max-width: 22rem; padding: 0 .4rem;
    font: italic 400 .7rem/var(--rule) 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink-3); text-wrap: balance;
}

/* =====================================================================
   THE JOURNAL — the surveyor's second book.

   A field book holds measurements; a journal holds what was written up
   afterwards. So the essays are not a panel of web chrome laid over the
   map: they are a second book on the same desk, in a darker cloth, at the
   sheet's SOUTH-EAST corner — the mirror of the field book at the
   south-west — and opening one shuts the other, because a desk this size
   only has room for one open book.
   ===================================================================== */
.book--journal { left: auto; right: 1.6rem; }
.book--journal {
    /* the spread. The measure is the thing being set here: about sixty
       characters to a page at seventeen point, which is a book, and two of
       them side by side is the width the object has to be. */
    /* A 170:250 mm leaf; the spread is 1.36 times as wide as it is tall. */
    --jh: min(82dvh, 46rem, calc((100vw - 3rem) / 1.36));
    --jw: calc(var(--jh) * 1.36);
    --jm-out: 2.2rem;   /* the outer margin of a leaf */
    --jm-in:  1.8rem;   /* the inner margin, against the gutter */
    --jm-top: 3rem;     /* the head, where the running head is set */
    --jm-bot: 2.4rem;   /* the foot, where the folio is set */
}
/* the mirror of the field book: the dark morocco, the spine to the east
   and the fore-edge to the west are all drawn in the scene (objects.js).
   What is left here is the corner the label is set against. */
.book--journal .book-face { border-radius: 4px 2px 2px 4px; }
.book--journal .book-label {
    left: .55rem; right: 1.45rem;
    background: #e9dcbd; border-color: rgba(38,22,16,.55);
}
.book--journal .book-label-b { color: #6d5a44; }

/* --- opened: a two-page spread, anchored at the closed book's south-east
       corner, opening up and to the WEST over the sheet --- */
.journal-body {
    left: auto; right: 0; bottom: 0;
    width: var(--jw);
    padding: 11px;
    background:
        repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 1px, rgba(0,0,0,.05) 1px 2px),
        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='120'><filter id='l'><feTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' seed='5'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .34 0 0 0 -.16'/></filter><rect width='180' height='120' filter='url(%23l)'/></svg>"),
        linear-gradient(120deg, #533226 0%, #3a2119 55%, #26130e 100%);
}
.book--journal[data-open="true"] .journal-body {
    max-height: calc(var(--jh) + 24px); opacity: 1; transform: none;
}

/* LAID PAPER, NOT RULED. This is prose, not a column of figures: the leaf
   carries the fine laid lines and the wider chain lines of a mould-made
   sheet, and nothing else. */
.journal-inner {
    display: block; height: var(--jh); max-height: none; overflow: hidden;
}
:root {   /* the leaf, and JL1's first page on a phone upright */
    --leaf:
        repeating-linear-gradient(90deg, rgba(120,96,60,.055) 0 1px, rgba(0,0,0,0) 1px 3px),
        repeating-linear-gradient(90deg, rgba(120,96,60,.10) 0 1px, rgba(0,0,0,0) 1px 27px),
        linear-gradient(90deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
        linear-gradient(270deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
        radial-gradient(120% 150% at 50% 0%, #f6efdc 0%, #efe7d1 60%, #e9dfc5 100%);
}
.journal-inner { background: var(--leaf); }
/* the gutter: deeper than the field book's, because the leaves are wider */
.journal-inner::before {
    content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 46px;
    transform: translateX(-50%); pointer-events: none; z-index: 2;
    background: linear-gradient(90deg, rgba(90,70,40,0) 0%, rgba(90,70,40,.16) 38%,
                rgba(60,44,22,.30) 50%, rgba(90,70,40,.16) 62%, rgba(90,70,40,0) 100%);
}
.journal-leaf {
    position: absolute; top: 0; bottom: 0; width: 50%;
}
.journal-leaf--left { left: 0; }
.journal-leaf--right { left: 50%; }

/* --- the running heads and the folios, set in the margins --- */
.journal-head {
    position: absolute; top: 1.05rem; margin: 0; height: 1rem;
    display: flex; align-items: baseline; gap: .55rem;
    font: 400 .68rem/1 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .17em; text-transform: uppercase; color: var(--ink-3);
    white-space: nowrap; overflow: hidden;
}
.journal-head--left { left: var(--jm-out); right: var(--jm-in); }
.journal-head--right {
    left: var(--jm-in); right: var(--jm-out);
    justify-content: flex-end;
    font-variant-numeric: lining-nums tabular-nums;
}
/* the numeral in the running head is the way back to the ground */
.journal-head-no {
    color: var(--accent); text-decoration: none;
    border-bottom: 1px solid rgba(150,50,42,.35);
    letter-spacing: .1em;
}
.journal-head-no:hover { border-bottom-color: var(--accent); }
.journal-head-title { overflow: hidden; text-overflow: ellipsis; }
/* an opening page carries no running head: the title is the head — but the
   way back to the contents is not a running head, it is the book's own
   navigation, and it is on every spread of every essay. */
.journal-inner[data-spread="0"] .journal-head-no,
.journal-inner[data-spread="0"] .journal-head-title { visibility: hidden; }
.journal-head-contents {
    margin-left: auto; color: var(--accent); text-decoration: none;
    letter-spacing: .17em; white-space: nowrap;
    border-bottom: 1px solid rgba(150,50,42,.35);
}
.journal-head-contents:hover { border-bottom-color: var(--accent); }
.journal-inner[data-mode="contents"] .journal-head-contents { display: none; }

/* ---------------------------------------------------------------------
   THE CONTENTS: the Journal's first spread.
   A list of marks set as a book sets a table of contents — the numeral in
   the second ink out in the margin, the title on the line, the date and a
   line of the standfirst under it — and not as a web page sets a list of
   links. Nothing here is a button. */
.journal-contents { list-style: none; margin: .2em 0 0; padding: 0; }
.journal-contents li { break-inside: avoid; page-break-inside: avoid; }
.journal-flow .jc-entry {
    position: relative; display: block; color: inherit;
    text-decoration: none; -webkit-text-decoration: none;
    padding: .34em .35em .38em 2.5em; margin: 0 -.35em;
    border-top: 1px solid rgba(46,38,28,.16);
    text-indent: 0; text-align: left;
}
.journal-contents li:last-child .jc-entry { border-bottom: 1px solid rgba(46,38,28,.16); }
.jc-entry:hover, .jc-entry:focus-visible { background: rgba(150,50,42,.09); outline: none; }
.jc-entry.is-open { background: rgba(150,50,42,.15); }
.jc-no {
    position: absolute; left: .25em; top: .5em; width: 2em;
    font: 400 .72rem/1 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .06em; color: var(--accent); text-align: right;
}
.jc-title {
    display: block;
    font: 400 1.02em/1.22 'Sorts Mill Goudy', Georgia, serif; color: var(--ink);
}
.jc-date {
    display: block; margin-top: .12em;
    font: 400 .62rem/1.3 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
    font-variant-numeric: lining-nums tabular-nums;
}
.jc-ex {
    display: block; margin-top: .04em;
    font: italic 400 .82em/1.35 Petrona, Georgia, serif; color: var(--ink-2);
    /* AU7: a printed contents page runs the standfirst on to a second line;
       it does not cut it with an ellipsis. The entry stays whole on its leaf
       (li: break-inside avoid). */
    hyphens: manual; -webkit-hyphens: manual;
}
.journal-opening--contents { margin-bottom: .7em; }
/* PJ1: the phone's leaf is a small one, and its contents held one or two
   marks a page with the foot of the page empty. The standfirst is set a
   size down and the rows a little closer, so a leaf holds two or three
   whole marks. */
html.phone .jc-ex { font-size: .74em; line-height: 1.3; }
html.phone .journal-flow .jc-entry { padding-top: .28em; padding-bottom: .3em; }
html.phone .jc-no { top: .44em; }
html.phone .journal-opening--contents { margin-bottom: .45em; }

.journal-folio {
    position: absolute; bottom: .85rem; margin: 0; height: 1rem;
    left: var(--jm-out); right: var(--jm-out);
    text-align: center;
    font: 400 .66rem/1 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .12em; color: var(--ink-3);
    font-variant-numeric: lining-nums tabular-nums;
}

/* --- the text block: one flow, cut into leaves by columns --- */
.journal-clip {
    position: absolute; left: var(--jm-out); right: var(--jm-out);
    top: var(--jm-top); bottom: var(--jm-bot);
    overflow: hidden; z-index: 1;
    container-type: inline-size;
}
/* AU8 (2026-10-02): THE MEASURE IS HELD, NOT THE TYPE SIZE. The leaf is
   narrower in a smaller window (a 356 px column at 1024x768 against 427 at
   2194x1234), and 17.2 px type justified across 43 characters opened wide
   rivers between the words (the audit's A16). Hyphenation was already on;
   what was short was the line. So the size is the column over 24.5 ems
   (about 52 characters, the line Tommy's own window sets), capped at the
   17.2 px it was and floored at 14.5. At 2194 wide nothing moves. */
.journal-flow {
    height: 100%;
    column-count: 2; column-gap: calc(var(--jm-in) * 2); column-fill: auto;
    font-family: Petrona, Georgia, serif;
    font-size: clamp(14.5px, calc((100cqw - var(--jm-in) * 2) / 2 / 24.5), 17.2px);
    line-height: 1.62; color: var(--ink);
    text-align: justify; hyphens: auto; -webkit-hyphens: auto;
}

/* PARAGRAPH INDENTS, NOT GAPS. A book does not put a blank line between
   paragraphs; it indents the second and every one after it. */
.journal-flow > * { margin: 0; }
.journal-flow > p + p { text-indent: 1.3em; }
.journal-flow h2, .journal-flow h3 {
    font-family: 'Sorts Mill Goudy', Georgia, serif; font-weight: 400;
    text-align: left; text-indent: 0;
    margin: 1.5em 0 .3em; line-height: 1.24;
    break-after: avoid; page-break-after: avoid;
}
.journal-flow h2 { font-size: 1.3em; }
.journal-flow h3 { font-size: 1.08em; letter-spacing: .01em; }
.journal-flow h2 + p, .journal-flow h3 + p { text-indent: 0; }
.journal-flow strong { font-weight: 600; }
.journal-flow em { font-style: italic; }
/* red ink, underlined by hand */
.journal-flow a {
    color: var(--accent); text-decoration-line: underline;
    text-decoration-style: wavy; text-decoration-thickness: .5px;
    text-underline-offset: 3px; text-decoration-color: rgba(150,50,42,.6);
}
.journal-flow a:hover { text-decoration-color: var(--accent); }
.journal-flow blockquote {
    margin: .9em 0 .9em 1.4em; padding-left: 1em; text-indent: 0;
    border-left: 1px solid rgba(150,50,42,.5);
    font-style: italic; color: var(--ink-2); text-align: left;
    break-inside: avoid;
}
.journal-flow ul, .journal-flow ol { padding-left: 1.5em; text-align: left; }
.journal-flow li + li { margin-top: .25em; }
.journal-flow hr {
    border: 0; width: 30%; margin: 1.1em auto;
    border-top: 1px solid rgba(46,38,28,.3);
}

/* THE OPENING OF AN ESSAY: title, numeral, date and the bench mark itself. */
.journal-opening { margin: 0 0 1.1em; text-align: left; break-inside: avoid; }
.journal-opening-ref {
    display: flex; align-items: center; gap: .5rem; margin: 0 0 .5rem;
    font: 400 .72rem/1 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .18em; text-transform: uppercase; color: var(--accent);
}
.journal-opening-ref svg { width: 26px; height: 13px; overflow: visible; }
.journal-opening-title {
    margin: 0 0 .3em;
    font: 400 1.72em/1.14 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: -.004em; color: var(--ink);
}
.journal-opening-date {
    margin: 0; padding-bottom: .5em;
    border-bottom: 3px double rgba(46,38,28,.35);
    font: 400 .74rem/1.4 'Sorts Mill Goudy', Georgia, serif;
    letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
    font-variant-numeric: lining-nums tabular-nums;
}

/* A CODE BLOCK IS A TYPEWRITTEN SLIP PASTED IN.
   Nothing else on this desk is set in a monospace. The slip is a paler
   paper than the leaf, out of square by a degree, with a strip of gummed
   tape across the head of it. */
.journal-slip {
    position: relative; margin: 1.5em .2em 1.6em; padding: .95em 1em .85em;
    text-indent: 0; text-align: left;
    background: #fbf7ec;
    border: 1px solid rgba(46,38,28,.20);
    box-shadow: 0 3px 9px rgba(40,28,14,.22);
    transform: rotate(-.9deg);
    break-inside: avoid; page-break-inside: avoid;
}
.journal-slip:nth-of-type(even) { transform: rotate(.8deg); }
.journal-slip pre, .journal-slip code {
    margin: 0; font-family: 'Special Elite', 'Courier Prime', Courier, monospace;
    font-size: .78em; line-height: 1.55; color: #3b3128;
    white-space: pre-wrap; word-break: break-word;
}
/* THE TAPE IS IN THE FLOW, NOT ON TOP OF IT. An absolutely positioned
   child of a block that a column break moves gets left behind by the
   fragment it belonged to; a block with a negative top margin does not. */
.journal-tape {
    display: block; width: 6.6rem; height: 1.35rem;
    margin: -1.75rem auto .5rem;
    transform: rotate(1.4deg);
    background: linear-gradient(180deg, rgba(228,214,175,.86), rgba(208,192,151,.9));
    box-shadow: 0 1px 3px rgba(40,28,14,.24);
    border-left: 1px dashed rgba(150,132,96,.75);
    border-right: 1px dashed rgba(150,132,96,.75);
}
/* the only monospace on the desk, inline as well as in the slips */
.journal-flow code {
    font-family: 'Special Elite', 'Courier Prime', Courier, monospace;
    font-size: .8em;
}

/* A PHOTOGRAPH IS PASTED IN, WITH CORNERS. */
.journal-photo {
    position: relative; margin: 1.3em .35em 1.4em; padding: 0;
    text-indent: 0; break-inside: avoid; page-break-inside: avoid;
}
/* the corners are grid cells laid over the photograph's own corners, for
   the same reason the tape is in the flow: absolutely positioned children
   do not travel with the fragment a column break moves. */
.journal-plate {
    display: grid;
    grid-template-columns: 17px 1fr 17px;
    grid-template-rows: 17px 1fr 17px;
}
.journal-photo img { grid-area: 1 / 1 / -1 / -1; }
.journal-photo img {
    display: block; width: 100%; height: auto;
    box-shadow: 0 3px 10px rgba(40,28,14,.3);
}
.journal-photo figcaption {
    margin-top: .45em;
    font: italic 400 .76rem/1.4 'Sorts Mill Goudy', Georgia, serif;
    color: var(--ink-3); text-align: center;
}
.journal-corner {
    width: 17px; height: 17px; pointer-events: none; align-self: stretch;
    background: linear-gradient(135deg, rgba(56,42,26,.94) 0 52%, rgba(0,0,0,0) 52%);
}
.journal-corner--tl { grid-area: 1 / 1; }
.journal-corner--tr { grid-area: 1 / 3; transform: rotate(90deg); }
.journal-corner--br { grid-area: 3 / 3; transform: rotate(180deg); }
.journal-corner--bl { grid-area: 3 / 1; transform: rotate(270deg); }

/* --- turning: the corner of the leaf, the outer margin, or the arrows --- */
.book--journal .book-corner { width: 2.9rem; height: 2.9rem; z-index: 3; }
.journal-margin {
    position: absolute; top: var(--jm-top); bottom: 3rem;
    width: var(--jm-out); padding: 0; border: 0; background: none;
    cursor: pointer; z-index: 2;
}
.journal-margin--prev { left: 0; }
.journal-margin--next { right: 0; }
.journal-margin[hidden] { display: none; }
.journal-margin:hover { background: rgba(150,50,42,.05); }

/* =====================================================================
   C: THE LEAF TURNS (2026-09-20). books.js runTurn puts three things
   into the inner for 450 ms: a `stay` over the page the leaf will land
   on (what it looked like), a `cast` over the page being uncovered, and
   the `leaf-turn` itself — a page-sized box hinged at the spine with a
   front and a back, turned in the inner's own perspective (set above,
   with the hinge's). The faces are clones of the pages with a paper of
   their own, because they float over the inner's. The book's body and
   inner let the leaf reach past their box while it stands, as a page
   lifted towards the eye does. The flow's own translate is set without
   a transition: the slide it used to make is gone (Tommy: "slide is
   rubbish").
   ===================================================================== */
.book[data-turning] .book-body, .book[data-turning] .book-inner { overflow: visible; }
.leaf-stay, .leaf-turn, .leaf-cast { position: absolute; top: 0; bottom: 0; width: 50%; pointer-events: none; }
.leaf-stay { z-index: 4; overflow: hidden; }
.leaf-stay--left, .leaf-cast--left { left: 0; }
.leaf-stay--right, .leaf-cast--right { left: 50%; }
/* the leaf's cast on the page it is lifting off, from the spine */
.leaf-cast { z-index: 5; opacity: 0; }
.leaf-cast--right { background: linear-gradient(90deg, rgba(40,28,14,.40), rgba(40,28,14,.12) 30%, rgba(40,28,14,0) 62%); }
.leaf-cast--left  { background: linear-gradient(270deg, rgba(40,28,14,.40), rgba(40,28,14,.12) 30%, rgba(40,28,14,0) 62%); }
.leaf-turn { z-index: 6; transform-style: preserve-3d; will-change: transform; }
.leaf-turn--fwd  { left: 50%; transform-origin: 0 50%; }
.leaf-turn--back { left: 0; transform-origin: 100% 50%; }
.leaf-turn-face {
    position: absolute; inset: 0; overflow: hidden;
    backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
.leaf-turn-back { transform: rotateY(180deg); }
/* the light across the leaf: dark at the spine as it stands, catching the
   light at the free edge; its weight is animated by books.js */
.leaf-turn-shade { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.leaf-turn--fwd .leaf-turn-front .leaf-turn-shade,
.leaf-turn--back .leaf-turn-back .leaf-turn-shade {
    background: linear-gradient(90deg, rgba(40,28,14,.36), rgba(40,28,14,.06) 45%, rgba(255,250,235,.22));
}
.leaf-turn--fwd .leaf-turn-back .leaf-turn-shade,
.leaf-turn--back .leaf-turn-front .leaf-turn-shade {
    background: linear-gradient(270deg, rgba(40,28,14,.36), rgba(40,28,14,.06) 45%, rgba(255,250,235,.22));
}
/* a face: the page as it stands, on a paper of its own, with the
   gutter's shade on its spine side */
.leaf-face { position: absolute; inset: 0; overflow: hidden; background: var(--paper); }
.leaf-face > .book-page { height: 100%; box-sizing: border-box; }
.leaf-face--left  { box-shadow: inset -14px 0 14px -14px rgba(60,44,22,.30); }
.leaf-face--right { box-shadow: inset 14px 0 14px -14px rgba(60,44,22,.30); }
.book--journal .leaf-face {
    background:
        repeating-linear-gradient(90deg, rgba(120,96,60,.055) 0 1px, rgba(0,0,0,0) 1px 3px),
        repeating-linear-gradient(90deg, rgba(120,96,60,.10) 0 1px, rgba(0,0,0,0) 1px 27px),
        linear-gradient(90deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
        linear-gradient(270deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
        radial-gradient(120% 150% at 50% 0%, #f6efdc 0%, #efe7d1 60%, #e9dfc5 100%);
    background-size: 200% 100%;
}
.book--journal .leaf-face--left  { background-position: 0 0; box-shadow: inset -23px 0 23px -23px rgba(60,44,22,.30); }
.book--journal .leaf-face--right { background-position: 100% 0; box-shadow: inset 23px 0 23px -23px rgba(60,44,22,.30); }
.leaf-face > .journal-leaf { left: 0; width: 100%; }
/* the flow, cloned at the spread's offset and laid where the real one
   lies relative to the inner, so the same columns break at the same
   words; the face cuts it to the leaf */
.leaf-flow-clip { position: absolute; top: var(--jm-top); bottom: var(--jm-bot); overflow: hidden; }
.leaf-flow-clip--left  { left: var(--jm-out); width: calc(200% - 2 * var(--jm-out)); }
.leaf-flow-clip--right { left: calc(var(--jm-out) - 100%); width: calc(200% - 2 * var(--jm-out)); }

/* --- the folded map: the journal is the second tab of the bottom sheet --- */
/* =====================================================================
   The essay in the document. With JS it is lifted into the inset; without
   JS it is the page, and it has to read properly on its own.
   ===================================================================== */
.essay-source {
    max-width: 44rem; margin: 0 auto; padding: 3rem 1.4rem 5rem;
    background: var(--paper); color: var(--ink);
    font-family: Petrona, Georgia, serif; font-size: 1.06rem; line-height: 1.72;
}
.essay-source h1 {
    font: 400 2.2rem/1.15 'Sorts Mill Goudy', Georgia, serif; margin: 0 0 .6rem;
}
.essay-source-ref {
    font: 400 .76rem/1 'Sorts Mill Goudy', Georgia, serif; letter-spacing: .16em;
    text-transform: uppercase; color: var(--ink-3); margin: 0 0 .8rem;
}
.essay-standfirst { font-size: 1.14rem; color: var(--ink-2); font-style: italic; }
.essay-source a { color: var(--accent); }
.js .essay-source { display: none; }
.no-js .canvas, .no-js .book { display: none; }
.no-js body { overflow: auto; }
/* the plain page (index.njk #plain): no WebGL2, or a touch-first device */
.js.nogl .essay-source { display: block; }
.nogl .canvas, .nogl .book { display: none; }
.nogl body { overflow: auto; }
/* PH2 (2026-09-28): THE PLAIN PAGE ON A PHONE. Tommy, before sending the
   site to friends: "need it to be impressive and work". Found on an
   emulated iPhone: an essay's picture kept its own width (The Step Down's
   is 1,526 px) and pushed the page sideways; long code or tables would
   too. And the list of essays was numbered twice, the list's figures
   beside the essays' own numerals. */
.essay-source img { max-width: 100%; height: auto; }
.essay-source pre, .essay-source table { max-width: 100%; overflow-x: auto; }
.essay-source.plain ol { list-style: none; padding-left: 0; }

/* =====================================================================
   Transposed sheet: time runs down the page
   ===================================================================== */
/* A portrait reader frames ONE physical leaf. It does not squeeze a spread
   to half-width, and the current page number remains the current leaf when
   the orientation changes (books.js repaginates by leaf index). */
/* ==== L: THE PANEL IS THE BOOK FOR THE WHOLE MOTION =====================
   Tommy: "currently the book opens up to 2 blank pages and then our actual
   book overlay pops over the top with the book underneath disappearing.
   and then the inverse when we close it. it's cool but doesn't quite add
   up. either the writing needs to be in the opened book or we do something
   different?" — and the answer chosen: ONE object, no substitution, the
   writing on the pages from the first frame of the open to the last frame
   of the close.

   So the GL spread with its blank leaves is not drawn any more. What the
   engine keeps is the SHUT BLOCK (desk-books.js drawBooks, bookMesh): it is
   carried to the reading place and stays there under the near page, the
   block of leaves that an open book has under one side. Everything that
   TURNS is this panel: the far page sits in a hinge (`.book-hinge`) that
   rotates about the gutter, its back the cover's outside (the cloth, the
   spine, the same paper label the shut cover wears) and its front the far
   page with its writing. At --hinge 180deg (the key; -180deg for the
   journal, whose spine is to the east) the leaf lies on the block and IS
   the shut cover; at 0deg it lies flat on the far side, page up. The near
   page (`.book-block`, or the journal's west leaf) is under it the whole
   time and is revealed as it rises. The angle is written every frame by
   desk-books.js (liftLabels) off the same eased channel that carries the
   block — no css transition, so the carry and the hinge keep the one
   clock they have.

   THE HAND-OVER FROM CLOTH TO CLOTH. At the first frame the block's top
   face is GL cloth, lit by the room's sun, and the panel's cover is a flat
   gradient of the same three colours: a swap between the two on one frame
   is the substitution Tommy is describing, in miniature. So the panel's
   paint comes in with the leaf: --leaf-on runs 0 to 1 over the first eight
   per cent of the hinge (about 15 degrees), and until then the leaf's
   cloth and the near page are clear, and what shows through them is the
   GL block they lie on, exactly where they lie. The label and the spine
   are opaque throughout — they are the same label, in the same place.

   The rim of cloth round the pages (the body's padding, 10/11 px) stays,
   but the cloth is painted PER HALF now (`::before` on each half, reaching
   out into the padding), so that with the leaf folded over the block the
   far half of the body is nothing at all, and the leaf's back is exactly
   one cover: the cell and its rim are half the body to the pixel, and half
   the body at carry 0 is the shut cover's own rectangle. The gutter's
   shadow, the paper and the block's edge shadow move on to the pages for
   the same reason. Nothing here touches the phone's bars (<= 820 px) or the
   reduced-motion path: both settle the hinge synchronously and never see a
   frame of it.
   ===================================================================== */
.book { --cloth: repeating-linear-gradient(0deg, rgba(255,255,255,.04) 0 1px, rgba(0,0,0,.05) 1px 2px),
                 repeating-linear-gradient(90deg, rgba(255,255,255,.04) 0 1px, rgba(0,0,0,.05) 1px 2px),
                 linear-gradient(120deg, #4d5c4a 0%, #3d4a3c 55%, #2f3a2f 100%);
        --paper: #f3ecd8; --rim-x: 11px; --rim-y: 10px; }
.book--journal { --cloth: repeating-linear-gradient(0deg, rgba(255,255,255,.03) 0 1px, rgba(0,0,0,.05) 1px 2px),
                 url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='120'><filter id='l'><feTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' seed='5'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .34 0 0 0 -.16'/></filter><rect width='180' height='120' filter='url(%23l)'/></svg>"),
                 linear-gradient(120deg, #533226 0%, #3a2119 55%, #26130e 100%);
                 --rim-x: 11px; --rim-y: 11px; }
/* PO1: none of this is a phone's. A phone held on its side is wider than
   821 px and its book still lies one leaf (in its own frame, below), so
   every rule here is :where(html:not(.phone)), which leaves a desk's
   specificity exactly as it was. */
@media (min-width: 821px) {
    /* the panel is on the desk from the first frame of the open to the last
       of the close: shown by the STAGE, not by data-open (which books.js
       turns off the instant a close begins), and with none of the old
       fade or the .42 s wait for the physical book to land. `reading` is
       the reader exactly as it was (the body's own cloth, the inner's
       paper, the inner scrolling a long page); every OTHER stage is the
       motion, where the halves paint themselves and the inner is opened
       up so a leaf standing on its gutter can reach past the box. */
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]) .book-body {
        opacity: 1; transition: none; transform-origin: 0 0; max-height: min(70dvh, 37rem);
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]) .journal-body { max-height: calc(var(--jh) + 24px); }
    :where(html:not(.phone)) .book[data-book-motion="closed"] .book-body { transition: none; }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-body {
        background: none; box-shadow: none; overflow: visible;
    }
    :where(html:not(.phone)) .book[data-book-motion="retracting"], :where(html:not(.phone)) .book[data-book-motion="closing"] { z-index: 60; }
    /* the label is on the leaf's back until the book is home, so the cover's
       own label goes and returns on the frame, not on R29's fade */
    :where(html:not(.phone)) .book[data-book-motion] .book-face { transition: none; }

    /* the field book: two halves in the grid, the block east, the leaf west.
       In motion the inner is held at its own height (desk-books.js writes
       it, the same number aspect-ratio gives) and its row is that height,
       so a page longer than the leaf is cut by its half instead of
       scrolled by the inner — the same pixels, scrolled to the top. */
    /* NO SCROLLBAR IN A FIELD BOOK — a taste call, named in the report: the
       inner's classic scrollbar took 15 px off the two pages at `reading`
       and nothing off them in motion, a 7.5 px jump at the gutter the
       moment the leaf landed. The page still scrolls (wheel, touch, keys);
       the bar is not drawn. */
    :where(html:not(.phone)) .book-inner { perspective: 1400px; perspective-origin: 50% 50%; scrollbar-width: none; }
    :where(html:not(.phone)) .book-inner::-webkit-scrollbar { display: none; }
    :where(html:not(.phone)) .book-half { position: relative; display: grid; grid-row: 1; min-width: 0; }
    :where(html:not(.phone)) .book-block { grid-column: 2; }
    :where(html:not(.phone)) .book-hinge { grid-column: 1; }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-inner {
        overflow: visible; background: none; box-shadow: none; grid-template-rows: minmax(0, 1fr);
    }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-block,
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge,
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge-front {
        grid-template-rows: minmax(0, 1fr);
    }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-block,
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge-front { overflow: hidden; }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-block { opacity: var(--leaf-on, 1); }
    /* ND2 (2026-10-06): AT NIGHT THE LAMP'S MULTIPLY LIES ON PAPER, NOT ON
       NOTHING. LK1's dim (.book-body::after) multiplies the body's whole box,
       and while a book moves that box is see-through: background none, the
       leaves at --leaf-on, the far half empty until the leaf arrives. The
       body is its own stacking context (the plate's transform), so the
       multiply had nothing under it and painted its own cream gradient: a
       blank double page carried across the desk, and the far half blank
       while the cover turned. Tommy, after dark: "weird thing in prod".
       In motion the dim is cut to the two things that ARE paper: the
       block's half, as its leaf comes in, and the turning leaf's front.
       Each takes its half of the same gradient (200% wide, from its own
       side), so on landing the whole-body layer is the same picture. */
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-body::after {
        left: 50%; background-size: 200% 100%; background-position: 100% 0;
        opacity: calc(var(--dim, 0) * var(--leaf-on, 1));
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-body::after {
        left: 0; right: 50%; background-position: 0 0;
    }
    /* and the turning leaf's front, with its half of it. Isolated: in the
       hinge's 3d a multiply with no group of its own has no backdrop it can
       reach, and painted its gradient over the words (filmed) */
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge-front { isolation: isolate; }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge-front::after {
        content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; mix-blend-mode: multiply;
        background: linear-gradient(140deg, #fff6e4 0%, #e6d6bc 35%, #a8987f 80%, #8a7c68 100%);
        background-size: 200% 100%; background-position: 0 0;
        opacity: var(--dim, 0);
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-hinge-front::after { background-position: 100% 0; }
    /* the block's rim: the inner's gutter pseudo, re-cut as the east half's
       cloth while the book is in motion (the gutter is on the pages then) */
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-inner::before {
        left: 50%; right: calc(-1 * var(--rim-x)); top: calc(-1 * var(--rim-y)); bottom: calc(-1 * var(--rim-y));
        width: auto; transform: none; z-index: -1; border-radius: 0 3px 3px 0;
        background: var(--cloth); opacity: var(--leaf-on, 1);
        box-shadow: inset 0 0 0 1px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.36);
    }
    /* 2026-09-20: THE BLOCK'S HALF THROWS ITS CAST TOO. The hinged leaf
       carried the sun's cast through the motion (`.book-hinge::after`,
       below) and the block's half carried none: its GL shadow is thrown
       on the oak, and an open book lies on the paper, so on the paper the
       block cast nothing until `reading` put the body's whole-panel shadow
       on as a step (desk-books.js BOOK_FRAME_MS says the rest). This is
       the block's half of that same shadow — the same box the rim paints,
       the same sun terms — in with the carry (`--carry-on`, written by
       liftHinge) so that at `reading` the body's one shadow replaces two
       halves already lying exactly where it lies. */
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-inner::after {
        content: ""; position: absolute; pointer-events: none;
        left: 50%; right: calc(-1 * var(--rim-x)); top: calc(-1 * var(--rim-y)); bottom: calc(-1 * var(--rim-y));
        z-index: -2; border-radius: 0 3px 3px 0; opacity: var(--carry-on, 1);
        box-shadow:
            calc(var(--sun-dx) * var(--sun-len) * var(--sun-cast) * 20px)
            calc(var(--sun-dy) * var(--sun-len) * var(--sun-cast) * 20px)
            calc(52px * var(--sun-soft)) rgba(0,0,0, calc(.62 * var(--sun-dark) * var(--sun-cast)));
    }
    /* the journal's block is its WEST half */
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-inner::after {
        left: calc(-1 * var(--rim-x)); right: 50%; border-radius: 3px 0 0 3px;
    }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-page {
        background-color: var(--paper); box-shadow: 0 1px 4px rgba(0,0,0,.4);
    }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-page--left {
        background-image:
            linear-gradient(90deg, rgba(90,70,40,.22) 0 3px, rgba(0,0,0,0) 14px),
            linear-gradient(270deg, rgba(60,44,22,.30) 0, rgba(90,70,40,.20) 2px, rgba(90,70,40,0) 13px),
            repeating-linear-gradient(180deg, rgba(70,96,120,0) 0 calc(var(--rule) - 1px), rgba(70,96,120,.16) calc(var(--rule) - 1px) var(--rule));
        background-size: 100% 100%, 100% 100%, 100% var(--rule);
        background-position: 0 0, 0 0, 0 calc(var(--rule) + var(--rule-drop));
    }
    :where(html:not(.phone)) .book[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .book-page--right {
        background-image:
            linear-gradient(270deg, rgba(90,70,40,.22) 0 3px, rgba(0,0,0,0) 14px),
            linear-gradient(90deg, rgba(60,44,22,.30) 0, rgba(90,70,40,.20) 2px, rgba(90,70,40,0) 13px),
            repeating-linear-gradient(180deg, rgba(70,96,120,0) 0 calc(var(--rule) - 1px), rgba(70,96,120,.16) calc(var(--rule) - 1px) var(--rule));
        background-size: 100% 100%, 100% 100%, 100% var(--rule);
        background-position: 0 0, 0 0, 0 calc(var(--rule) + var(--rule-drop));
    }

    /* THE HINGE. The wrapper turns about its gutter edge; its front (the
       page, cut to the leaf), its rim (::before) and its back each hide
       their reverse, so the cover is seen until 90 degrees and the page
       after. Its cast (::after — the sun's, as the body's was) comes with
       the hinge, so that a leaf lying shut on the block throws nothing the
       block's own GL shadow does not already throw. Landed, the wrapper is
       a plain cell and its faces are gone: the reader is the reader. */
    :where(html:not(.phone)) .book-hinge {
        transform-style: preserve-3d; transform-origin: 100% 50%;
        transform: rotateY(var(--hinge, 0deg)); will-change: transform;
    }
    :where(html:not(.phone)) .book--journal .book-hinge { transform-origin: 0 50%; }
    :where(html:not(.phone)) .book-hinge-front, :where(html:not(.phone)) .book-hinge-back, :where(html:not(.phone)) .book-hinge::before, :where(html:not(.phone)) .book-hinge::after {
        backface-visibility: hidden; -webkit-backface-visibility: hidden;
    }
    :where(html:not(.phone)) .book-hinge-front { position: relative; display: grid; min-width: 0; }
    :where(html:not(.phone)) .book-hinge::before, :where(html:not(.phone)) .book-hinge::after {
        content: ""; position: absolute; pointer-events: none;
        top: calc(-1 * var(--rim-y)); bottom: calc(-1 * var(--rim-y)); left: calc(-1 * var(--rim-x)); right: 0;
    }
    :where(html:not(.phone)) .book-hinge::before {
        z-index: -1; background: var(--cloth); border-radius: 3px 0 0 3px;
        box-shadow: inset 0 0 0 1px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.36);
    }
    :where(html:not(.phone)) .book-hinge::after {
        z-index: -2; opacity: var(--hinge-on, 1);
        box-shadow:
            calc(var(--sun-dx) * var(--sun-len) * var(--sun-cast) * 20px)
            calc(var(--sun-dy) * var(--sun-len) * var(--sun-cast) * 20px)
            calc(52px * var(--sun-soft)) rgba(0,0,0, calc(.62 * var(--sun-dark) * var(--sun-cast)));
    }
    :where(html:not(.phone)) .book--journal .book-hinge::before, :where(html:not(.phone)) .book--journal .book-hinge::after { left: 0; right: calc(-1 * var(--rim-x)); }
    :where(html:not(.phone)) .book--journal .book-hinge::before { border-radius: 0 3px 3px 0; }
    /* BS1 (2026-10-01): THE LEAF'S CAST STOPS AT THE SPINE. It is a box-shadow
       offset towards the light, so with the leaf landed it spilled over the
       spine on to the other page as a dark slanted wedge, and `reading` (one
       shadow behind the whole body) took it away on one frame (Tommy: "the
       shadow changes once it's finished opening"). A page lying in a book
       throws nothing on its neighbour; outward of the spine the cast is the
       body's own, and it is kept. Only the neighbour's RECTANGLE is cut out
       (the leaf's own height, rim and all): above and below the book the
       cast runs on across the spine, or the two halves' shadows on the
       paper would meet in a light seam. The key's spine is the leaf's east
       edge, the journal's its west. */
    /* and the leaf's rim keeps its outline off the spine: in motion its
       inset hairline stood down the gutter and through the head's edge, and
       the landed body has a soft gutter there, not a line. */
    :where(html:not(.phone)) .book-hinge::before { clip-path: inset(-8px 1px -8px -8px); }
    :where(html:not(.phone)) .book--journal .book-hinge::before { clip-path: inset(-8px -8px -8px 1px); }
    :where(html:not(.phone)) .book-hinge::after {
        clip-path: polygon(-160px -160px, calc(100% + 160px) -160px, calc(100% + 160px) 0, 100% 0,
                           100% 100%, calc(100% + 160px) 100%, calc(100% + 160px) calc(100% + 160px),
                           -160px calc(100% + 160px));
    }
    :where(html:not(.phone)) .book--journal .book-hinge::after {
        clip-path: polygon(-160px -160px, calc(100% + 160px) -160px, calc(100% + 160px) calc(100% + 160px),
                           -160px calc(100% + 160px), -160px 100%, 0 100%, 0 0, -160px 0);
    }
    :where(html:not(.phone)) .book[data-book-motion="reading"] .book-hinge::before,
    :where(html:not(.phone)) .book[data-book-motion="reading"] .book-hinge::after,
    :where(html:not(.phone)) .book[data-book-motion="reading"] .book-hinge-back { display: none; }
    /* the back: the cover's outside. The cloth comes in with --leaf-on; the
       label is the shut cover's own (`.book-label`, the same rules), set in
       a box the cover's own size and scaled up to the leaf's by
       desk-books.js (panelsAtPose writes --leaf-sx/--leaf-sy), so that at
       carry 0, where the plate scales the leaf back down to the cover, the
       label is the label at rest, to the pixel. */
    :where(html:not(.phone)) .book-hinge-back {
        position: absolute; top: calc(-1 * var(--rim-y)); bottom: calc(-1 * var(--rim-y));
        left: calc(-1 * var(--rim-x)); right: 0;
        transform: rotateY(180deg); border-radius: 2px 4px 4px 2px; overflow: hidden;
        pointer-events: none;
    }
    :where(html:not(.phone)) .book--journal .book-hinge-back { left: 0; right: calc(-1 * var(--rim-x)); border-radius: 4px 2px 2px 4px; }
    :where(html:not(.phone)) .book-hinge-back::before {
        content: ""; position: absolute; inset: 0; background: var(--cloth);
        opacity: var(--leaf-on, 1);
    }
    :where(html:not(.phone)) .book-hinge-cover {
        position: absolute; left: 0; top: 0; width: var(--bk-w, 150.4px); height: var(--bk-h, 102.4px);
        transform-origin: 0 0; transform: scale(var(--leaf-sx, 1), var(--leaf-sy, 1));
    }

    /* the journal: its leaves are absolute halves already; the hinge wraps
       the east one. Its writing is ONE two-column flow across both leaves
       (`.journal-flow`), which a rigid leaf cannot carry, so while the leaf
       is in the air its front shows a CLONE of that flow (desk-books.js,
       leafCloneSync) laid at the same width and the same paging offset —
       the same columns, so the same words — and the real flow is cut to
       its west column. Landed (`reading`), the clone goes, the paper goes
       back on to the inner, and the real east column shows through the
       leaf as it always did. */
    :where(html:not(.phone)) .journal-inner { perspective: 1400px; perspective-origin: 50% 50%; }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-inner {
        overflow: visible; background: none;
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-inner::before { display: none; }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-clip {
        clip-path: inset(0 50% 0 0); opacity: var(--leaf-on, 1);
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-leaf--left { opacity: var(--leaf-on, 1); }
    :where(html:not(.phone)) .book--journal .book-hinge { position: absolute; left: 50%; top: 0; bottom: 0; width: 50%; z-index: 3; display: block; }
    :where(html:not(.phone)) .book--journal[data-book-motion="reading"] .book-hinge { z-index: auto; }
    :where(html:not(.phone)) .book--journal .book-hinge-front { position: absolute; inset: 0; display: block; overflow: hidden; }
    :where(html:not(.phone)) .book--journal .book-hinge .journal-leaf--right { left: 0; width: 100%; }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-leaf {
        background:
            repeating-linear-gradient(90deg, rgba(120,96,60,.055) 0 1px, rgba(0,0,0,0) 1px 3px),
            repeating-linear-gradient(90deg, rgba(120,96,60,.10) 0 1px, rgba(0,0,0,0) 1px 27px),
            linear-gradient(90deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            linear-gradient(270deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            radial-gradient(120% 150% at 50% 0%, #f6efdc 0%, #efe7d1 60%, #e9dfc5 100%);
        background-size: 200% 100%;
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-leaf--left {
        /* GC1: the crease is the west half of the landed gutter
           (.journal-inner::before, 46px), not a shadow of its own, so the
           fold does not deepen the moment the book lands */
        background-image:
            linear-gradient(90deg, rgba(90,70,40,0) 0%, rgba(90,70,40,.16) 76%, rgba(60,44,22,.30) 100%),
            repeating-linear-gradient(90deg, rgba(120,96,60,.055) 0 1px, rgba(0,0,0,0) 1px 3px),
            repeating-linear-gradient(90deg, rgba(120,96,60,.10) 0 1px, rgba(0,0,0,0) 1px 27px),
            linear-gradient(90deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            linear-gradient(270deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            radial-gradient(120% 150% at 50% 0%, #f6efdc 0%, #efe7d1 60%, #e9dfc5 100%);
        background-size: 23px 100%, 200% 100%, 200% 100%, 200% 100%, 200% 100%, 200% 100%;
        background-repeat: no-repeat;
        background-position: 100% 0, 0 0, 0 0, 0 0, 0 0, 0 0;
    }
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-leaf--right {
        background-image:
            linear-gradient(270deg, rgba(90,70,40,0) 0%, rgba(90,70,40,.16) 76%, rgba(60,44,22,.30) 100%),
            repeating-linear-gradient(90deg, rgba(120,96,60,.055) 0 1px, rgba(0,0,0,0) 1px 3px),
            repeating-linear-gradient(90deg, rgba(120,96,60,.10) 0 1px, rgba(0,0,0,0) 1px 27px),
            linear-gradient(90deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            linear-gradient(270deg, rgba(90,70,40,.20) 0 3px, rgba(0,0,0,0) 16px),
            radial-gradient(120% 150% at 50% 0%, #f6efdc 0%, #efe7d1 60%, #e9dfc5 100%);
        background-size: 23px 100%, 200% 100%, 200% 100%, 200% 100%, 200% 100%, 200% 100%;
        background-repeat: no-repeat;
        background-position: 0 0, 100% 0, 100% 0, 100% 0, 100% 0, 100% 0;
    }
    /* the west leaf's rim, in motion: the inner's own gutter pseudo re-cut */
    :where(html:not(.phone)) .book--journal[data-book-motion]:not([data-book-motion="closed"]):not([data-book-motion="reading"]) .journal-inner::before {
        display: block; left: calc(-1 * var(--rim-x)); right: 50%; top: calc(-1 * var(--rim-y)); bottom: calc(-1 * var(--rim-y));
        width: auto; transform: none; z-index: -1; border-radius: 3px 0 0 3px;
        background: var(--cloth); opacity: var(--leaf-on, 1);
        box-shadow: inset 0 0 0 1px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.36);
    }
    :where(html:not(.phone)) .book-hinge-clip {
        position: absolute; left: calc(var(--jm-out) - 100%); width: calc(200% - 2 * var(--jm-out));
        top: var(--jm-top); bottom: var(--jm-bot); overflow: hidden;
    }
}
/* a portrait window shows one leaf (above): there is no far page to turn,
   so the block is not shown and the leaf lies flat */
@media (orientation: portrait), (max-width: 820px) {
    .book-block { display: none; }
    .book-hinge { grid-column: 1; transform: none !important; }
    .book-hinge-back { display: none; }
}

@media (orientation: portrait) {
    .book-body {
        width: min(30rem, calc(100vw - 3.2rem), calc((70dvh - 20px) * .666667));
    }
    .book-inner { grid-template-columns: 1fr; aspect-ratio: 2 / 3; }
    .book-inner::before, .book-page--right { display: none; }
    .book-page--left { border-right: 0; }
    .book--journal {
        --jh: min(82dvh, 46rem, calc((100vw - 3rem) / .68));
        --jw: calc(var(--jh) * .68);
    }
    .book--journal .journal-body { width: var(--jw); }
    .journal-inner { width: 100%; height: auto; aspect-ratio: .68; }
    .journal-inner::before { display: none; }
    .journal-leaf { width: 100%; left: 0; }
    .journal-leaf--right { pointer-events: none; }
    .journal-leaf--right .book-corner, .journal-leaf--right .journal-margin { pointer-events: auto; }
    .journal-head--right, #journal-folio-right { display: none; }
    .journal-head--left { right: var(--jm-out); }
    /* PJ1: one leaf on show, but the flow is still TWO columns, set twice
       the leaf wide, so the leaves run east as they do on the desk. A
       `column-count: 1` box is not a multicolumn box at all in WebKit (every
       browser on an iPhone): the flow ran straight down off the foot of the
       leaf, the end mark stayed in the first column, and the contents read
       "Page 1 of 1" with two marks on it and ten lost under the clip. */
    .journal-flow { column-count: 2; width: calc(200% + var(--jm-in) * 2); }
}

@media (max-width: 820px) {
    /* THE FOLDED MAP. No paper object and no border: the plate is the
       screen. The marginalia used to be a ruled DOM strip across the top
       (`.sheet-strip`, with the sheet's name and London's clock in it); it
       went with the old plate at THE SWITCH, and the engine budgets the
       780 x 1410 canvas for the whole window. */
    .canvas::after { opacity: .09; }


    /* the field book is a bottom sheet you pull up; its two pages become one */
    /* The folded map fills the screen, so the book cannot lie under it:
       on a phone it is a bottom sheet again, over the plate. */
    .book { left: 0; right: 0; bottom: 0; top: auto; z-index: 40; }
    .book[data-open="true"] { transform: none; }
    .book-cover { width: 100%; height: 2.9rem; }
    .book-face {
        height: 2.9rem; border-radius: 0;
        border-top: 1px solid rgba(0,0,0,.5);
    }
    .book-label {
        left: 50%; right: auto; top: 50%; transform: translate(-50%, -50%);
        display: flex; align-items: baseline; gap: .5rem;
        padding: .2rem .8rem .24rem; white-space: nowrap;
    }
    .book-label-rule { display: none; }
    .book-label-a { font-size: .82rem; }
    .book-label-b { margin-top: 0; font-size: .55rem; }
    .book-body {
        position: fixed; left: 50%; right: auto; bottom: 2.9rem;
        width: min(100vw, calc(66dvh * .666667));
        padding: 0; border-radius: 0; transform: translate(-50%, 14px);
    }
    .book-inner { grid-template-columns: 1fr; width: 100%; height: auto; aspect-ratio: 2 / 3; }
    .book-inner::before { display: none; }
    .book-page--left { border-right: 0; border-bottom: 1px solid rgba(120,96,60,.25); }
    .book[data-open="true"] .book-body { max-height: 66dvh; transition-delay: 0s; transform: translateX(-50%); }
    .book-page--right { display: none; }
    .book[data-open="true"] .book-face { transform: none; }
    .book-corner { width: 3.4rem; height: 3.4rem; }

    /* TWO TABS IN THE ONE STRIP. The desk is gone on a phone, so the two
       books lie side by side along the foot of the plate — Field Book to
       the west, Journal to the east — and either one pulls up over it.
       Opening one still shuts the other. */
    .book:not(.book--journal) { right: 50%; }
    .book--journal { left: 50%; right: 0; }
    /* BOTH TABS STAY IN THE STRIP. The body opens full width over the plate,
       but the two covers keep their halves of the foot, so the open book's
       own tab is what you press to shut it and the other one is still there
       to press instead. */
    .book[data-open="true"] .book-cover { pointer-events: auto; }
    .book[data-open="true"] .book-face { opacity: 1; transform: none; }
    .book--journal .book-face { border-radius: 0; }
    .book--journal .book-label { left: 50%; right: auto; }

    /* one leaf, not two: a phone gets a single page and turns it */
    .book--journal {
        --jh: min(68dvh, calc(100vw / .68)); --jw: calc(var(--jh) * .68);
        --jm-out: 1.1rem; --jm-in: 1.1rem; --jm-top: 2.1rem; --jm-bot: 2.6rem;
    }
    .book--journal .journal-body { position: fixed; left: 50%; right: auto; bottom: 2.9rem; width: var(--jw);
        padding: 0; border-radius: 0; }
    .book--journal[data-open="true"] .journal-body { max-height: 68dvh; transform: translateX(-50%); }
    .journal-inner { width: 100%; height: auto; aspect-ratio: .68; }
    .journal-inner::before { display: none; }
    /* the right leaf keeps its corner (which is how the page turns) but
       nothing else: on a phone there is one page, not a spread */
    .journal-leaf { width: 100%; left: 0; }
    .journal-leaf--right { pointer-events: none; }
    .journal-leaf--right .book-corner, .journal-leaf--right .journal-margin { pointer-events: auto; }
    .journal-head--right, #journal-folio-right { display: none; }
    .journal-head--left { right: var(--jm-out); }
    .journal-flow { column-count: 2; width: calc(200% + var(--jm-in) * 2);
        font-size: 16px; line-height: 1.58; }   /* PJ1: see the portrait rule */
    /* a photograph on one narrow leaf must not take the whole leaf */
    .journal-photo img { max-height: 30dvh; width: auto; max-width: 100%; margin: 0 auto; }
    .journal-opening-title { font-size: 1.5em; }
    .book--journal .book-corner { width: 3.2rem; height: 3.2rem; }
    /* PB1 (2026-10-02, his iPhone 393 wide): the text block stops above the
       3.2rem page-turn corners, so no line is set under them or the folio;
       and at ~30 characters a justified line opens rivers ("descent.  The
       loss  function"), so the one narrow leaf is set ragged right, still
       hyphenated, as a pocket book is. */
    .journal-clip { bottom: 3.5rem; }
    .journal-flow { text-align: left; }
}

/* ==== PO1 (2026-10-02): THE PHONE'S BOOKS ARE LAID IN THEIR OWN FRAME ====
   notes/phone-design.md. On a phone both books live in `.phone-books`
   (phone.js makes it; a desk never has it), and the frame is the glass
   upright, or (the phone on its side with a book out) the glass's height
   wide and its width high, turned a quarter onto the screen, so the book
   lies sideways for the turn. Everything the narrow window and the
   portrait window say about a book is said again here against the FRAME
   (a size container, so its units are cqw and cqh, not the window's),
   which is why a book is laid as the same leaf upright and sideways. The
   three blocks above stay as they were for a narrow desk window.
   TB1 (2026-10-04): at any width. A tablet is laid as a phone (base.njk),
   and a 12.9-inch iPad upright is 1024 wide, past this block's old 820 px
   gate: its Journal fell back to the desk's small book over the map. ==== */
@container books (min-width: 0px) {
    .book-block { display: none; }
    .book-hinge { grid-column: 1; transform: none !important; }
    .book-hinge-back { display: none; }

    .book-body {
        width: min(30rem, calc(100cqw - 3.2rem), calc((70cqh - 20px) * .666667));
    }
    .book-inner { grid-template-columns: 1fr; aspect-ratio: 2 / 3; }
    .book-inner::before, .book-page--right { display: none; }
    .book-page--left { border-right: 0; }
    .book--journal {
        --jh: min(82cqh, 46rem, calc((100cqw - 3rem) / .68));
        --jw: calc(var(--jh) * .68);
    }
    .book--journal .journal-body { width: var(--jw); }
    .journal-inner { width: 100%; height: auto; aspect-ratio: .68; }
    .journal-inner::before { display: none; }
    .journal-leaf { width: 100%; left: 0; }
    .journal-leaf--right { pointer-events: none; }
    .journal-leaf--right .book-corner, .journal-leaf--right .journal-margin { pointer-events: auto; }
    .journal-head--right, #journal-folio-right { display: none; }
    .journal-head--left { right: var(--jm-out); }
    /* PJ1: one leaf on show, but the flow is still TWO columns, set twice
       the leaf wide, so the leaves run east as they do on the desk. A
       `column-count: 1` box is not a multicolumn box at all in WebKit (every
       browser on an iPhone): the flow ran straight down off the foot of the
       leaf, the end mark stayed in the first column, and the contents read
       "Page 1 of 1" with two marks on it and ten lost under the clip. */
    .journal-flow { column-count: 2; width: calc(200% + var(--jm-in) * 2); }

    /* THE FOLDED MAP. No paper object and no border: the plate is the
       screen. The marginalia used to be a ruled DOM strip across the top
       (`.sheet-strip`, with the sheet's name and London's clock in it); it
       went with the old plate at THE SWITCH, and the engine budgets the
       780 x 1410 canvas for the whole window. */


    /* the field book is a bottom sheet you pull up; its two pages become one */
    /* The folded map fills the screen, so the book cannot lie under it:
       on a phone it is a bottom sheet again, over the plate. */
    .book { left: 0; right: 0; bottom: 0; top: auto; z-index: 40; }
    .book[data-open="true"] { transform: none; }
    .book-cover { width: 100%; height: 2.9rem; }
    .book-face {
        height: 2.9rem; border-radius: 0;
        border-top: 1px solid rgba(0,0,0,.5);
    }
    .book-label {
        left: 50%; right: auto; top: 50%; transform: translate(-50%, -50%);
        display: flex; align-items: baseline; gap: .5rem;
        padding: .2rem .8rem .24rem; white-space: nowrap;
    }
    .book-label-rule { display: none; }
    .book-label-a { font-size: .82rem; }
    .book-label-b { margin-top: 0; font-size: .55rem; }
    .book-body {
        position: fixed; left: 50%; right: auto; bottom: 2.9rem;
        width: min(100cqw, calc(66cqh * .666667));
        padding: 0; border-radius: 0; transform: translate(-50%, 14px);
    }
    .book-inner { grid-template-columns: 1fr; width: 100%; height: auto; aspect-ratio: 2 / 3; }
    .book-inner::before { display: none; }
    .book-page--left { border-right: 0; border-bottom: 1px solid rgba(120,96,60,.25); }
    .book[data-open="true"] .book-body { max-height: 66cqh; transition-delay: 0s; transform: translateX(-50%); }
    .book-page--right { display: none; }
    .book[data-open="true"] .book-face { transform: none; }
    .book-corner { width: 3.4rem; height: 3.4rem; }

    /* TWO TABS IN THE ONE STRIP. The desk is gone on a phone, so the two
       books lie side by side along the foot of the plate — Field Book to
       the west, Journal to the east — and either one pulls up over it.
       Opening one still shuts the other. */
    .book:not(.book--journal) { right: 50%; }
    .book--journal { left: 50%; right: 0; }
    /* BOTH TABS STAY IN THE STRIP. The body opens full width over the plate,
       but the two covers keep their halves of the foot, so the open book's
       own tab is what you press to shut it and the other one is still there
       to press instead. */
    .book[data-open="true"] .book-cover { pointer-events: auto; }
    .book[data-open="true"] .book-face { opacity: 1; transform: none; }
    .book--journal .book-face { border-radius: 0; }
    .book--journal .book-label { left: 50%; right: auto; }

    /* one leaf, not two: a phone gets a single page and turns it */
    .book--journal {
        --jh: min(68cqh, calc(100cqw / .68)); --jw: calc(var(--jh) * .68);
        --jm-out: 1.1rem; --jm-in: 1.1rem; --jm-top: 2.1rem; --jm-bot: 2.6rem;
    }
    .book--journal .journal-body { position: fixed; left: 50%; right: auto; bottom: 2.9rem; width: var(--jw);
        padding: 0; border-radius: 0; }
    .book--journal[data-open="true"] .journal-body { max-height: 68cqh; transform: translateX(-50%); }
    .journal-inner { width: 100%; height: auto; aspect-ratio: .68; }
    .journal-inner::before { display: none; }
    /* the right leaf keeps its corner (which is how the page turns) but
       nothing else: on a phone there is one page, not a spread */
    .journal-leaf { width: 100%; left: 0; }
    .journal-leaf--right { pointer-events: none; }
    .journal-leaf--right .book-corner, .journal-leaf--right .journal-margin { pointer-events: auto; }
    .journal-head--right, #journal-folio-right { display: none; }
    .journal-head--left { right: var(--jm-out); }
    .journal-flow { column-count: 2; width: calc(200% + var(--jm-in) * 2);
        font-size: 16px; line-height: 1.58; }   /* PJ1: see the portrait rule */
    /* TB1 (2026-10-04): a tablet's leaf is twice a phone's, and 16 px across
       it ran ~100 characters a line. The type grows with the leaf to hold
       about 70 (the column over 32 ems, the clip being the container), from
       the phone's 16 px, which every phone keeps, to 20 px */
    .journal-flow { font-size: clamp(16px, calc(100cqw / 32), 20px); }
    /* a photograph on one narrow leaf must not take the whole leaf */
    .journal-photo img { max-height: 30cqh; width: auto; max-width: 100%; margin: 0 auto; }
    .journal-opening-title { font-size: 1.5em; }
    .book--journal .book-corner { width: 3.2rem; height: 3.2rem; }
    /* PB1 (2026-10-02, his iPhone 393 wide): the text block stops above the
       3.2rem page-turn corners, so no line is set under them or the folio;
       and at ~30 characters a justified line opens rivers ("descent.  The
       loss  function"), so the one narrow leaf is set ragged right, still
       hyphenated, as a pocket book is. */
    .journal-clip { bottom: 3.5rem; }
    .journal-flow { text-align: left; }
    /* PH10 (2026-10-02): ON THE PHONE AN OPEN BOOK IS THE PAGE, the full
       width of the glass less a hair, and it stands in the middle of the
       height; there are no tabs at the foot to stand on any more (PB1).
       Tommy's ruling: "opens it upright as one full-width leaf". It was
       78% of 393 px. */
    /* TB1: and no taller than an 11-inch iPad's upright leaf, so a 12.9-inch one
       keeps a line of about 70 characters (the dimmed desk round it) */
    html.phone .book--journal { --jh: min(calc(100cqh - 5rem), calc((100cqw - 12px) / .68), 1100px); }
    html.phone .book--journal .journal-body { bottom: calc((100cqh - var(--jh)) / 2); }
    html.phone .book--journal[data-open="true"] .journal-body { max-height: var(--jh); }
    html.phone .book:not(.book--journal) { --kw: min(calc(100cqw - 12px), calc((100cqh - 5rem) * .666667)); }
    html.phone .book:not(.book--journal) .book-body { width: var(--kw); bottom: calc((100cqh - var(--kw) * 1.5) / 2); }
    html.phone .book:not(.book--journal)[data-open="true"] .book-body { max-height: calc(var(--kw) * 1.5); }
}
html.phone .phone-books {
    position: fixed; left: 0; top: 0; width: 100vw; height: 100dvh;
    container: books / size; z-index: 40; pointer-events: none;
}
/* on its side with a book out: turned so the book's head is where the
   phone's head is, a quarter turn from upright. TR1: phone.js sets its
   size (the glass's UPRIGHT size, so the turn does not deal the page
   again) and its matrix (turned, scaled to fit, centred); the map round
   it is dimmed by its shadow */
/* ==== LA1/LA2 (2026-10-03): THE TURN IS THE WINDOW'S SHAPE, read here by
   the media query so the first frame of a new shape is laid right before
   any script runs (phone.js sets only the numbers: the glass's two sizes,
   which way it came round). Upright the map's canvas keeps its size on its
   side and lies turned under the Journal; on its side a book brought out
   there lies turned at the upright glass's size; either way up the map
   under a book is dimmed. ==== */
@media (orientation: portrait) {
    /* RU1: but not in the room: upright off the map the canvas is the
       upright glass (phone.js `uproom`) */
    html.phone:not(.uproom) .canvas {
        width: var(--land-w, 100vh); height: var(--land-h, 100vw); transform-origin: 0 0;
        transform: translate(50vw, 50dvh) rotate(var(--crot, 90deg)) translate(-50%, -50%);
    }
    html.phone.bookout .phone-books { background: rgba(22, 15, 9, .55); }
}
@media (orientation: landscape) {
    /* ST1: on its side the books' frame keeps the UPRIGHT glass's size
       whether or not a book is out, so a book is never laid out at the
       wide shape (a stowed one keeps its page; one brought out is ready) */
    html.phone .phone-books { width: var(--up-w); height: var(--up-h); }
    html.phone.sidebook .phone-books {
        width: var(--up-w); height: var(--up-h); transform-origin: 0 0;
        transform: translate(50vw, 50dvh) rotate(var(--rot, -90deg)) scale(var(--up-sc, 1)) translate(-50%, -50%);
        background: rgba(22, 15, 9, .55);
        box-shadow: 0 0 0 100vmax rgba(22, 15, 9, .55);
    }
    /* TB2 (2026-10-06): a tablet on its side is laid as the desk, and a
       book brought out there lies the right way up over it, as a desk's
       does: the frame is the glass itself, not turned */
    html.phone.tablet .phone-books,
    html.phone.tablet.sidebook .phone-books { width: 100vw; height: 100dvh; transform: none; }
    /* an upright reading's book, on its way to being put down by the turn,
       is not drawn on its side for the frame before it is */
    html.phone:not(.sidebook) .phone-books .book[data-open="true"] > :not(.book-cover),
    html.phone:not(.sidebook) .phone-books .book[data-open="true"] > :not(.book-cover) *,
    html.phone:not(.sidebook) .phone-books .tm.is-page { visibility: hidden !important; }
}
/* ST1: a book put away by a turn is open and not drawn (phone.js) */
html.phone .phone-books .book.stowed .book-cover { pointer-events: auto; }   /* its spine brings it out */
html.phone .phone-books .book.stowed > :not(.book-cover),
html.phone .phone-books .book.stowed > :not(.book-cover) * { visibility: hidden !important; }
/* a turn's book comes and goes at once: the window's turning is the motion */
html.phone.turning .book, html.phone.turning .book *,
html.phone.turning .phone-books { transition: none !important; animation: none !important; }
html.phone .phone-books .book { transform: none; }
/* upright the Journal is the page; a shut cover is never shown there, nor
   any cover while a book is out */
html.phone .phone-books .book-cover,
html.phone.offmap .phone-books .book-cover { visibility: hidden; }
@media (orientation: landscape) {
    html.phone:not(.bookout):not(.offmap) .phone-books .book-cover { visibility: visible; }
}
/* ON ITS SIDE THE TWO BOOKS ARE SPINES at the foot of the glass, below the
   plate (engine.js lays it across the middle of the height), clear of the
   home bar and the notch, each a thumb high */
@media (orientation: landscape) {
    html.phone .phone-books .book-cover {
        position: fixed; top: auto; right: auto;
        bottom: max(8px, env(safe-area-inset-bottom));
        left: calc(max(10px, env(safe-area-inset-left)) + var(--spine-x, 0px));
        width: 8.4rem; height: 2.75rem; transform: none;
    }
    html.phone .phone-books #journal .book-cover { --spine-x: 9.1rem; }
    html.phone .phone-books .book-face {
        top: 0; height: 100%; transform: none; opacity: 1;
        background: var(--cloth); border: 0; border-radius: 2px 3px 3px 2px;
        box-shadow: 0 1px 0 rgba(255,255,255,.08) inset, 0 3px 7px rgba(0,0,0,.45);
    }
    html.phone .phone-books .book-label {
        left: 50%; right: auto; top: 50%; bottom: auto; transform: translate(-50%, -50%);
        display: flex; align-items: baseline; gap: .45rem;
        padding: .2rem .7rem .24rem; white-space: nowrap;
    }
    html.phone .phone-books .book-label-rule { display: none; }
    html.phone .phone-books .book-label-a { font-size: .8rem; }
}
/* BV1 (2026-10-05, desk-books.js): on the open
   sideways map the real books lie on the paper's foot, so the spine
   buttons are laid unseen over them (`.a2-hit`) and a book's name is
   on its head, the part on the glass (`.desk-label--head`) */
html.phone .phone-books .book-cover { opacity: 0; }
html.phone .phone-books .book-cover:not(.a2-hit) { pointer-events: none; }
/* TB2: on a tablet laid as the desk the label is the book's hit box */
.desk-label.desk-label--hand { pointer-events: auto; cursor: pointer; }
.desk-label.desk-label--head .book-label { top: calc(var(--bk-z, 0px) + .5rem); bottom: auto; }
/* PK1 (2026-10-03): a day card held up to the eye (day.js `peek`) is in
   front of the laptop and the books, so what is DOM over them (the screen
   laid on the laptop, the paper labels on the covers) goes while it is */
.under-card { visibility: hidden !important; }   /* CP1: only what the card is over (day.js peekCover) */
/* LB1 (2026-10-03): the name alone on the phone. Tommy: "why not make the
   labels just 'Field Book' and 'Journal' and drop 'Reference' + 'The Essays'
   ... so the labels can fit nicely within the books". Desktop keeps both.
   Both ways up: the spines upright and the covers on their side. */
html.phone .book-label-b { display: none; }
/* LB2: the label a book on the desk wears on the phone, off the map
   (desk-books.js phoneLabels lays it on the drawn cover) */
.desk-label {
    position: fixed; left: 0; top: 0; transform-origin: 0 0;
    pointer-events: none; z-index: 2; visibility: hidden;
}
/* the phone's books are smaller than the desk's: the name on one line, as on its spines (LB1) */
.desk-label .book-label-rule { display: none; }
/* and the desk's own rules, not the narrow window's bottom bar's (which
   centre the label on the cover) */
.desk-label .book-face {
    position: absolute; left: 0; right: 0; bottom: 0; border: 0;
    border-radius: 2px 4px 4px 2px; opacity: 1; transform: none;
}
.desk-label .book-label {
    left: calc(var(--bk-sp, 12.8px) + 10.4px); right: .55rem; top: auto; bottom: .5rem;
    transform: none; display: block; padding: .26rem .2rem .3rem; white-space: nowrap;
}
.desk-label--journal .book-label {
    left: .55rem; right: calc(var(--bk-sp, 12.8px) + 10.4px);
    background: #e9dcbd; border-color: rgba(38,22,16,.55);
}
.desk-label .book-label-a { font-size: .72rem; white-space: nowrap; letter-spacing: .03em; }
/* PO1: the running head's box is 1rem with its overflow hidden (for the
   title's ellipsis), and iPhone WebKit sets the Goudy capitals taller than
   that (the line box sat 2 px above the clip even in Chrome): his iPhone showed
   "THE STEP DOWN" and "CONTENTS" with their tops shaved off. The box reaches
   .5rem higher; the type stays where it was. */
html.phone .journal-head { top: .55rem; padding-top: .5rem; }
/* the quiet sign on the contents' leaf that the phone turns (phone.js) */
.phone-turn { display: none; }
@media (orientation: portrait) {
    html.phone .journal-inner[data-mode="contents"] .phone-turn {
        display: block; position: absolute; z-index: 4; right: var(--jm-out); top: .5rem;
        width: 40px; height: 28px; color: var(--sepia); opacity: .85; pointer-events: auto;
    }
}
.phone-turn svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.1;
    stroke-linecap: round; stroke-linejoin: round; transform-origin: 30% 70%; }
.phone-turn.nudge svg { animation: phone-nudge .9s ease-in-out; }
@keyframes phone-nudge { 0%, 100% { transform: none; } 35% { transform: rotate(-24deg); } 70% { transform: rotate(5deg); } }
@media (prefers-reduced-motion: reduce) { .phone-turn.nudge svg { animation: none; } }


@media (prefers-reduced-motion: reduce) {
    .book[data-open="true"] .book-body { transition-delay: 0s; }
    .book-body, .book-face { transition: none; }
}

/* the reference, dealt out a leaf at a time (see paginateBook) */
.book-ref-source[hidden] { display: none; }
.book-ref > .book-leaf > .key-sec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* the land's legend (v24, C). The land itself — strip fields, furrows,
   pasture, woods, walls, the coast road and the building footprints — is
   drawn by the engine (js/room/marks.js, js/room/field.js); what is left
   here is the FIELD BOOK's key to it, which is DOM and is read. */
.lg-bound { fill: none; stroke: var(--ink); stroke-width: .8; }
.lg-furrow { fill: none; stroke: var(--sepia); stroke-width: .6; opacity: .7; }
.lg-tree { fill: none; stroke: var(--ink-2); stroke-width: .8; stroke-linejoin: round; }
.lg-wall { fill: none; stroke: var(--ink); stroke-width: 1.4; stroke-dasharray: 4 2; }
.lg-road-c { fill: none; stroke: var(--ink-2); stroke-width: 2.6; }
.lg-road { fill: none; stroke: var(--paper-hi); stroke-width: 1.4; }
.lg-foot { fill: var(--ink); }


/* =====================================================================
   THE ROOM ENGINE — the only renderer (THE SWITCH)

   One canvas holds the desk, the sheet, the books and the ground. The old
   plate's paper, marginalia, overlay, portal and loading state are not
   hidden here any more: they left the repo, and with them ~26 KB of the
   CSS that dressed them.

   `html.room` is set unconditionally in the document head and is kept for
   the one thing it still decides: whether there is an engine at all. With
   JS off the class is never added, and the `html:not(.room)` rules below
   are what keep the day's card and the light line off that page.
   ===================================================================== */
/* The ground under frame 1. The oak's mean tone (measured on the fit: rgb 66,55,43),
   so the ~250 ms of bare page before the first picture and the picture itself are
   the same brown — Tommy saw the old #1d140d "render one way and then change". */
html.room body, html.room .board { background: #6f5a43; }
/* ==== LD5 (2026-10-02): THE DESK AND THE SHEET AT FIRST PAINT ===========
   Tommy: "Agree about the loading screen made of objects." The head script
   (base.njk) puts the sheet's four corners where frame 1 will project them
   and strikes the desk's light from the sun; this paints them. No spinner,
   no words: a desk with a sheet on it, and frame 1 lands on top of it. The
   whole thing is gone in the frame the canvas arrives (room-ready). */
.first-desk { display: none; }
html.room .first-desk {
    display: block; position: fixed; inset: 0; pointer-events: none;
    background:
        linear-gradient(to bottom, transparent var(--fd-fy, 200vh), var(--fd-front, #2a201a) var(--fd-fy, 200vh)),
        linear-gradient(to bottom, transparent 45%, rgba(10, 6, 3, .24) var(--fd-fy, 100%)),
        radial-gradient(ellipse 80% 110% at var(--fd-x, 50%) 40%, var(--fd-pool, #6f5a43), var(--fd-edge, #4a3b2c));
}
html.room .first-shadow, html.room .first-sheet { position: absolute; inset: 0; clip-path: polygon(var(--fd-sheet, 0 0)); }
html.room .first-shadow { background: rgba(16, 10, 6, .45); transform: translate(3px, 7px); filter: blur(5px); }
html.room .first-sheet { background: var(--fd-paper, #ede3cc); }
html.room.room-ready .first-desk { display: none; }
/* JL1 (2026-10-04): A PHONE HELD UPRIGHT OPENS ON THE JOURNAL, so its first
   desk is the Journal's page, blank, where the page will be (the phone's
   rule, styles.css `html.phone .book--journal`), on the board dimmed as it
   is under a book. No sheet and no desk: phone.js PJ1 opens the Journal in
   the frame this goes. */
@media (orientation: portrait) {
    html.room.phone .first-desk { background: #3e3123; container-type: size; }
    html.room.phone .first-shadow { display: none; }
    html.room.phone .first-sheet {
        background: var(--leaf);
        --jh: min(calc(100cqh - 5rem), calc((100cqw - 12px) / .68), 1100px);
        inset: auto; clip-path: none; width: calc(var(--jh) * .68); height: var(--jh);
        left: 50%; top: 50%; transform: translate(-50%, -50%);
        box-shadow: 0 2px 4px rgba(0,0,0,.36), 34px 9px 52px rgba(0,0,0,.6), inset 0 0 0 1px rgba(0,0,0,.4);
    }
} /* the amber desk's mean tone under the falling light (edge default, 2026-09-14); was oak's #42372b */

/* =====================================================================
   R4 — THE DOM DOES NOT GET AHEAD OF THE PICTURE

   Tommy, on the load: "you get text in the top left like 'at the survey
   front' - if it'll take a bit of time to draw what should the page show
   before that?" The card's typing and the books' labels are DOM inside
   the canvas, so the browser painted them at the FIRST PAINT — a couple
   of hundred milliseconds before there was a card or a book under them,
   floating on bare oak in the corner of the screen.

   They now wait for the frame that draws the thing they are written on.
   `room-ready` is set in the same frame as the desk, the sheet and the
   books; `room-map` in the frame that opens the window in the paper, which
   is where the card is. Before either, the page is the desk and nothing
   else: no blank paper, no spinner, no words.

   visibility, never display: the boxes are laid out from the start, so
   the class landing costs a paint and not a relayout.
   ===================================================================== */
html.room:not(.room-ready) .book,
html.room:not(.room-ready) .sheet-corner,
html.room:not(.room-ready) .sheet-clip,
html.room:not(.room-ready) .sheet-index,
html.room:not(.room-ready) .sheet-handle,
html.room:not(.room-ready) .day-card { visibility: hidden; }

.room-canvas {
    position: absolute; inset: 0; display: block;
    width: 100%; height: 100%;
    touch-action: none; cursor: grab;
}
.room-canvas:active { cursor: grabbing; }

/* The terminal remains lazy and interactive only after the reader asks for
   it. Until then this quiet phosphor face is part of the laptop itself: the
   same title and prompt, projected onto the real display, with no focus or
   hit surface of its own. */
.lap-passive {
    position: absolute; left: 0; top: 0; z-index: 39; overflow: hidden;
    box-sizing: border-box; transform-origin: 0 0; visibility: hidden;
    pointer-events: none; user-select: none;
    padding: 8px 12px;
    color: #6bb77b; background: #0b0f0c;
    background-image: linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,.24) 50%);
    background-size: 100% 3px;
    font: 12px/1.45 "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    text-shadow: 0 0 6px rgba(120,255,160,.20);
}
.lap-passive.is-laid { visibility: visible; }
.lap-passive-title { color: #568a61; }
.lap-passive-line { display: flex; align-items: baseline; gap: 6px; margin-top: .45em; }
.lap-passive-prompt { color: #b49a62; }
.lap-passive-cursor { width: .62em; height: 1.1em; background: #6bb77b; opacity: .38; }

/* =====================================================================
   E6 — THE TYPE ON THE DAY'S CARD

   The card itself is an object in the scene (js/room/day.js): its stock,
   its red head rule, its feint rules, its cut edge and its shadow are
   drawn on the canvas under the same sun as the books. What is here is
   the TYPING, and it is DOM for one reason — text that is READ stays
   over the canvas, crisp and selectable at every zoom, which is the
   split Google Maps makes and the one this engine keeps.

   It is laid by the SAME PORTAL ARITHMETIC the mesh is: day.js projects
   three of the card's own corners through the one camera and hands the
   result over as a single affine transform, so the type is on the card
   and not merely near it. `transform-origin: 0 0` is what makes that
   matrix mean what it says.

   NOTHING IS SIGNPOSTED. There is no border, no panel, no title and no
   hint: the card is never empty, so it says what it is by saying
   something.
   ===================================================================== */
.day-card {
    position: absolute; left: 0; top: 0;
    transform-origin: 0 0;
    pointer-events: none;
    /* OVER THE CANVAS, UNDER THE BOOKS. The canvas is appended to the
       window at boot, so it is the last child and paints over anything
       declared before it in the document; the type has to say so. An
       open book (z 60) is still the front thing on the desk. */
    z-index: 30;
    /* the type is measured off the card's own width, so it holds its
       proportion at every window: --dck is (card width / 320) */
    --dck: 1;
    --dcf: calc(11.4px * var(--dck));
    /* N2: the left margin was widened to clear the drawing pin that went
       through the card's north-west corner. There is no pin; what the
       width is now is a ruled card's own left margin, which is what it
       looked like anyway. */
    padding: calc(9px * var(--dck)) calc(15px * var(--dck)) calc(8px * var(--dck)) calc(27px * var(--dck));
    box-sizing: border-box;
    display: grid;
    grid-template-rows: auto auto auto auto 1fr auto;
    row-gap: calc(1.5px * var(--dck));
    font-family: 'Special Elite', 'Courier Prime', Courier, monospace;
    font-size: var(--dcf);
    line-height: calc(15.6px * var(--dck));
    color: #3a3128;
    /* a change of day is a change of text inside this box and nothing
       outside it */
    contain: layout style paint;
    -webkit-font-smoothing: antialiased;
}
.day-card p { margin: 0; }
/* LK1b: the card's GL stock dims outside the lamp's pool at night, so its
   typing dims with it (the pale head line was vanishing into the stock) */
.day-card { filter: brightness(calc(1 - .5 * var(--dim, 0))); }
html.dbg-nofilter .day-card, html.dbg-nofilter .day-card * { filter: none !important; }
html.dbg-noshadow .day-card, html.dbg-noshadow .day-card * { box-shadow: none !important; }
/* A NEW DISCOVERY IS A CARD PUT INTO THE READER'S HAND. The resting date
   card remains part of the desk scene; only `data-discovery=1` uses this
   centred piece of ruled stock. There is no dimmer and no bright app
   panel: the room remains visible around an index card with cut edges, a
   red head rule and the same typewriter face as the card on the map. */
.day-card[data-discovery="1"] {
    left: 50%; top: 50%;
    width: min(390px, calc(100vw - 28px)) !important;
    height: auto !important;
    min-height: min(228px, calc(100vh - 28px));
    max-height: calc(100vh - 28px);
    overflow: auto;
    transform: translate(-50%, -50%) rotate(-1.1deg) !important;
    z-index: 90;
    pointer-events: auto;
    --dck: 1.06 !important;
    padding: 25px 24px 18px 30px;
    row-gap: 4px;
    border: 1px solid rgba(65,52,35,.34);
    border-radius: 1px 2px 2px 1px;
    background:
        linear-gradient(90deg, transparent 0 26px, rgba(150,50,42,.34) 26px 27px, transparent 27px),
        repeating-linear-gradient(0deg, transparent 0 24px, rgba(100,86,63,.11) 24px 25px),
        radial-gradient(ellipse at 28% 12%, rgba(255,255,255,.58), transparent 48%),
        #eee5cf;
    box-shadow:
        0 1px 0 rgba(255,255,255,.65) inset,
        1px 2px 0 rgba(62,43,24,.14),
        7px 12px 22px rgba(24,17,10,.34);
    contain: layout style paint;
    animation: discovery-card-in 190ms cubic-bezier(.2,.75,.32,1) both;
}
/* AL1: the pit's card is held to the left of the glass, so the pit the
   view has flown to can be seen standing up beside it. AL8 (2026-09-27):
   moved left only, at the discovery card's own height — it was lifted to
   the top-left corner too. Tommy: "can't it just move left, does it have
   to move up too?" */
.day-card[data-discovery="1"][data-discovery-at="corner"] {
    left: 28px; top: 50%;
    transform: translateY(-50%) rotate(-1.1deg) !important;
}
.day-card[data-discovery="1"]::before {
    content: "";
    position: absolute; left: 0; right: 0; top: 19px;
    border-top: 1px solid rgba(150,50,42,.56);
    pointer-events: none;
}
.day-card[data-discovery="1"]::after {
    content: "";
    position: absolute; width: 8px; height: 8px; right: 12px; top: 8px;
    border-radius: 50%;
    background: rgba(72,58,40,.18);
    box-shadow: inset 0 1px 2px rgba(35,25,15,.38), 0 1px 0 rgba(255,255,255,.45);
    pointer-events: none;
}
.day-card[data-discovery="1"] .dc-state { display: none; }
.day-card[data-discovery="1"] .dc-date { font-size: 14px; margin-top: 2px; }
.day-card[data-discovery="1"] .dc-fig { font-size: 13px; font-weight: 700; }
.day-card[data-discovery="1"] .dc-level,
.day-card[data-discovery="1"] .dc-settle { line-height: 1.38; }
.day-card[data-discovery="1"] .dc-essay {
    margin-top: 8px;
    padding: 8px 4px 2px;
    min-height: 35px;
}
.day-card[data-discovery="1"] .dc-essay-t { font-size: 11px; letter-spacing: .045em; }
@keyframes discovery-card-in {
    from { opacity: 0; margin-top: 12px; }
    to { opacity: 1; margin-top: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .day-card[data-discovery="1"] { animation: none; }
}
/* While the card is held, the books remain where they are and take no
   focus, hover or click through it. The card is the nearest object. */
html.room-card-present .book,
html.room-card-present .sheet-corner,
html.room-card-present .sheet-clip,
html.room-card-present .sheet-index,
html.room-card-present .sheet-handle { pointer-events: none; }
/* THE TYPEWRITER IS NOT EVEN. Every line is struck a shade differently
   and sits a hair off its neighbour's baseline, because a card typed by
   hand on a machine does. */
.dc-state {
    font-size: calc(9.2px * var(--dck));
    letter-spacing: .06em; text-transform: lowercase;
    color: #8a7c68;
    /* A FIXED ROW, full or empty. With `min-height` alone an empty line
       stood 11 px and a typed one took the card's 15.6 px line box, so
       "at the survey front" pushed the date 4.6 px down onto the red
       head rule the canvas draws at a fixed height (Tommy, 2026-09-14:
       "the red line on the card goes through the date"). The date's
       baseline now sits on the rule whether this line is typed or not. */
    height: calc(11px * var(--dck)); line-height: calc(11px * var(--dck));
    overflow: hidden;
    transform: translateX(calc(1px * var(--dck)));
}
/* R24: the card said "Pinned — Esc to release" on the small grey line
   above, in the revision ink, and the rule that inked it went with the
   words. Tommy: "remember this is physical, not a computer!" — nothing
   on this card is allowed to talk about the interface. What that line
   carries is the survey front's own line (day.js, copy.json
   `card.state_front`); N2's memorandum head stood on it between
   2026-09-20 and NL1, and went with the memorandum. */
.dc-date {
    font-size: calc(13.2px * var(--dck));
    color: #2f2820; letter-spacing: -.01em;
    margin-top: calc(1px * var(--dck));
}
/* NL1 (2026-09-21): MG1's memorandum mark stood here — the drawing of the
   hint's own mark in the card's left margin, shown one at a time off
   `data-memo`. The memorandum has left this card for the ruled list on the
   note under the map, and the drawings went with it (`.nc-mark`, below),
   so the margin is the day's again and nothing on this card is struck off.
   NC1 (2026-09-22): the list is back on this card on a folded sheet
   (`.dc-list`, below), WITHOUT the drawings — those were the note's plan.
   FU1/FU2 (2026-09-27): the note is gone and each hint carries its glyph. */
.dc-fig { color: #4a4034; transform: translate(calc(-.5px * var(--dck)), calc(.5px * var(--dck))); }
.dc-level { color: #5b5043; font-size: calc(10.6px * var(--dck)); }
.dc-settle {
    color: #6b6052; font-size: calc(10px * var(--dck));
    line-height: calc(12.4px * var(--dck));
    align-self: start;
}
/* THE ONE DOOR TO THE ESSAYS. The mark on the plate does not open the
   Journal any more (v32 A, kept): this does, and it is the only thing on
   the card the hand can take hold of. */
.dc-essay {
    pointer-events: auto;
    display: block; width: 100%; text-align: left;
    margin: 0; padding: calc(3px * var(--dck)) 0 0;
    border: 0; border-top: 1px solid rgba(150,50,42,.34);
    background: none; cursor: pointer;
    font: inherit; font-size: calc(10.2px * var(--dck));
    line-height: calc(12.2px * var(--dck));
    color: #96322a;
}
.dc-essay[hidden] { display: none; }
.dc-essay-t { display: block; }
.dc-essay-go {
    display: block; font-size: calc(8.8px * var(--dck));
    letter-spacing: .10em; color: #a6604f;
}
.dc-essay:hover .dc-essay-go, .dc-essay:focus-visible .dc-essay-go { color: #96322a; }
.dc-essay:focus-visible { outline: 1px dashed rgba(150,50,42,.7); outline-offset: 2px; }
/* TC1: the phone's two-card ones (day.js twoCard): which words are on the card on top. The pit: its head and
   first line, then its head and the clue. OC1: the sea's is one card, the way in and London, without the list. */
html.phone .day-card[data-two="pit"] .dc-state,
html.phone .day-card[data-two="pit"][data-page="1"] #dc-date,
html.phone .day-card[data-two="pit"][data-page="0"] #dc-settle,
html.phone .day-card[data-two="pit"][data-page="1"] #dc-level,
html.phone .day-card[data-two="sea"] .dc-punch-cols { display: none !important; }
/* VL1: under way it is the list and London, without the line over it */
html.phone .day-card[data-two="voy"] .dc-punch-top { display: none !important; }
html.phone .day-card[data-two="voy"] .dc-punch-away,
html.phone .day-card[data-two="voy"] .dc-punch-london { white-space: normal !important; }
html.phone .day-card[data-two="voy"] .dc-punch-cols { grid-template-columns: max-content max-content !important; }
html:not(.room) .day-card { display: none; }

/* FU2 (2026-09-27): X2b/N1's note under the map (`.note-card`, `.nc-*`,
   the plan's rings and re-inking, `.np-g`, `.np-ring`) is deleted with the
   card; its glyphs are in the margin of the file card's hint list now
   (`.dc-g`, below). What is kept is the drawings' own pencil, which the
   list's glyphs wear: the weights are in the glyph's 20-unit box. */
.np-ink {
    fill: none;
    stroke: #4a4034;             /* the card's own pencil — the list's colour */
    stroke-width: .9;
    stroke-linecap: butt;
    stroke-linejoin: miter;
}
.np-ink .sv-pg-fill { fill: #4a4034; stroke: #4a4034; stroke-width: .55; }
.np-ink .sv-pg-faint { stroke: #8b8071; stroke-width: .62; }

/* =====================================================================
   NC1 (2026-09-22) — THE MEMORANDUM: THE RULED LIST ON THE DAY CARD

   Tommy: "change the next hint card to actually be a list of all the
   hints that get struck through as they're done ... that was meant to be
   on the file cards, not the hidden cards." NL1's list, moved here off
   the note under the map, and shown only on a folded sheet at rest
   (day.js, THE MEMORANDUM, removes `hidden`).

   IT IS LAID OVER THE STOCK'S OWN RULING. The card is ruled from its red
   head rule (20.5% of its height) to its last feint rule (94.5%); while
   the list is up day.js rules that span in N spaces, one to each ROW of
   type the list needs (NC1 (2): the pit's hint and the closing line wrap
   and take two each), and this box is exactly that span at a line-height
   of one pitch, so every row stands on a printed rule at any window and
   the card is not re-cut.

   AN ERRAND NOT YET REACHED WAS A BLANK RULE until FU1 (below), which
   writes every line from the start. NL2's rule holds here as on the note: nothing
   in this block may say `visibility: visible`, because the card itself
   is hidden by visibility before the room is ready and a `visible` child
   outranks a hidden parent.

   THE STRIKE IS THE TYPE'S OWN BACKGROUND (X2b's pen rule, laid by
   day.js, a different hand each line); `box-decoration-break: clone`
   strikes a wrapped line on both rows with nothing measured. It grows
   from no width to full, which is the pen travelling.

   NO DRAWINGS was NC1's rule; FU1 puts each mark's drawing in its line's
   margin (`.dc-g`, below), on Tommy's word, and FU2 deleted the note.
   ===================================================================== */
.dc-list {
    position: absolute;
    left: calc(27px * var(--dck)); right: calc(15px * var(--dck));
    top: 20.5%; height: 74%;
    margin: 0; padding: 0; list-style: none;
    /* NC1 (2): ONE ROW TO ONE RULED SPACE. `--dcp` is the pitch of the
       stock's ruling over this span in px (day.js memoFit: the span over
       N, the rows the list needs), and it is the line-height, so a hint
       that wraps takes a space a row. The block is let down a fifth of a
       pitch so each row's baseline sits just above its rule rather than
       in the middle of its space. */
    --dcp: calc(13.9px * var(--dck));
    transform: translateY(calc(var(--dcp) * .2));
}
.dc-list[hidden] { display: none; }
.dc-line {
    display: block;
    font-size: calc(9.2px * var(--dck));
    line-height: var(--dcp);
    color: #4a4034;
    transition: color .45s ease-out;
}
/* the words stay INLINE, so a wrapped line gets its own strike per row */
.dc-w { display: inline; }
/* FU1 (2026-09-27): EVERY LINE IS WRITTEN FROM THE START. Tommy: "the
   hint card actually showing all hints at once and them crossing through
   as you find them". NC1's blank rules (an errand not yet reached hidden
   by selector) are gone; only the closing line still waits. */
.dc-line { position: relative; }
.dc-line.is-struck { color: #6b6052; }
/* FU1: AND EACH LINE CARRIES ITS MARK'S DRAWING IN THE MARGIN. Tommy:
   "the hint card having each related glyph at the left of the hint line".
   The card's own 27-unit left margin holds it, centred on the line's first
   row; it is the drawing in the card's pencil (`.np-ink`, above), and
   re-inked in the revision red as the line is struck, as the note's
   plan did (FU2 deleted the note). */
.dc-g {
    position: absolute;
    left: calc(-21px * var(--dck));
    top: calc((var(--dcp) - 13px * var(--dck)) / 2);
    width: calc(13px * var(--dck)); height: calc(13px * var(--dck));
    overflow: visible;
}
/* the weights are in the glyph's 20-unit box, and this box is a third of
   the note's, so they are gone over at twice the note's to read the same */
.dc-g .np-ink { stroke-width: 1.8; }
.dc-g .np-ink .sv-pg-fill { stroke-width: 1.1; }
.dc-g .np-ink .sv-pg-faint { stroke-width: 1.24; }
.dc-line.is-struck .dc-g .np-ink { stroke: var(--accent); stroke-width: 2.4; }
.dc-line.is-struck .dc-g .np-ink .sv-pg-fill { fill: var(--accent); stroke: var(--accent); stroke-width: 1.4; }
.dc-line.is-struck .dc-g .np-ink .sv-pg-faint { stroke: var(--accent); stroke-width: 1.6; }
.dc-list.is-settled .dc-g .np-ink,
.dc-list.is-settled .dc-g .np-ink * { transition: stroke .45s ease-out, stroke-width .45s ease-out, fill .45s ease-out; }
.dc-t {
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    /* the rule overruns the words a little either end, the way a struck
       line does, without moving the type */
    padding: 0 .22em;
    margin: 0 -.22em;
    background-repeat: no-repeat;
    background-position: 0 50%;
    background-size: 0% 100%;
}
.dc-line.is-struck .dc-t { background-size: 100% 100%; }
/* THE CLOSING LINE, written only once there is nothing left to write
   (copy.json `card.hint_done`); its space is ruled from the start */
.dc-done { color: #8a7c68; }
.dc-list:not(.is-done) .dc-done .dc-t { visibility: hidden; }
.dc-list.is-settled .dc-t { transition: background-size .42s ease-out; }
@media (prefers-reduced-motion: reduce) {
    .dc-line, .dc-list.is-settled .dc-t,
    .dc-list.is-settled .dc-g .np-ink, .dc-list.is-settled .dc-g .np-ink * { transition: none; }
}

/* =====================================================================
   X2 — THE ROOM IS A MODE, AND THE SHEET CAN BE FOLDED

   Two classes on the root and nothing else. `room-standing` is the reader
   on their feet: everything the paper carries stays exactly where it is —
   the index is still printed at the foot of the sheet and the light line
   is still in the margin — and none of it takes a pointer, because in the
   room you are not working the map. `room-folded` is a sheet in eight:
   the print on the paper folds with the paper because it is IN the paper's
   own texture, but the DOM laid OVER the paper cannot fold, so it goes
   with the map it belongs to and comes back when the sheet is opened.

   Nothing here is signposted, and nothing here is a cursor: a reader who
   never stands up and never folds anything sees not one rule of this.
   ===================================================================== */
html.room-standing .sheet-index,
html.room-standing .sheet-corner,
html.room-standing .sheet-clip,
html.room-standing .day-card { pointer-events: none; }
/* R47: two of the three have their own answer now and neither is a class.
   THE YEAR INDEX is print on the one panel of the eight that never moves,
   so it stays where it is printed until the east half comes down over it
   — `Room.onPaperQuad` returns null the moment the paper it is on stops
   being uncovered, and index.js has hidden itself on a null since R21.
   THE DAY'S CARD is a piece of index stock lying on the map, and the map
   folding does not make it vanish: it slides off on to the oak (day.js
   foldSlide) and is a card on the desk for the rest of the fold, typing
   and all. What is left here is the LIGHT LINE, which is set in the foot
   margin east of the middle crease — its own panel is in the air at the
   first degree of the first turn, and DOM cannot go with it. */
/* (N1: the light line is in the print now and folds with it; its rule went) */
/* THE LIGHT LINE (E9) was DOM set here; N1 struck it into the sheet's own
   print (engine.js strikeNote) -- same face, same ink as the datum notes. */

/* =====================================================================
   R23 — WHERE THE HAND GOES FOR THE SHEET'S OWN CORNER

   R14's block was the drawing pin: five inks, two states, a 2.5 px lift, a
   19-unit grip. All of it has gone with the pin (pin.js says why). What is
   left is a box that draws nothing at all — the corner it sits over is
   WebGL, lifted and curled by objects.js, and the only job the DOM has is
   to take the pointer and say `pointer`.

   It is laid in the SHEET'S OWN UNITS (one css px of this element is one
   sheet unit, `Room.onPaper(x, y, 1)`), sized by pin.js from the corner's
   own chamfer, and it is inert until there is something to undo.
   ===================================================================== */
.sheet-corner, .sheet-clip, .sheet-index {
    position: absolute; left: 0; top: 0;
    transform-origin: 0 0;
    pointer-events: none;
    /* over the canvas (the window's last child), under the card and under
       an open book — the corner is the sheet's, not the reading's */
    z-index: 20;
}
.sheet-index { overflow: visible; }
/* NOTHING IS LIVE UNTIL IT HAS BEEN LAID ON THE PAPER. Both elements are
   absolute with no transform of their own until pin.js's first frame, and
   an untransformed one sits at the window's origin — the top-left corner
   of the bare oak. The corner's box draws nothing, so this is only about
   where the pointer would answer. */
.sheet-corner:not(.is-laid), .sheet-clip:not(.is-laid),
.sheet-index:not(.is-laid) { visibility: hidden; }

/* THE ONLY THING IT SAYS ABOUT ITSELF IS THE CURSOR — and the paper
   lifting under it, which is not a style, it is the scene. At the fit the
   sheet is already home: the box takes no pointer at all, so a hover over
   the corner of a fitted sheet changes exactly nothing.
   Tommy, on v11: "let the UI just be discovered by the movement on hover". */
.sheet-corner.is-live { pointer-events: auto; cursor: default; }
/* B2: AND THE ONE TIME THE CORNER SAYS ANYTHING is the widest view, where
   a reader who keeps rolling out has already asked to put the map away.
   The corner is tugged up a few millimetres by objects.js and the cursor
   is the only other word: take hold of this. Nowhere else, because the
   fold is not meant to be the first thing found. */
.sheet-corner.is-tug { cursor: grab; }
.sheet-corner.is-tug:active { cursor: grabbing; }

/* =====================================================================
   B1 — THE CLIP AT THE HEAD OF THE SHEET

   A drawing-board spring clip: a barrel lapped over the top edge, a jaw
   plate pressing the paper, a lip where the metal meets the map, and the
   shadow it throws. Painted graphite rather than chrome, so it belongs to
   the same warm, matte world as the oak and the paper. It lies on the
   sheet's own plane (pin.js lays it with `Room.onPaperQuad`, 190 x 132
   sheet units at 965, -22), so all it has to do here is have a state.

   CLOSED is the map clipped flat, which is how it starts. OPEN is the jaw
   stood up on its barrel, which is what the first pan or zoom does to it
   and what it stays until the map is home. The open jaw is a scaleY about
   the barrel's own axis — a hinge seen from above IS a foreshortening,
   and doing it that way costs one transform and no geometry.
   ===================================================================== */
.sheet-clip { overflow: visible; }
.sheet-clip .sc-svg { display: block; overflow: visible; }
.sheet-clip .sc-shade rect { fill: #221a0e; opacity: 0.30; }

/* S3 — THE THREE METALS, AS FOUR STOPS AND FOUR FLAT PARTS.
   pin.js says what the object is; everything below is the metal. The
   shape of every part is shared by all three, so a metal is now just its
   run of stop-colours: DIM, MID, LIT. None of the three is polished — a
   mirror finish under one green-shaded lamp would be the brightest thing
   in the room and the eye would go to the clip rather than to the survey.
   Aged warm brass is the default and the other two are its neighbours in
   value, not in hue. */
.sheet-clip .sc-plate { fill: url(#sc-gp); }
.sheet-clip .sc-tongue { fill: url(#sc-gt); }
.sheet-clip .sc-barrel { fill: url(#sc-gk); }
.sheet-clip .sc-screw-head { fill: url(#sc-gs); }
.sheet-clip .sc-hair { fill: none; stroke-width: 1.3; stroke-linecap: round; }
.sheet-clip .sc-pin { stroke-width: 3; stroke-linecap: round; }
.sheet-clip .sc-wire { fill: none; stroke-width: 5; stroke-linecap: round; }

/* a — AGED WARM BRASS (the default). #b8925a mid against #f1e7d1 paper is
   warm and dull; the shade is the brass in its own shadow and the light
   is the polish a forty-year-old fitting keeps only on its edges. */
.sheet-clip.clip-a .sc-s-dim { stop-color: #7a5a2c; }
.sheet-clip.clip-a .sc-s-mid { stop-color: #b8925a; }
.sheet-clip.clip-a .sc-s-lit { stop-color: #e6c98a; }
.sheet-clip.clip-a .sc-plate-lit, .sheet-clip.clip-a .sc-lip-lit,
.sheet-clip.clip-a .sc-barrel-lit { fill: rgba(255, 246, 214, 0.42); }
.sheet-clip.clip-a .sc-gleam { fill: rgba(255, 248, 224, 0.26); }
.sheet-clip.clip-a .sc-lip { fill: #a07c42; }
.sheet-clip.clip-a .sc-hair { stroke: #5e4520; }
.sheet-clip.clip-a .sc-pin { stroke: #6a5024; }
.sheet-clip.clip-a .sc-screw-seat { fill: #6f5623; }
.sheet-clip.clip-a .sc-screw-slot { fill: #5a4320; }

/* b — BLACK-JAPANNED STEEL. Nearly black, with the japanning's own sheen
   kept narrow, and the two handles in bright drawn wire so they read
   against it. The only one of the three that is BLACK on cream, which is
   a strong mark to put on a survey — which is why it is not the default. */
.sheet-clip.clip-b .sc-s-dim { stop-color: #16150f; }
.sheet-clip.clip-b .sc-s-mid { stop-color: #2a2720; }
.sheet-clip.clip-b .sc-s-lit { stop-color: #4b463b; }
.sheet-clip.clip-b .sc-plate-lit, .sheet-clip.clip-b .sc-lip-lit,
.sheet-clip.clip-b .sc-barrel-lit { fill: rgba(255, 252, 244, 0.16); }
.sheet-clip.clip-b .sc-gleam { fill: rgba(255, 252, 244, 0.10); }
.sheet-clip.clip-b .sc-lip { fill: #8b8f95; }
.sheet-clip.clip-b .sc-hair { stroke: #0e0d09; }
.sheet-clip.clip-b .sc-wire { stroke: #b6bac0; }
.sheet-clip.clip-b .sc-wire-end { fill: #6d7278; }

/* c — PLAIN NICKEL. The same fitting re-metalled: cool, one value lighter
   than the oak, and the quietest of the three. */
.sheet-clip.clip-c .sc-s-dim { stop-color: #6e757b; }
.sheet-clip.clip-c .sc-s-mid { stop-color: #a6acb1; }
.sheet-clip.clip-c .sc-s-lit { stop-color: #dfe3e6; }
.sheet-clip.clip-c .sc-plate-lit, .sheet-clip.clip-c .sc-lip-lit,
.sheet-clip.clip-c .sc-barrel-lit { fill: rgba(255, 255, 255, 0.40); }
.sheet-clip.clip-c .sc-gleam { fill: rgba(255, 255, 255, 0.28); }
.sheet-clip.clip-c .sc-lip { fill: #8e959b; }
.sheet-clip.clip-c .sc-hair { stroke: #5b6268; }
.sheet-clip.clip-c .sc-pin { stroke: #676e74; }
.sheet-clip.clip-c .sc-screw-seat { fill: #6c7379; }
.sheet-clip.clip-c .sc-screw-slot { fill: #575e64; }

/* THE HINGE. The tongue turns on the knuckle's own axis (y 25 in the
   clip's units) and the plate, the screws and the barrels do not move,
   because they are screwed to the board. A hinge seen from above IS a
   foreshortening, so the leaf standing up is a scaleY and costs one
   transform and no geometry. */
.sheet-clip .sc-jaw, .sheet-clip .sc-shade {
    transform-origin: 95px 25px;
    /* CL3, 2026-09-22: AND THE SPRING DOES NOT ANIMATE EITHER. The note
       below (2026-09-20) found the hover's fault: a transform TRANSITION
       on an SVG group inside a projective plate is re-rasterised, with
       its gradients and blur, at another resolution while it animates,
       and the lighting jumps on a real GPU. The 420 ms spring open was
       the last such transition on the clip, and Tommy, 2026-09-22: "when
       the clip unlocks, within a second or so, the shadows/light flips
       horizontally". A spring clip snaps; the leaf is up in one frame. */
}
.sheet-clip.is-open .sc-jaw { transform: scaleY(0.62); }
/* and the open clip throws a LONGER shadow, not a shorter one: the leaf
   is off the paper now, so what it casts reaches further down the sheet */
.sheet-clip.is-open .sc-shade { transform: scaleY(1.07) translateY(3px); opacity: 0.82; }
/* the hand on it: a millimetre, and nothing said out loud. ONLY THE LEAF
   MOVES. The shade used to step with it (scaleY 1.07 -> 1.09, +1px,
   opacity .82 -> .86) and Tommy read that as "the lighting/shadow on the
   clip changes a lot when you hover it" (2026-09-19): a shadow that jumps
   is a lamp that moved, and nothing in the room moved but a fingertip.
   So the contact shadow is the OPEN shadow, on hover as at rest — the
   leaf's own 3.5 % of lean is not enough to reach the paper differently
   — and the tongue's gradients are the defs' and never change with the
   pointer at all. */
/* AND NOW NOT EVEN THE LEAF. Tommy, 2026-09-20, with the two pictures
   from the night before: "The clip is definitely not fixed! I just don't
   think you understand what the problem is!" — the whole clip's light
   changes under the hand: the tongue's gleam, the lip, the plate's
   shadow. Headless, the only pixels that move on hover are the lip's
   edge; on a real GPU a transform TRANSITION on an SVG group inside a
   projective plate makes the browser re-rasterise the group and its
   gradients and blur at another resolution while it animates, and that
   is a clip whose lighting jumps. So nothing on the clip moves under a
   hand any more: the cursor says it can be pressed, and the press itself
   (is-pressed, is-shutting) is the only motion. */
/* and the finger pressing it shut, which is faster than it springs open */
.sheet-clip.is-open.is-pressed .sc-jaw { transform: scaleY(0.90); }
.sheet-clip.is-open.is-pressed .sc-shade { transform: scaleY(1.01); opacity: 0.6; }
/* and held there once it has been pressed home (pin.js `is-shutting`):
   the leaf lies flat and the shadow is the shut one, until the map is at
   the fit and the clip is simply shut — no spring back between the two */
.sheet-clip.is-open.is-shutting .sc-jaw { transform: scaleY(1); }
.sheet-clip.is-open.is-shutting .sc-shade { transform: none; opacity: 1; }
/* THE PAPER AROUND IT IS STILL THE MAP. Only the metal takes the pointer,
   and only while the clip is open — a shut clip has nothing to say. */
/* TH1 (2026-10-07): AND NO GREY BOX ON A TAP. iOS lays its tap highlight over any element that takes a press, and on
   the clip that is the whole 174 x 117 hit rect (Tommy: "a whole big box that highlights around it"). Everything that
   can be pressed on this site shows its own press, so the highlight is off everywhere. Inherited. */
html { -webkit-tap-highlight-color: transparent; }
.sheet-clip .sc-hit { fill: none; stroke: none; pointer-events: none; }
.sheet-clip.is-open .sc-hit { pointer-events: all; cursor: pointer; }
@media (prefers-reduced-motion: reduce) {
    .sheet-clip .sc-jaw, .sheet-clip .sc-shade { transition: none; }
}
html:not(.room) .sheet-corner, html:not(.room) .sheet-clip,
html:not(.room) .sheet-index { display: none; }

/* S3 -- THE SECTION PANEL, LIVE: no rule here. The live panel is struck
   into the sheet's own print (handle.js, `Room.furnPatch`), so its ink
   rides inside the SVG it is rastered from, not in this sheet. */


/* =====================================================================
   R21 — THE YEAR INDEX, LIVE

   The rule, its ticks and the caption are still PRINT in the furniture
   texture; the five figures, the bracket and its grip are this element.
   index.js says why the black figures came out of the raster with the red
   one (five numbers on one rule cannot be in two different inks) and how
   the bracket is driven. Everything below is geometry and ink.

   AND THE TWO INKS ARE PRE-DARKENED BY DIFFERENT AMOUNTS, WHICH ONLY A
   MEASUREMENT WOULD HAVE SAID. E9 called the loss it corrected for
   "lighting" — the furniture "rastered off-thread and then lit and
   filtered as part of the sheet". SHEET_FS does no such thing: it is
   `col = mix(paper, f.rgb/f.a, f.a)` and nothing else, so printed ink and
   DOM ink are the same colour over the same paper. What differs is the
   WEIGHT of the glyph. The printed figures are drawn by the SVG
   rasteriser into a texture and then sampled; this element's are drawn by
   the page through a matrix, which is a path-rendering route with no
   hinting and no stem darkening — and stem darkening depends on the
   text's own colour against its background, which is why the correction
   is not one factor.

   Measured at the fit on R21-before-fit.png against R21-after-fit.png,
   over each figure's own 26 x 12 device-pixel box, as the mean of the box
   (the total ink a figure lays down, which is what the eye reads at 7 css
   px, rather than the darkest pixel, which is only its sharpness):

     printed --ink-2  #55483a   reads as  #312921 set here  (x0.57)
     printed --accent #96322a   reads as  #913329 set here  (x0.97)

   Both land within 0.5 of the printed figure's mean; the numbers and the
   pair of pictures are in REPORT-room-R21.md.
   ===================================================================== */
.sheet-index {
    z-index: 20;
    /* --ink-2 and --accent through the printed-type difference (above) */
    --sx-ink: #312921;
    --sx-red: #913329;
}
.sx-year {
    font-family: 'Sorts Mill Goudy', Georgia, serif;
    font-size: 9.5px; font-variant-numeric: lining-nums tabular-nums;
    letter-spacing: .02em; stroke: none;
    fill: var(--sx-ink); transition: fill .22s ease;
}
/* THE MARK: the year (or the two years) holding a real share of the
   window. v32's is-here, in the revision ink everything corrected on this
   sheet is drawn in. */
.sx-cell.is-here .sx-year { fill: var(--sx-red); }
.sx-bracket-line {
    fill: none; stroke: var(--sx-red); stroke-width: 1.4;
    stroke-linejoin: miter; stroke-linecap: butt;
}
/* THE TARGETS ARE TRANSPARENT AND THEY SAY NOTHING BUT THE CURSOR. v32
   washed the hovered cell with rgba(150,50,42,.1) and the hovered grip
   with .14 of the same; R14 took the cell's wash out when it made one cell
   live, because a hard-edged panel of tint on the paper reads as chrome,
   and the whole index keeps that ruling. Subtle, not signposted: a hand
   over the rule finds a pointer, a hand over the bracket finds a bar it
   can pull. */
.sheet-index { pointer-events: none; }
.sx-box  { fill: transparent; stroke: none; pointer-events: auto; cursor: pointer; }
.sx-grip { fill: transparent; stroke: none; pointer-events: auto; cursor: ew-resize; }
/* TM1: the boat in the section at sea, a way back to her */
.sx-boat { fill: transparent; stroke: none; pointer-events: auto; cursor: pointer; }
/* the marked cell at the fit has nothing to do, so it does not answer —
   the pin's rule (R14), and the reason a click on 2026 at home is silent */
.sheet-index.is-fitted .sx-cell.is-here .sx-box { pointer-events: none; }
/* A FINGER IS NOT A POINTER, again. v32's targets are 26 units deep, which
   is 20 css px at the fit and 9.8 on the 780 x 1410 canvas (measured). The
   DRAWING is untouched — it is paper furniture and scales with the paper,
   and a year's figure must not be a different size from the rule it stands
   on — and only the invisible rectangles that take the hand grow, as the
   pin's disc does. Both of them here, not just the grip: on this index the
   cells ARE the travel control, where the pin had three other ways home.
   Even doubled they are ~25 x 20 css px on that canvas, under the 44 px a
   thumb wants; the honest answer to that is a portrait layout, which the
   plan says not to design. */
@media (pointer: coarse) { .sx-box, .sx-grip { y: 826.6px; height: 52px; } }


/* =====================================================================
   X4 — THE NAME IN THE CARTOUCHE

   One box on the paper, laid by the same projection the year index is laid
   by. At rest it draws NOTHING: the name under it is printed in the
   furniture texture and this is a transparent rectangle that answers a
   hand with a cursor and nothing else ("subtle, not signposted" — a reader
   who never puts a hand on the cartouche never learns there is anything
   there). While it is being edited the printed name is blanked in the
   raster and this is the only name, set in the cartouche's own face at the
   size handle.js measures for it.
   ===================================================================== */
.sheet-handle {
    position: absolute; left: 0; top: 0;
    transform-origin: 0 0;
    pointer-events: none;
    z-index: 20;
}
.sheet-handle:not(.is-laid) { visibility: hidden; }
html:not(.room) .sheet-handle { display: none; }

/* the hand's target: the printed name's own band, and no further */
.sh-hit {
    position: absolute; left: 0; top: 0; right: 0;
    pointer-events: auto; cursor: text;
}
.sheet-handle.is-editing .sh-hit { pointer-events: none; }
/* EG3 (2026-10-02): on a phone the printed name is 8 css px high at the
   whole-sheet framing, which no finger finds; the target reaches a thumb's
   width round it (the box is drawn at the sheet's scale, about a third, so
   these are about 18 px on the glass) and stays invisible */
html.phone .sh-hit::before { content: ""; position: absolute; inset: -52px -30px; }

/* THE NAME, while a hand is in it. --ink is #2e261c; DOM type over this
   paper reads lighter than printed type at the same value (R21 measured
   the two inks of the year index), so it is set a shade darker, which puts
   the edited name at the weight of the name it replaced. */
.sh-edit {
    position: absolute; left: 0; top: 0; right: 0;
    display: none;
    font-family: 'Sorts Mill Goudy LN', 'Sorts Mill Goudy', Georgia, serif;
    color: #241d15;
    text-align: center;
    white-space: nowrap;
    outline: none;
    caret-color: #913329;
    box-sizing: border-box;
}
.sheet-handle.is-editing .sh-edit { display: block; pointer-events: auto; }
.sheet-handle.is-asking .sh-edit { opacity: .5; }
/* AND THE SELECTION IS NOT THE BROWSER'S BLUE. A system highlight across a
   cartouche is the one thing on this sheet that could not be mistaken for
   paper; the name is selected in the revision red the index's bracket and
   the cartouche's own dot are drawn in, at the weight of a wash. */
.sh-edit::selection { background: rgba(145, 51, 41, .20); color: inherit; }

/* CX1 -- THE CARTOUCHE SAYS NOTHING. No remark line, no way-back line:
   the deletion mark and the strike are the whole of its language.
   The MARK: red, hairline, off the east end of a stranger's printed name,
   shown while a hand is over the name or over the mark itself. The
   STRIKE: a pen rule through the name, grown from the west end and a
   little off level, faded once the name under it has changed. */
.sh-dele {
    position: absolute;
    display: none;
    opacity: 0;
    pointer-events: auto; cursor: pointer;
    transition: opacity 160ms ease;
}
.sh-dele svg { display: block; width: 100%; height: 100%; overflow: visible; }
.sh-dele path { fill: none; stroke: #96322a; stroke-width: 1.1; stroke-linecap: round; stroke-linejoin: round; }
/* shown faint whenever a stranger's record is up (Tommy: on hover alone
   "it's hard to find"), full under a hand */
.sheet-handle.is-guest .sh-dele { display: block; opacity: .5; }
.sheet-handle.is-guest .sh-hit:hover ~ .sh-dele,
.sheet-handle.is-guest .sh-dele:hover { opacity: 1; }
.sheet-handle.is-editing .sh-dele,
.sheet-handle.is-asking .sh-dele { display: none; }
.sh-strike {
    position: absolute;
    display: none;
    background: #96322a;
    opacity: .88;
    transform-origin: 0 50%;
    transform: scaleX(0) rotate(-0.6deg);
    pointer-events: none;
}
.sheet-handle.is-struck .sh-strike { display: block; transform: scaleX(1) rotate(-0.6deg); transition: transform 260ms cubic-bezier(.3,.05,.2,1); }
.sheet-handle.is-rubbed .sh-strike { opacity: 0; transition: opacity 300ms ease; }
/* the miss: the typed name fades under its strike before the print returns */
.sheet-handle.is-rubbing .sh-edit { opacity: 0; transition: opacity 300ms ease; }
@media (prefers-reduced-motion: reduce) {
    .sheet-handle.is-struck .sh-strike, .sheet-handle.is-rubbed .sh-strike,
    .sheet-handle.is-rubbing .sh-edit, .sh-dele { transition: none; }
}
